Android6.0許可權適配的那些坑
記錄一下自己在專案中做6.0適配時遇到的一些坑,希望大家可以少走一些彎路。
1.首先你需要把targetSdkVersion升級到23
2.主要API
public int checkSelfPermission (String permission)
被授權函式返回PackageManager.PERMISSION_GRANTED,否則返回PackageManager.PERMISSION_DENIED 。
public final void requestPermissions(@android.support.annotation.NonNull java.lang.String[] permissions,int requestCode)
為應用程式申請許可權,系統會彈出對話方塊,詢問使用者是否給予應用授權該許可權,使用者可以選擇允許或拒絕。開發者可以在Activity的 public void onRequestPermissionsResult(int requestCode,@NonNull String[] permissions,@NonNull int[] grantResults) 方法中處理使用者選擇後的回撥,包括允許和拒絕。
public boolean shouldShowRequestPermissionRationale (String permission)
它的意思是是否需要彈框像使用者解釋為什麼需要申請這個許可權。它的使用場景是,應用沒有該許可權,而且在使用者不能直觀的看出該許可權和當前功能的關係。比如,我們做一個相機應用,毫無疑問需要相機許可權,我們不需要再單獨向用戶解釋為什麼需要相機許可權,但是該應用還需要定位許可權來為相片新增標籤,對於對科技不是很瞭解的使用者來說,他們很難理解照相為什麼還需要定位許可權,這時候就應該想想怎麼向用戶解釋清楚了。
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
許可權申請的回撥。
3.使用相容庫來相容舊版
對於第2條提到的程式碼在android 6.0以上執行沒問題,但是23 api之前就不行了,因為沒有那些方法。v4相容庫已對這個做過相容,可以用以下方法代替:
ContextCompat.checkSelfPermission()
被授權函式返回PackageManager.PERMISSION_GRANTED,否則返回PackageManager.PERMISSION_DENIED ,在所有版本都是如此。
ActivityCompat.requestPermissions()
這個方法在M之前版本呼叫,OnRequestPermissionsResultCallback 直接被呼叫,帶著正確的 PERMISSION_GRANTED或者 PERMISSION_DENIED 。
ActivityCompat.shouldShowRequestPermissionRationale()
在M之前版本呼叫,永遠返回false。
4.許可權和許可權組
大家都知道,並不是所有的許可權都需要動態申請,只是一些系統規定的危險許可權,許可權被分組了:
同一組的任何一個許可權被授權了,其他許可權也自動被授權。例如,一旦WRITE_CONTENTS被授權了,APP也有READ_CONTACTS和GET_ACCOUNTS了。
但是:某些手機廠商比如小米,對於許可權組好像有自己的策略,在測試當中,我們發現,當給應用賦予了CALL_PHONE 許可權後,並沒有獲得READ_PHONE_STATE許可權,測試手機的系統是MIUI 8.6.7.14開發版
5.應用必須的許可權及使用時的許可權
對於應用的必須許可權,比如讀取手機狀態等,我們的策略是在啟動頁中進行申請。如果使用者允許,則繼續執行,如果拒絕,則退出應用。
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ...省略無關程式碼 PermissionUtil.getInstance().requestNecessaryPermissions(this, new OnPermissionListener() { @Override public void onDenied() { MyApplication.getInstance().exit(true); } @Override public void onGranted() { init(); } }); }
如果是某些具體功能才會用到的許可權,比如拍照功能,我們的策略是在需要用到的時候再進行申請(當然是在相機工具類中統一處理的,如果你們沒有封裝,那就只好每個入口都要判斷了)
/** * 啟動相機功能 * * @param activity */ public static void startCamera(final Activity activity) { PermissionUtil.getInstance().requestCamera(activity, new OnPermissionListener() { @Override public void onDenied() { } @Override public void onGranted() { start(activity); } });
}
6.在APP使用過程中,從設定中更改許可權
特別提醒:在APP使用過程中,從設定中更改許可權,應用程式會被重啟。或者說,會被系統Kill掉然後重新啟動一個新的,當然它會記住當前的任務棧。所以再切回到我們的APP的時候,還是會停留在你去設定之前的頁面,但是程序號已經變了#→_→#
你需要做的是:
在BaseActivity中檢查必須的許可權,如果是拒絕狀態,直接跳到啟動頁,重新申請。之後就是啟動頁的流程了。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ...省略無關程式碼 if (!PermissionUtil.getInstance().checkNecessaryPermissions(this)) { PermissionUtil.getInstance().clearFragmentManagerInsideFragments(this); finish(); mIsFinishing = true; return; } init(); } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); PermissionUtil.getInstance().onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}
清理fragment。
App的某些頁面,如果存在一個或者多個Fragment時,尤其是V4.Fragment,在修改許可權回到該頁面時,如果沒有許可權該頁面會finish掉自己,但是某些手機會出現Fragment 依舊存在,而且生命週期會正常執行,有兩個問題:1.getActivity為空; 2.API請求需要手機資訊許可權,如果後臺拒絕了這個許可權,則會崩掉。所以Activity onCreate時進行許可權判斷,如果沒有則將FragmentManager中的Fragment全部移除,禁止進行一系列的操作。
/** * 清理 FragmentManager 中的 Fragment。<br/> * 解決在系統設定中更改許可權後,App 被 kill 掉重啟時的 Fragment 狀態錯誤問題。 * * @param activity */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) public void clearFragmentManagerInsideFragments(Activity activity) { if (activity instanceof FragmentActivity) { FragmentManager manager = ((FragmentActivity) activity).getSupportFragmentManager(); int count = manager.getBackStackEntryCount(); List<Fragment> list = manager.getFragments(); int fragmentCount = list == null ? 0 : list.size(); if (list != null) { for (Fragment fragment : list) { manager.beginTransaction().remove(fragment).commit(); } } }
}