Android适配系列:
- Android 6.0 的動态權限管理
- Android 7.0脫坑指南
- Android 8.0适配指北
- Android 9.0 适配指南
1.前言
大家都知道Android 6.0的新特性之一就是應用權限的管理。也就是說凡是涉及使用者隐私的權限,使用者可以自己去設定管理了。然而在6.0以前,我們安裝一款APP是預設同意此APP所需的所有權限(比如定位、通路通訊錄),不同意就不能安裝。當然,國内的一些手機廠商基于Android定制的系統中,可以實作在6.0以前關閉指定的權限。如下圖:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICO2AzNzYDNxEDNycDM2EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
2.危險權限清單(Dangerous Permission)
Dangerous Permission一般都是涉及使用者隐私的權限。
從上面的圖檔中可以看到,攝像頭、電話、定位等等都是我們平常開發中常用的權限。
3.可以在6.0不适配權限管理嗎?
答案是可以,但是不推薦。
首先說怎麼不适配,那就是設定targetSdkVersion小于23(Android 6.0系統預設為targetSdkVersion小于23的應用預設授予了所申請的所有權限,是以如果您APP設定的targetSdkVersion低于23,在運作時也不會崩潰。)
有人一看這不是挺好的嘛,解決問題。那麼我想告訴你,首先這不是長久之計,早晚都要面對的。你不可能永遠targetSdkVersion低于23。其次,它是有一個前提,那就是使用者自己不去操作權限。要知道如果使用者是6.0以上的手機或是國内部分6.0以前的手機,他可以自己在設定中關閉權限,那麼到時APP因為沒有權限擷取資料異常,導緻空指針的異常時,APP就會崩潰。
4.怎麼适配
首先Android Studio:
在build.gradle中聲明targetSdkVersion為23及以上。
Eclipse:
在AndroidManifest.xml中聲明targetSdkVersion為23及以上。
這裡引用高德定位Demo的CheckPermissionsActivity類,代碼如下:
/**
* 繼承了Activity,實作Android6.0的運作時權限檢測
* 需要進行運作時權限檢測的Activity可以繼承這個類
*
* @建立時間:2016年5月27日 下午3:01:31
* @項目名稱: AMapLocationDemo
* @author hongming.wang
* @檔案名稱:PermissionsChecker.java
* @類型名稱:PermissionsChecker
* @since 2.5.0
*/
public class CheckPermissionsActivity extends Activity {
/**
* 需要進行檢測的權限數組
*/
protected String[] needPermissions = {
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.READ_PHONE_STATE
};
private static final int PERMISSON_REQUESTCODE = 0;
/**
* 判斷是否需要檢測,防止不停的彈框
*/
private boolean isNeedCheck = true;
@Override
protected void onResume() {
super.onResume();
if (Build.VERSION.SDK_INT >= 23
&& getApplicationInfo().targetSdkVersion >= 23) {
if (isNeedCheck) {
checkPermissions(needPermissions);
}
}
}
/**
*
* @param permissions
* @since 2.5.0
* requestPermissions方法是請求某一權限,
*/
private void checkPermissions(String... permissions) {
try {
if (Build.VERSION.SDK_INT >= 23
&& getApplicationInfo().targetSdkVersion >= 23) {
List<String> needRequestPermissonList = findDeniedPermissions(permissions);
if (null != needRequestPermissonList
&& needRequestPermissonList.size() > 0) {
String[] array = needRequestPermissonList.toArray(new String[needRequestPermissonList.size()]);
Method method = getClass().getMethod("requestPermissions", new Class[]{String[].class,
int.class});
method.invoke(this, array, PERMISSON_REQUESTCODE);
}
}
} catch (Throwable e) {
}
}
/**
* 擷取權限集中需要申請權限的清單
*
* @param permissions
* @return
* @since 2.5.0
* checkSelfPermission方法是在用來判斷是否app已經擷取到某一個權限
* shouldShowRequestPermissionRationale方法用來判斷是否
* 顯示申請權限對話框,如果同意了或者不在詢問則傳回false
*/
private List<String> findDeniedPermissions(String[] permissions) {
List<String> needRequestPermissonList = new ArrayList<String>();
if (Build.VERSION.SDK_INT >= 23
&& getApplicationInfo().targetSdkVersion >= 23){
try {
for (String perm : permissions) {
Method checkSelfMethod = getClass().getMethod("checkSelfPermission", String.class);
Method shouldShowRequestPermissionRationaleMethod = getClass().getMethod("shouldShowRequestPermissionRationale",
String.class);
if ((Integer)checkSelfMethod.invoke(this, perm) != PackageManager.PERMISSION_GRANTED
|| (Boolean)shouldShowRequestPermissionRationaleMethod.invoke(this, perm)) {
needRequestPermissonList.add(perm);
}
}
} catch (Throwable e) {
}
}
return needRequestPermissonList;
}
/**
* 檢測是否所有的權限都已經授權
* @param grantResults
* @return
* @since 2.5.0
*
*/
private boolean verifyPermissions(int[] grantResults) {
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
/**
* 申請權限結果的回調方法
*/
@TargetApi(23)
public void onRequestPermissionsResult(int requestCode,
String[] permissions, int[] paramArrayOfInt) {
if (requestCode == PERMISSON_REQUESTCODE) {
if (!verifyPermissions(paramArrayOfInt)) {
showMissingPermissionDialog();
isNeedCheck = false;
}
}
}
/**
* 顯示提示資訊
*
* @since 2.5.0
*
*/
private void showMissingPermissionDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.notifyTitle);
builder.setMessage(R.string.notifyMsg);
// 拒絕, 退出應用
builder.setNegativeButton(R.string.cancel,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
});
builder.setPositiveButton(R.string.setting,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
startAppSettings();
}
});
builder.setCancelable(false);
builder.show();
}
/**
* 啟動應用的設定
*
* @since 2.5.0
*
*/
private void startAppSettings() {
Intent intent = new Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivity(intent);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if(keyCode == KeyEvent.KEYCODE_BACK){
this.finish();
return true;
}
return super.onKeyDown(keyCode, event);
}
}
我在上面的類中,自己加入了一些注釋,大家仔細看就可以明白了。
補充:小米手機在動态權限這裡還需要一些相容,我們需要注意一下。當然對于國内部分6.0以前手機,隻能在需要權限去去捕獲異常來處理了。
當然不止上面一種實作方法,github上有許多大神開源的封裝庫,可以很友善的實作權限适配。我推薦兩個庫,大家根據需求選擇:
1. PermissionsDispatcher
2. 鴻洋大神的MPermissions
5.參考
1. Android M 新的運作時權限開發者需要知道的一切
2. 高德地圖定位API