1.簡介
在Android6.0之前版本,權限請求較為簡單,僅在用戶安裝app時將自己需要使用的所有權限列出來告知用戶,若用戶授權,則app安裝后可隨時使用該權限。自6.0開始,一些涉及用戶隱私的敏感權限需在使用時動態申請,且用戶可選擇授權或拒絕。當然,權限的改進對用戶而言是好事,畢竟更能保護用戶隱私。但對于開發者而言,也多了一項動態權限申請的工作
2.權限分類
對開發者而言,權限則主要分為以下兩類:
1)普通權限(normal permissions): 只需在manifest中注冊
2)危險權限(dangerous permissions):仍需在manifest中注冊,但具體授權分以下幾種情況
targetSdk<23 | targetSdk>=23 | |
---|---|---|
手機系統<23 | 安裝時默認獲得權限且用戶無法在安裝后取消權限 | 安裝時默認獲得權限且用戶無法在安裝后取消權限 |
手機系統>=23 | 安裝時默認獲得權限,但用戶可在安裝后取消授權( 取消時手機會提示用戶該APP是為舊版手機打造,讓用戶謹慎操作 ) | 安裝時不會獲得權限而需在運行時向用戶動態申請。用戶授權后仍可在設置界面中取消,取消授權后在app運行過程中可能會出現crash |
由上表可知當APP的targetSdk>=23且運行在Android>=6.0(API23)的手機上時必須使用動態申請權限
具體危險權限如下:
3.運行時權限申請(使用系統提供的API)
1)權限檢查
對于權限檢查,Android提供了以下3種方式
1.ContextCompat#checkSelfPermission
2.Context#checkSelfPermission
3.PermissionChecker#checkSelfPermission
需注意的是若應用targetSdk<23,則第1、2種方式返回的永遠是PERMISSION_GRANTED,即永遠返回已授權。根據本文上面內容知當targetSdk<23時,應用雖在安裝時就獲得授權,但若運行在>=Android6.0的手機上時,用戶可在安裝后取消授權,此時就不能使用第1、2種方式來檢查權限,而需使用第3種
public static boolean checkSelfPermission(String permission, Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//getTargetVersion是判斷app的targetSdk的方法
if (getTargetVersion(context) >= Build.VERSION_CODES.M) {
//應用的targetSdk>=23則使用Context#checkSelfPermission(permission)
return context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
} else {
//若targetSdk<23則使用PermissionChecker#checkSelfPermission
return PermissionChecker.checkSelfPermission(context, permission) == PermissionChecker.PERMISSION_GRANTED;
}
} else {//手機版本低于6.0的,安裝后即授權且用戶無法取消
return true;
}
}
2)請求權限
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
requestCode);
注意:1.Fragment中請求需使用自己的requestPermissions方法
3)處理請求結果
請求權限后系統會回調申請權限的Activity的onRequestPermissionsResult(),若使用的是Fragment的requestPermissions方法,則回調對應Fragment的onRequestPermissionsResult()
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode != this.requestCode || grantResults.length = 0) {
return;
}
for (int i = 0; i < grantResults.length; i++) {
String permission = permissions[i];
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, permission + "已被授權", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, permission + "已被拒絕", Toast.LENGTH_SHORT).show();
if (shouldShowRequestPermissionRationale(permission)) {
//請求被用戶拒絕但用戶未勾選不再詢問框,可繼續請求權限
} else {
//請求被用戶拒絕且用戶勾選了不再詢問框,需要用戶前往設置中授權
}
}
}
}
4)總結
用系統提供的api進行申請步驟較為繁瑣,且請求和處理代碼不在相同位置,代碼量多了的話可讀性變差。那有沒有什么好的方法既能簡化流程提高可讀性又能避免以上第二種情況呢?答案是肯定的,著名基佬交流網站github上就有豐富的權限請求庫供各位客官享用!
4.使用EasyPermissions進行權限申請
1)特點
- 鏈式操作
- 請求前會自動檢查是否已被授予 (這樣在請求前就不必再進行權限檢查了)
- 若請求的權限未在manifest中注冊,將拋出明確的異常 (請求未在manifest注冊的權限將導致不彈出dialog而直接返回false,有時我們可能對此十分懵逼,因為這既不報錯也不彈出dialog代碼也OK就是請求失敗,可能要很久才反應過來忘了在manifest中注冊)
- 自動重試(可配置項),配置該選項后若請求被拒但用戶未勾選不再提示框時會自動重試直到用戶授權或勾選不再提示框
2)依賴
A.在項目根build.gradle中
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
B.添加依賴
dependencies {
implementation 'com.github.Ficat:EasyPermissions:v2.1.0'
}
3)使用
//requestEach方式
EasyPermissions
.with(activity)
.requestEach(Manifest.permission.CAMERA)
.result(new RequestEachExecutor.ResultReceiver() {
@Override
public void onPermissionsRequestResult(Permission permission) {
String name = permission.name;
if (permission.granted) {
//name權限被授予
} else {
if (permission.shouldShowRequestPermissionRationale) {
//name權限被拒絕但用戶未勾選不再提示框,可繼續請求
} else {
//name權限被拒絕且用戶勾選了不再提示框
//此時不能再次請求了,而需要user前往設置界面手動授權
EasyPermissions.goToSettingsActivity(activity);
}
}
}
});
//request方式,請求的所有權限被用戶授權后返回true,否則返回false
EasyPermissions
.with(activity)
.request(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.WRITE_EXTERNAL_STORAGE)
.autoRetryWhenUserRefuse(true, new BaseRequestExecutor.RequestAgainListener() {//是否自動重試
@Override
public void requestAgain(String[] needAndCanRequestAgainPermissions) {
//該監聽回調中傳入的是再次請求的權限,用以在重新請求時彈出說明框等信息(如
//向用戶說明為何要使用該權限)
for (String s : needAndCanRequestAgainPermissions) {
Log.e("TAG", "request again permission = "+s);
}
}
})
.result(new RequestExecutor.ResultReceiver() {
@Override
public void onPermissionsRequestResult(boolean grantAll, List<Permission> results) {
if (grantAll) {
Toast.makeText(MainActivity.this, "request permissions success!", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, "request permissions fail!", Toast.LENGTH_SHORT).show();
}
}
});