1.簡介
在Android6.0之前版本,權(quán)限請(qǐng)求較為簡單,僅在用戶安裝app時(shí)將自己需要使用的所有權(quán)限列出來告知用戶,若用戶授權(quán),則app安裝后可隨時(shí)使用該權(quán)限。自6.0開始,一些涉及用戶隱私的敏感權(quán)限需在使用時(shí)動(dòng)態(tài)申請(qǐng),且用戶可選擇授權(quán)或拒絕。當(dāng)然,權(quán)限的改進(jìn)對(duì)用戶而言是好事,畢竟更能保護(hù)用戶隱私。但對(duì)于開發(fā)者而言,也多了一項(xiàng)動(dòng)態(tài)權(quán)限申請(qǐng)的工作
2.權(quán)限分類
對(duì)開發(fā)者而言,權(quán)限則主要分為以下兩類:
1)普通權(quán)限(normal permissions): 只需在manifest中注冊(cè)
2)危險(xiǎn)權(quán)限(dangerous permissions):仍需在manifest中注冊(cè),但具體授權(quán)分以下幾種情況
| targetSdk<23 | targetSdk>=23 | |
|---|---|---|
| 手機(jī)系統(tǒng)<23 | 安裝時(shí)默認(rèn)獲得權(quán)限且用戶無法在安裝后取消權(quán)限 | 安裝時(shí)默認(rèn)獲得權(quán)限且用戶無法在安裝后取消權(quán)限 |
| 手機(jī)系統(tǒng)>=23 | 安裝時(shí)默認(rèn)獲得權(quán)限,但用戶可在安裝后取消授權(quán)( 取消時(shí)手機(jī)會(huì)提示用戶該APP是為舊版手機(jī)打造,讓用戶謹(jǐn)慎操作 ) | 安裝時(shí)不會(huì)獲得權(quán)限而需在運(yùn)行時(shí)向用戶動(dòng)態(tài)申請(qǐng)。用戶授權(quán)后仍可在設(shè)置界面中取消,取消授權(quán)后在app運(yùn)行過程中可能會(huì)出現(xiàn)crash |
由上表可知當(dāng)APP的targetSdk>=23且運(yùn)行在Android>=6.0(API23)的手機(jī)上時(shí)必須使用動(dòng)態(tài)申請(qǐng)權(quán)限
具體危險(xiǎn)權(quán)限如下:

3.運(yùn)行時(shí)權(quán)限申請(qǐng)(使用系統(tǒng)提供的API)
1)權(quán)限檢查
對(duì)于權(quán)限檢查,Android提供了以下3種方式
1.ContextCompat#checkSelfPermission
2.Context#checkSelfPermission
3.PermissionChecker#checkSelfPermission
需注意的是若應(yīng)用targetSdk<23,則第1、2種方式返回的永遠(yuǎn)是PERMISSION_GRANTED,即永遠(yuǎn)返回已授權(quán)。根據(jù)本文上面內(nèi)容知當(dāng)targetSdk<23時(shí),應(yīng)用雖在安裝時(shí)就獲得授權(quán),但若運(yùn)行在>=Android6.0的手機(jī)上時(shí),用戶可在安裝后取消授權(quán),此時(shí)就不能使用第1、2種方式來檢查權(quán)限,而需使用第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) {
//應(yīng)用的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 {//手機(jī)版本低于6.0的,安裝后即授權(quán)且用戶無法取消
return true;
}
}
2)請(qǐng)求權(quán)限
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
requestCode);
注意:1.Fragment中請(qǐng)求需使用自己的requestPermissions方法
3)處理請(qǐng)求結(jié)果
請(qǐng)求權(quán)限后系統(tǒng)會(huì)回調(diào)申請(qǐng)權(quán)限的Activity的onRequestPermissionsResult(),若使用的是Fragment的requestPermissions方法,則回調(diào)對(duì)應(yīng)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 + "已被授權(quán)", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, permission + "已被拒絕", Toast.LENGTH_SHORT).show();
if (shouldShowRequestPermissionRationale(permission)) {
//請(qǐng)求被用戶拒絕但用戶未勾選不再詢問框,可繼續(xù)請(qǐng)求權(quán)限
} else {
//請(qǐng)求被用戶拒絕且用戶勾選了不再詢問框,需要用戶前往設(shè)置中授權(quán)
}
}
}
}
4)總結(jié)
用系統(tǒng)提供的api進(jìn)行申請(qǐng)步驟較為繁瑣,且請(qǐng)求和處理代碼不在相同位置,代碼量多了的話可讀性變差。那有沒有什么好的方法既能簡化流程提高可讀性又能避免以上第二種情況呢?答案是肯定的,著名基佬交流網(wǎng)站github上就有豐富的權(quán)限請(qǐng)求庫供各位客官享用!
4.使用EasyPermissions進(jìn)行權(quán)限申請(qǐng)
1)特點(diǎn)
- 鏈?zhǔn)讲僮?/strong>
- 請(qǐng)求前會(huì)自動(dòng)檢查是否已被授予 (這樣在請(qǐng)求前就不必再進(jìn)行權(quán)限檢查了)
- 若請(qǐng)求的權(quán)限未在manifest中注冊(cè),將拋出明確的異常 (請(qǐng)求未在manifest注冊(cè)的權(quán)限將導(dǎo)致不彈出dialog而直接返回false,有時(shí)我們可能對(duì)此十分懵逼,因?yàn)檫@既不報(bào)錯(cuò)也不彈出dialog代碼也OK就是請(qǐng)求失敗,可能要很久才反應(yīng)過來忘了在manifest中注冊(cè))
- 自動(dòng)重試(可配置項(xiàng)),配置該選項(xiàng)后若請(qǐng)求被拒但用戶未勾選不再提示框時(shí)會(huì)自動(dòng)重試直到用戶授權(quán)或勾選不再提示框
2)依賴
A.在項(xiàng)目根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權(quán)限被授予
} else {
if (permission.shouldShowRequestPermissionRationale) {
//name權(quán)限被拒絕但用戶未勾選不再提示框,可繼續(xù)請(qǐng)求
} else {
//name權(quán)限被拒絕且用戶勾選了不再提示框
//此時(shí)不能再次請(qǐng)求了,而需要user前往設(shè)置界面手動(dòng)授權(quán)
EasyPermissions.goToSettingsActivity(activity);
}
}
}
});
//request方式,請(qǐng)求的所有權(quán)限被用戶授權(quán)后返回true,否則返回false
EasyPermissions
.with(activity)
.request(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.WRITE_EXTERNAL_STORAGE)
.autoRetryWhenUserRefuse(true, new BaseRequestExecutor.RequestAgainListener() {//是否自動(dòng)重試
@Override
public void requestAgain(String[] needAndCanRequestAgainPermissions) {
//該監(jiān)聽回調(diào)中傳入的是再次請(qǐng)求的權(quán)限,用以在重新請(qǐng)求時(shí)彈出說明框等信息(如
//向用戶說明為何要使用該權(quán)限)
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();
}
}
});