從Android6.0開始,Android系統提供動態申請權限的機制, APP在使用危險權限時,須要用戶的受權纔可進一步操做。java
Android系統中權限申請的方式有兩種,以下圖所示: git
Android6.0之前的系統(API < 23)採用的這種方式,只要用戶在AndroidManifest.xml中註冊了權限,安裝APP後默認就獲取了這些權限。這種受權方式安全性極低,若是用戶安裝後沒有關閉相應的權限,用戶的私密數據很容易被哪些垃圾APP竊取。爲了解決這種問題,國內的各大手機廠商爲Android5.0如下的系統,針對某些權限作了必定的限制,即使在Android5.0如下,也須要用戶進行手動受權纔可以使用,這在某種程度上提升了安全性,但也因沒有統一的標準,從而出現了各類兼容問題。github
隨着系統的升級,Google也意識到靜態申請權限的弊端,因此在Android6.0中,對權限進行了從新梳理,將權限分爲普通權限和危險權限:數組
在Android開發中咱們應該儘量使用隔離式申請權限,用戶沒有受權時屏蔽相應功能便可。如今不少內容性APP,用戶進去什麼都沒看到,就須要各類權限,沒有權限還不能使用,這可能會引發用戶的反感,固然,也是對用戶安全意識的一種衝擊,時間久了,用戶可能會以爲使用APP就應該受權,從而給一些惡意APP做惡的餘地。安全
// 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進行判斷, 因此判斷權限是否已獲取可採用如下實現:ide
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來請求權限。學習
// ActivtyCompat.java
// 檢查APP是否應該向用於展現申請權限的解釋
public static boolean shouldShowRequestPermissionRationale(@NonNull Activity activity, @NonNull String permission);
複製代碼
此方法的返回值解釋以下:this
因此在使用此方法時,咱們要先判斷APP是否已申請過權限,不然難以判斷返回false的兩種狀況。spa
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;
複製代碼
一般申請權限後的處理邏輯都是在該方法中實現。
對於不能同時知足以上條件的狀況,默認使用的靜態申請權限的方式,但不一樣的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是一個基於AOP實現的動態申請權限的開源庫,目的是讓申請權限的過程更簡單。固然,也可當作學習Aspectj的的參考項目。具備如下優勢:
具體的介紹可參考其源碼地址:PermissionManager
因爲Android碎片化嚴重,國內的各大廠商對權限這塊也作了不一樣的處理,因此在動態申請時確定存在失敗的狀況,若是在使用PermissionManager的時候發現問題,歡迎隨時提issue & pull request。