引言:運行時權限是版本升級的一個更新點,學起來挺容易的,寫一篇筆記感覺真費勁。
時間:2017年04月18日23:45:50
作者:JustDo23
01. 前言
運行時權限是Android 6.0 變更中尤為突出的一點。6以下的手機在安裝時候提示權限列表,用戶全部同意后才能安裝程序;6以后的手機直接安裝,在運行過程中動態的向用戶申請所需權限,另外用戶可以在設置界面對程序的各個權限進行管理。
Android 中的權限可以分為三類:
- 普通權限(Normal Permissions)
- 危險權限(Dangerous Permissions)
- 特殊權限(Special Permissions)
普通權限不涉及用戶隱私,在AndroidManifest.xml
中聲明即獲取;危險權限涉及用戶的隱私,在用戶授權之后方能使用。另外,Google 對危險權限進行了分組,當某一個組中的某一個權限被授權之后應用同時就獲取到了整個組的所有權限。而且,調用 API 申請權限時系統將向用戶顯示一個標準對話框,該對話框上的提示權限說明是針對整個組的說明,應用無法配置或更改此對話框。
02. 聲明權限
在官方文檔中提到:為了保護系統的完整性和用戶隱私權,Android 在訪問受限的沙盒中運行每款應用。如果應用需要使用其沙盒以外的資源或信息,則必須明確請求權限。根據應用請求的權限類型,系統可能會自動授予權限,也可能會要求用戶授予權限。可以在應用清單中列出相應的權限,聲明應用需要此權限。
聲明權限,將<uses-permission>
元素置于頂級<manifest>
元素的子項。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.snazzyapp">
<uses-permission android:name="android.permission.SEND_SMS"/>
<application ...>
...
</application>
</manifest>
03. 檢查權限
/**
* Determine whether you have been granted a particular permission.
*
* @param permission The name of the permission being checked.
*/
ContextCompat.checkSelfPermission(@NonNull Context context, @NonNull String permission)
以上為核心 API 檢查是否擁有權限,簡單封裝如下:
/**
* 檢查是否具有某權限
*
* @return true, 有權限 false,無權限
*/
private boolean checkPermission(Context context, String permission) {
boolean checkPermission = false;
int permissionCheck = ContextCompat.checkSelfPermission(context, permission);// 檢查權限
switch (permissionCheck) {
case PackageManager.PERMISSION_GRANTED:// 有權限
checkPermission = true;
break;
case PackageManager.PERMISSION_DENIED:// 無權限
checkPermission = false;
break;
}
return checkPermission;
}
- 返回的結果是
int
類型 - 常量
PackageManager.PERMISSION_GRANTED
表示有權限 - 常量
PackageManager.PERMISSION_DENIED
表示無權限
// 檢查日歷權限
ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.WRITE_CALENDAR);
04. 請求權限
/**
* 請求指定的權限集合
*
* @param activity The target activity.
* @param permissions The requested permissions. Must me non-null and not empty.
* @param requestCode Application specific request code.
*/
ActivityCompat.requestPermissions(
final @NonNull Activity activity,
final @NonNull String[] permissions,
final @IntRange(from = 0) int requestCode);
- 此方法為異步方法
- 第二參數指定一個權限的字符數組
// 請求一個相機權限
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 23);
// 同時請求三個權限
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA, Manifest.permission.READ_CALENDAR, Manifest.permission.READ_SMS}, 32);
05. 請求響應
/**
* Callback for the result from requesting permissions.
*
* @param requestCode The request code.
* @param permissions The requested permissions. Never null.
* @param grantResults The grant results.
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
switch (requestCode) {
case 23:
if (grantResults.length > 0) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
ToastUtil.showShortToast(MainActivity.this, "授權");
} else {
ToastUtil.showShortToast(MainActivity.this, "拒絕");
}
}
break;
case 32:
break;
}
}
- 第二個參數是請求的權限數組
- 第三個參數是與請求數組對應的授權結果數組
06. 提供解釋
/**
* Gets whether you should show UI with rationale for requesting a permission.
*
* @param activity The target activity.
* @param permission A permission your app wants to request.
* @return Whether you can show permission rationale UI.
*/
public static boolean shouldShowRequestPermissionRationale(@NonNull Activity activity,
@NonNull String permission) {
if (Build.VERSION.SDK_INT >= 23) {
return ActivityCompatApi23.shouldShowRequestPermissionRationale(activity, permission);
}
return false;// 低版本直接返回 false
}
- 如果應用之前請求過此權限但用戶拒絕了請求,此方法將返回
true
。 - 如果用戶在過去拒絕了權限請求,并在權限請求系統對話框中選擇了 Don't ask again 選項,此方法將返回
false
。 - 如果設備規范禁止應用具有該權限,此方法也會返回
false
。
這部分的內容Android M 權限最佳實踐這篇文章寫得很好。
07. 官方例子
if (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {
// Show an expanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
}
}
08. 兼容問題
首先明確一下在什么情況下需要使用運行時權限動態申請:
- 危險權限
- Android 版本 >= 6.0
- targetSdkVersion >= 23
在這三個條件同時具備的情況下必須使用運行時權限機制。當targetSdkVersion < 23
的時候不用
調用運行時權限機制,當targetSdkVersion >= 23
的時候必須
調用運行時權限機制以確保程序不會出現崩潰。另外,運行時權限機制的相關 API 都是在support.v4
包下,說明做了低版本兼容,相關 API 運行在低版本時候均有默認的返回值。在此基礎上兼容適配問題可以考慮從兩個因素Android 版本
和targetSdkVersion
入手,用一張表來簡單分析兼容適配問題:
targetSdkVersion | Android 版本 | 兼容適配 |
---|---|---|
targetSdkVersion < 23 | SDK < 23 | 安裝時授權,正常使用 |
targetSdkVersion < 23 | SDK >= 23 | 直接安裝并授權,用戶管理權限,可能崩潰 |
targetSdkVersion >= 23 | SDK < 23 | 安裝時授權,運行時權限 API 有默認返回值,正常使用 |
targetSdkVersion >= 23 | SDK >= 23 | 直接安裝,運行時授權,用戶管理權限,正常使用 |
從表格中看出如果應用targetSdkVersion < 23
運行在Android 6.0
的手機上,由于用戶可以自主管理權限,取消某些授權,便會引起程序崩潰。因此,盡快提升目標版本同時添加運行時權限判斷申請 API 是很有必要的。
注意:對于以 Android 6.0 或更高版本為目標平臺的應用,請務必在運行時檢查和請求權限。即使不以 Android 6.0 為目標平臺,也應該在新權限模式下測試應用。
09. 其他小點
- 如果您的應用需要危險權限,則每次執行需要這一權限的操作時您都必須檢查自己是否具有該權限。用戶始終可以自由調用此權限,因此,即使應用昨天使用了相機,它不能假設自己今天仍具有該權限。
- 用戶只需要為每個權限組授予一次權限。當請求已經被授予的權限時,系統會調用您的
onRequestPermissionsResult()
回調方法,并傳遞PERMISSION_GRANTED
。 - 當系統要求用戶授予權限時,用戶可以選擇指示系統不再要求提供該權限。這種情況下,無論應用在什么時候使用
requestPermissions()
再次要求該權限,系統都會立即拒絕此請求。系統會調用您的onRequestPermissionsResult()
回調方法,并傳遞PERMISSION_DENIED
。
10. 奇思怪想
- 同時申請多條相同權限時,只有一個權限提示。
- 同時申請同組的多條權限時,只有一個權限提示。
- 在
onRequestPermissionsResult
方法中的權限字符數組與requestPermissions
方法中的相同。 - 先檢查權限再申請權限。原因是第一次申請權限勾選允許,再次申請權限勾選拒絕,程序崩潰。同時申請多個權限,有的拒絕有的允許,再次申請時應該先檢查并剔除已經允許的權限。注意:崩潰出現在6.0的手機上,在7.0上回自動返回授權或者自動剔除。
11. 推薦文章
有幾篇非常不錯的文章值得閱讀學習: