Android動態許可權總結
從Android6.0開始,Android系統提供動態申請許可權的機制, APP在使用危險許可權時,需要使用者的授權才可進一步操作。
許可權申請方式
Android系統中許可權申請的方式有兩種,如下圖所示:
靜態申請
Android6.0以前的系統(API < 23)採用的這種方式,只要使用者在AndroidManifest.xml中註冊了許可權,安裝APP後預設就獲取了這些許可權。這種授權方式安全性極低,如果使用者安裝後沒有關閉相應的許可權,使用者的私密資料很容易被哪些垃圾APP竊取。為了解決這種問題,國內的各大手機廠商為Android5.0以下的系統,針對某些許可權做了一定的限制,即便在Android5.0以下,也需要使用者進行手動授權才可使用,這在某種程度上提高了安全性,但也因沒有統一的標準,從而出現了各種相容問題。
動態申請
隨著系統的升級,Google也意識到靜態申請許可權的弊端,所以在Android6.0中,對許可權進行了重新梳理,將許可權分為普通許可權和危險許可權:
- 正常許可權:不會給使用者隱私帶來危險的許可權,只要開發者在AndroidManifest.xml中註冊了,系統將自動授權;
- 危險許可權:可以訪問使用者隱私資料的許可權,必須獲取使用者的同意才可獲得授權;
危險許可權彙總
在Android開發中我們應該儘可能使用隔離式申請許可權,使用者沒有授權時遮蔽相應功能即可。現在很多內容性APP,使用者進去什麼都沒看到,就需要各種許可權,沒有許可權還不能使用,這可能會引起使用者的反感,當然,也是對使用者安全意識的一種衝擊,時間久了,使用者可能會覺得使用APP就應該授權,從而給一些惡意APP作惡的餘地。
動態申請許可權的過程
核心API介紹
檢查許可權是否已獲取:
// ContextCompat.java
public static int checkSelfPermission(@NonNull Context context, @NonNull String permission)
// PermissionChecker.java
public static int checkSelfPermission(@NonNull Context context,
@NonNull String permission)
複製程式碼
這兩個方法都是檢查許可權是否獲取的方法,但ContextCompat.checkSelfPermission在某些系統上(如基於Android8.0的MIUI10檢查簡訊許可權時)有bug, 不能準確判斷許可權是否已獲取,此時可結合PermissionChecker.checkSelfPermission進行判斷, 所以判斷許可權是否已獲取可採用以下實現:
public static boolean hasPermission(@NonNull Context context, @NonNull String permission) {
if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED
|| PermissionChecker.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
return true;
}
複製程式碼
申請許可權
當未獲取許可權時,需要向系統請求,請求時使用requestPermissions方法:
// ActivityCompat.java
// 在Activity中申請許可權
public static void requestPermissions(final @NonNull Activity activity,
final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode)
// Fragment.java
// 在Fragment中申請許可權
public final void requestPermissions(@NonNull String[] permissions, int requestCode)
複製程式碼
在Fragment使用ActivityCompat.requestPermissions申請許可權時,如果使用者拒絕了(且勾線了不再提示)請求,Fragment中的onRequestPermissionsResult不會被回撥,也就不能引導使用者開啟許可權。所以在Fragment中應該使用Fragment的成員方法requestPermissions來請求許可權。
檢查APP是否應該向用於展示申請許可權的解釋
// ActivtyCompat.java
// 檢查APP是否應該向用於展示申請許可權的解釋
public static boolean shouldShowRequestPermissionRationale(@NonNull Activity activity, @NonNull String permission);
複製程式碼
此方法的返回值解釋如下:
- 當APP從未申請過指定的許可權或申請了指定許可權,但被使用者拒絕,且勾選了【不再提示】,返回false;
- APP申請指定許可權時被使用者拒絕,但未勾選【不再提示】,返回true
所以在使用此方法時,我們要先判斷APP是否已申請過許可權,否則難以判斷返回false的兩種情況。
申請許可權的結果回撥
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
複製程式碼
這是Activity和Fragment中申請許可權的結果回撥方法,其中permissions表示申請的許可權陣列,grantResults表示每個許可權的請求結果。取值為:
// 獲得授權
public static final int PERMISSION_GRANTED = 0;
// 未獲授權
public static final int PERMISSION_DENIED = -1;
複製程式碼
通常申請許可權後的處理邏輯都是在該方法中實現。
動態許可權申請的實現
動態申請許可權的條件:
- targetSdkVersion >= 23;
- Android系統版本在6.0及以上;
對於不能同時滿足以上條件的情況,預設使用的靜態申請許可權的方式,但不同的ROM為了安全性,可能對其機制進行了修改,所以可能因ROM不同而有所差異。
瞭解了申請許可權的核心API,接下來就介紹一下在Activity中申請許可權的實現過程,下面以點選申請拍照許可權為例:
private void startPhoto() {
if (hasPermission(this, new String[]{Manifest.permission.CAMERA})) {
// 執行拍照的邏輯
} else {
ActivityCompat.requestPermissions(context, rnew String[]{Manifest.permission.ACCESS_FINE_LOCATION}),
PERMISSION_REQUEST_CODE);
}
}
複製程式碼
然後在onRequestPermissionsResult中監聽許可權申請的結果:
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (hasPermission(permissions)) {
// 執行拍照的邏輯
} else {
if (ActivityCompat.shouldShowRequestPermissionRationale(context, permissions[refusedPermissionIndex])) {
// 向用戶展示申請許可權的理由
} else {
// 引用使用者去開啟許可權
}
}
}
複製程式碼
上面只是一個申請許可權的基本流程,真正實現時還要考慮多許可權問題,版本的相容問題,ROM的相容問題等。
當然,在開發中也不會這樣在每個需要申請許可權的Activity/Fragment中寫這一段程式碼,即便封裝成工具類也需要在每個Activity/Fragment引用,耦合性太高。通常可將許可權申請在一個透明的Activity中實現,這樣在申請許可權時,直接跳轉到該Activity即可。下面推薦一個動態申請許可權的庫供參考。
PermissionManager
PermissionManager是一個基於AOP實現的動態申請許可權的開源庫,目的是讓申請許可權的過程更簡單。當然,也可當做學習Aspectj的的參考專案。具有以下優點:
- 支援多許可權申請;
- 簡單易用,一行註解實現許可權申請
- 提供了可擴充套件的許可權說明和引導設定方式;
- 提供了阻塞/非阻塞的申請方式;
具體的介紹可參考其原始碼地址:PermissionManager
總結
由於Android碎片化嚴重,國內的各大廠商對許可權這塊也做了不同的處理,所以在動態申請時肯定存在失敗的情況,如果在使用PermissionManager的時候發現問題,歡迎隨時提issue & pull request。