從 Android 6.0(API 級別 23)開始,用戶開始在應用運行時向其授予權限,而不是在應用安裝時授予。此方法能夠簡化應用安裝過程,由於用戶在安裝或更新應用時不須要授予權限。它還讓用戶能夠對應用的功能進行更多控制;例如,用戶能夠選擇爲相機應用提供相機訪問權限,而不提供設備位置的訪問權限。用戶能夠隨時進入應用的設置頁面修改權限。html
iPhone上的App都是默認下載安裝的,而後運行App時須要什麼權限就彈窗向用戶申請,這對用戶來講就很是好。由於用戶不想給App權限就不給,而Android 6.0之前是這樣的,我下載了一個App安裝,系統就彈出這個App須要使用的所有的權限,就給我看一下,我須要這個App 的話,只能贊成全部的權限都給這個App,要麼我不安裝這個App。前端
系統權限分爲兩類:java
表 1. 危險權限和權限組。android
權限組 | 權限 |
---|---|
CALENDAR | |
CAMERA | |
CONTACTS | |
LOCATION | |
MICROPHONE | |
PHONE | |
SENSORS | |
SMS | |
STORAGE |
從上圖中咱們能夠看到,Android系統把危險權限分了9大組,這樣也是爲了簡化權限的申請機制。若是你申請了android.permission.READ_CONTACTS讀取聯繫人的權限,那麼6.0 系統就會把這一組中其餘的權限也打包給你。我以爲這個和iOS的隱私管理機制很是類似,在iOS系統設置的「隱私->通信錄」中能夠看到,若是你給一個App通信錄的權限,那麼這個App既能夠讀也能夠寫的數組
Android 6.0裏面只有危險權限才須要運行時獲取的app
例如如下圖片中用戶在android6.0的版本的設置中把權限關閉,此時你的權限就用不了了。那麼程序須要考慮對6.0及以上版本的兼容,具體參考下面的(android.support.v4.content.PermissionChecker)。異步
值得注意的是Android系統有一套自動權限調整的機制,咱們知道android每次sdk升級有可能會加入新的權限,而你的app已經發布到用戶手機上安裝了,除了升級不可能修改Androidmanifest文件了,此時你可能擔憂本身的app可以在這些新的sdk版本的手機上運行正常嗎,其實android已經考慮了這種場景,Android 將根據爲 targetSdkVersion 屬性提供的值決定應用是否須要權限。若是該值低於在其中添加權限的版本,則 Android 會爲App自動添加該權限。ide
例如,API 級別 4 中加入了 WRITE_EXTERNAL_STORAGE 權限,用以限制訪問共享存儲空間。若是您的 targetSdkVersion 爲 3 或更低版本,則會向更新 Android 版本設備上的應用添加此權限。ui
申請權限過程包括如下幾個步驟:this
若是您的應用須要危險權限,則每次執行須要這一權限的操做時您都必須檢查本身是否具備該權限。用戶始終能夠自由調用此權限,所以,即便應用昨天使用了相機,它不能假設本身今天仍具備該權限,由於用戶可能在設置裏面關閉了。
要檢查您是否具備某項權限,請調用 ContextCompat.checkSelfPermission() 方法。例如,如下代碼段顯示瞭如何檢查 Activity 是否具備在日曆中進行寫入的權限:
1
2
3
|
// 假設thisActivity是當前屏幕最前端正在和用戶交互的activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.WRITE_CALENDAR);
|
若是應用具備此權限,方法將返回 PackageManager.PERMISSION_GRANTED,而且應用能夠繼續操做。若是應用不具備此權限,方法將返回 PERMISSION_DENIED,且應用必須明確向用戶要求權限。
也可使用ActivityCompat,它們兩個的checkSelfPermission方法是同一個,由於ActivityCompat繼承自ContextCompat,而checkSelfPermission是ContextCompat中的方法。
檢查權限會有一些特別的問題須要注意,主要有如下兩個:
android.support.v4.content.PermissionChecker能夠幫咱們解決這個問題。這個類的文檔是這麼描述的:
For apps targeting API lower than android.os.Build.VERSION_CODES.M these permissions are always granted as such apps do not expect permission revocations and would crash. Therefore, when the user disables a permission for a legacy app in the UI the platform disables the APIs guarded by this permission making them a no-op which is doing nothing or returning an empty result or default error.
翻譯一下是:
當app的targetsdkversion小於23的時候,這些權限默認都會自動給當前app,但若是app沒有考慮在6.0設備中被用戶主動撤銷該權限的場景,那麼可能形成app的崩潰。因而app在使用該權限過程當中系統權限檢查時若是這個權限被用戶撤銷了,那麼對應請求的API會什麼都不作或者返回一個空的結果,或者出錯。
PermissionChecker.checkSelfPermission方法就是用於檢查App自身有沒有某一個權限,這個方法的返回結果只有三種:
PERMISSION_DENIED和PERMISSION_DENIED_APP_OP都表示沒有被受權,可是它們的區別就在於targetSdkVersion的值,若是targetSdkVersion小於23,就返回PERMISSION_DENIED_APP_OP,不然就返回PERMISSION_DENIED
所以,若是你的App的targetSdkVersion小於23,可是運行在Android 6.0及之後的系統上,你能夠用下面的代碼來檢測app是否有某個權限:
1
|
PermissionChecker.checkSelfPermission(context, permission) == PermissionChecker. PERMISSION_DENIED_APP_OP
|
國產不少手機在google以前已經作了本身的權限管理,例如小米,因此即便運行在6.0的手機系統上面並使用ContextCompat的checkSelfPermission方法即使返回 PackageManager.PERMISSION_GRANTED 也可能不許確。若是出現這種狀況咱們須要作一次特殊處理,此時咱們須要用到android的隱藏API — AppOpsManager
AppOpsManager官方的解釋是系統內部使用,不提供給APP開發者使用。一個小米設備兼容判斷的代碼以下:
1
2
3
4
5
6
7
8
9
10
|
@TargetApi(Build.VERSION_CODES.M)
private static boolean hasSelfPermissionForXiaomi(Context context, String permission) {
AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
String op = AppOpsManager.permissionToOp(permission);
if (!TextUtils.isEmpty(op)) {
int checkOp = appOpsManager.checkOp(op, Process.myUid(), context.getPackageName());
return checkOp == AppOpsManager.MODE_ALLOWED && ActivityCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED;
}
return true;
}
|
如上面所說小米這樣的手機已經在android 6.0 以前已經有了本身的權限管理,若是你也想判斷小於6.0的小米手機是否賦予了某個權限也是能夠作到的,仍是用到AppOpsManager,可是因爲AppOpsManager在API 19才引入的,因此使用時須要指定 @TargetApi(19),示例代碼以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@TargetApi(19)
private static boolean hasSelfPermissionForXiaomi(Context context, String permission) {
Object opsManager = context.getSystemService(Context.APP_OPS_SERVICE);
Class<?> clazz = opsManager.getClass();
try {
Method dispatchMethod = clazz.getMethod("checkOp", new Class[] { int.class, int.class, String.class });
int checkOp = (Integer) dispatchMethod.invoke(opsManager, new Object[] {AppOpsChecker.OP_CAMERA, Binder.getCallingUid(), context.getApplicationContext().getPackageName() });
return checkOp == AppOpsManager.MODE_ALLOWED && ActivityCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED;
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return true;
}
|
這個 AppOpsChecker.OP_CAMERA 你就須要從源碼中拷貝出來了,在API 19裏面是引用不到的。
若是您的應用須要應用manifest文件中列出的危險權限,那麼,它必需要求用戶授予該權限。Android 爲您提供了多種權限請求方式。調用這些方法將顯示一個標準的 Android 對話框,不過,您不能對它們進行自定義。
若是上面的權限檢查步驟中結果是應用尚無所需的權限,則應用必須調用一個 requestPermissions() 方法,以請求適當的權限。應用將傳遞其所需的權限,以及您指定用於識別此權限請求的整型
提示用戶授予或拒絕權限的系統對話框以下:
如下代碼能夠檢查應用是否具有讀取用戶聯繫人的權限,並根據須要請求該權限:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// 這裏的 thisActivity 是當前屏幕最前端正在和用戶交互的activity
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// 是否須要給用戶一個解釋?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {
// 顯示給用戶須要這個權限的理由,這個須要是異步的(不能阻塞當前線程去等待用戶的響應!) ,在用戶看完這個解釋後,繼續嘗試請求這些權限
} else {
// 不須要解釋, 咱們能夠開始請求權限
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS 這個是app定義的整形常量,用於標識一個請求,回調方法中會得到這個請求對應的結果
}
}
|
注:當您的應用調用 requestPermissions() 時,系統將向用戶顯示一個標準對話框。您的應用
當應用請求權限時,系統將向用戶顯示一個對話框。當用戶響應時,系統將調用應用的 onRequestPermissionsResult() 方法,向其傳遞用戶響應。您的應用必須覆寫該方法,以瞭解是否已得到相應權限。回調會將您傳遞的相同請求代碼傳遞給 requestPermissions()。例如,若是應用請求 READ_CONTACTS 訪問權限,則它可能採用如下回調方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
// 若是受權取消 這個結果數組是空
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 權限已經受權, 很棒!能夠繼續聯繫人相關的操做了
} else {
// 權限被拒絕了,很糟糕! 禁用和該權限相關的功能
}
return;
}
// 其它的'case' 代碼去處理其它的權限請求回調
}
}
|
注意:
當系統要求用戶授予權限時,用戶能夠選擇指示系統再也不要求提供該權限(即勾選對話框裏的不在提示)。這種狀況下,不管應用在何時使用 requestPermissions() 再次要求該權限,系統都會當即拒絕此請求。系統會調用您的 onRequestPermissionsResult() 回調方法,並傳遞 PERMISSION_DENIED
在某些狀況下,您可能須要幫助用戶瞭解您的應用爲何須要某項權限。例如,若是用戶啓動一個攝影應用,用戶對應用要求使用相機的權限可能不會感到吃驚,但用戶可能沒法理解爲何此應用想要訪問用戶的位置或聯繫人。在請求權限以前,不妨爲用戶提供一個解釋。請記住,您不須要經過解釋來講服用戶;若是您提供太多解釋,用戶可能發現應用使人失望並將其移除。
您能夠採用的一個方法是僅在用戶已拒絕某項權限請求時提供解釋。若是用戶繼續嘗試使用須要某項權限的功能,但繼續拒絕權限請求,則可能代表用戶不理解應用爲何須要此權限才能提供相關功能。對於這種狀況,比較好的作法是顯示解釋。
爲了幫助查找用戶可能須要解釋的情形,Android 提供了一個實用程序方法,即 shouldShowRequestPermissionRationale()。若是應用以前請求過此權限但用戶拒絕了請求,此方法將返回 true。
注:若是用戶在過去拒絕了權限請求,並在權限請求系統對話框中選擇了 Don’t ask again 選項,此方法將返回 false。若是設備規範禁止應用具備該權限,此方法也會返回 false。