Demo下載地址:https://pan.baidu.com/s/1dnaugm
需求:
最近把APP的TargetSdk從21提高至25,梳理了一下,APP中有多個(gè)地方用到了動(dòng)態(tài)權(quán)限。
所以,需要把動(dòng)態(tài)權(quán)限的申請(qǐng)與處理統(tǒng)一設(shè)計(jì)。
動(dòng)態(tài)權(quán)限的基礎(chǔ)知識(shí),不再累述,請(qǐng)自行查詢(xún)資料。
安卓端動(dòng)態(tài)權(quán)限請(qǐng)求與處理的過(guò)程,通常如下:
- 檢測(cè)權(quán)限
ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED - 在Activity中請(qǐng)求權(quán)限
ActivityCompat.requestPermissions(activity, permissions, resultCode); - APP彈出系統(tǒng)的授權(quán)提示框,讓用戶(hù)授權(quán)(用戶(hù)可授權(quán)、也可取消、也可選擇不再提醒)
- 在Activity的onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)方法中,返回用戶(hù)授權(quán)的信息(請(qǐng)求碼,請(qǐng)求的權(quán)限集合、每個(gè)權(quán)限授權(quán)的情況集合)
a) 如果已都已授權(quán),可以繼續(xù)操作,也可以是什么也不做,等待用戶(hù)重新操作;
b) 如果未授權(quán),引導(dǎo)用戶(hù)去授權(quán);
常見(jiàn)問(wèn)題分析 與 真機(jī)實(shí)驗(yàn):
總結(jié)安卓動(dòng)態(tài)權(quán)限時(shí),在網(wǎng)上也看了不少資料,反應(yīng)最多的問(wèn)題就是:
無(wú)法絕對(duì)檢測(cè)權(quán)限的狀態(tài)(授予、拒絕、禁止(不再提醒))?。?!
原因如:各大手機(jī)廠商的定制或自身的權(quán)限體系與谷歌不一致、checkSelfPermission無(wú)效、shouldShowRequestPermissionRationale無(wú)效等等,對(duì)應(yīng)的解釋也是千奇百怪。
與其那么糾結(jié),不如自己動(dòng)手實(shí)驗(yàn)一下,看一看動(dòng)態(tài)權(quán)限的判斷是否如傳說(shuō)中的那么艱難。
- 實(shí)驗(yàn)?zāi)康模簻y(cè)試檢測(cè)權(quán)限狀態(tài)的方法
- 實(shí)驗(yàn)機(jī)器:小米7.1.2 三星6.0.1
- 查詢(xún)官方資料,查找對(duì)應(yīng)的API
用戶(hù)權(quán)限狀態(tài)檢測(cè)的 主要方法有3種,谷歌官方大概釋義如下:
//方法1:檢測(cè)是否授予某權(quán)限,如果是返回0,反之,返回-1
ContextCompat.checkSelfPermission(activity, permission);
//方法2:是否應(yīng)該向用戶(hù)顯示請(qǐng)求權(quán)限的原因說(shuō)明(true/false)
ActivityCompat.shouldShowRequestPermissionRationale(activity, permission);
//方法3:判斷程序的某操作是被允許的還是拒絕的還是忽略的
AppOpsManagerCompat.noteProxyOp(activity, op, activity.getPackageName());
- Google將權(quán)限進(jìn)行了分組,申請(qǐng)屬于normal permissions的權(quán)限在manifest中聲明即可,對(duì)于dangerous permissions 權(quán)限必須運(yùn)行時(shí)動(dòng)態(tài)申請(qǐng)。
normal permissions權(quán)限如下:
android.permission.ACCESS_LOCATION_EXTRA_COMMANDS
android.permission.ACCESS_NETWORK_STATE
android.permission.ACCESS_NOTIFICATION_POLICY
android.permission.ACCESS_WIFI_STATE
android.permission.ACCESS_WIMAX_STATE
android.permission.BLUETOOTH
android.permission.BLUETOOTH_ADMIN
android.permission.BROADCAST_STICKY
android.permission.CHANGE_NETWORK_STATE
android.permission.CHANGE_WIFI_MULTICAST_STATE
android.permission.CHANGE_WIFI_STATE
android.permission.CHANGE_WIMAX_STATE
android.permission.DISABLE_KEYGUARD
android.permission.EXPAND_STATUS_BAR
android.permission.FLASHLIGHT
android.permission.GET_ACCOUNTS
android.permission.GET_PACKAGE_SIZE
android.permission.INTERNET
android.permission.KILL_BACKGROUND_PROCESSES
android.permission.MODIFY_AUDIO_SETTINGS
android.permission.NFC
android.permission.READ_SYNC_SETTINGS
android.permission.READ_SYNC_STATS
android.permission.RECEIVE_BOOT_COMPLETED
android.permission.REORDER_TASKS
android.permission.REQUEST_INSTALL_PACKAGES
android.permission.SET_TIME_ZONE
android.permission.SET_WALLPAPER
android.permission.SET_WALLPAPER_HINTS
android.permission.SUBSCRIBED_FEEDS_READ
android.permission.TRANSMIT_IR
android.permission.USE_FINGERPRINT
android.permission.VIBRATE
android.permission.WAKE_LOCK
android.permission.WRITE_SYNC_SETTINGS
com.android.alarm.permission.SET_ALARM
com.android.launcher.permission.INSTALL_SHORTCUT
com.android.launcher.permission.UNINSTALL_SHORTCUT
我們先來(lái)看看將normal 和 dangerous的權(quán)限 同時(shí)在6.0+手機(jī)上的效果吧:
我們準(zhǔn)備測(cè)試的權(quán)限如下,共6個(gè):
前面的4個(gè)為危險(xiǎn)權(quán)限,分為3類(lèi)(SD卡讀寫(xiě)、相機(jī)、短信);
后面的2個(gè)為標(biāo)準(zhǔn)權(quán)限,分為2類(lèi)(音頻調(diào)節(jié)、桌面快捷方式);
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>


通過(guò)上面兩圖可以看到,申請(qǐng)同樣的權(quán)限,在不同手機(jī)上顯示的效果是不同的:
三星只顯示了危險(xiǎn)的權(quán)限,但是位置權(quán)限我們沒(méi)有申請(qǐng),它也給默認(rèn)顯示了出來(lái);
小米將危險(xiǎn)權(quán)限和標(biāo)準(zhǔn)權(quán)限都顯示了出來(lái),不過(guò)標(biāo)準(zhǔn)權(quán)限默認(rèn)都顯示著禁用的圖標(biāo),清單中倒是沒(méi)有位置權(quán)限,卻多了后臺(tái)彈出等權(quán)限項(xiàng)。
推測(cè):不同廠商對(duì)于授權(quán)頁(yè)面都有自己的定制規(guī)則。
- 測(cè)試方法1:檢測(cè)是否授予某權(quán)限,如果是,返回0,反之,返回-1
ContextCompat.checkSelfPermission(activity, permission);
//動(dòng)態(tài)請(qǐng)求的權(quán)限數(shù)組
String[] permissions =new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.CAMERA,
Manifest.permission.READ_SMS,
Manifest.permission.INSTALL_SHORTCUT,
Manifest.permission.MODIFY_AUDIO_SETTINGS};
//測(cè)試
Log.i("checkSelfPermission", "--------------------------------"+permissions.length);
for (String permission : permissions) {
int isGranted = ContextCompat.checkSelfPermission(activity, permission);
if (isGranted == PackageManager.PERMISSION_GRANTED) {
//已授權(quán)
Log.i("checkSelfPermission", PermissionUtils.getInstance().getPermissionName(permission)+ ": 已授權(quán)");
}else if(isGranted == PackageManager.PERMISSION_DENIED){
//未授權(quán)的
Log.i("checkSelfPermission", PermissionUtils.getInstance().getPermissionName(permission)+ ": 未授權(quán)");
}
}
Log.i("checkSelfPermission", "--------------------------------"+permissions.length);
運(yùn)行兩個(gè)手機(jī),輸出的結(jié)果如下:
三星:
02-26 15:52:07.376 3247-3247/iwangzhe.paizhaocaiqie I/checkSelfPermission: --------------------------------5
02-26 15:52:07.396 3247-3247/iwangzhe.paizhaocaiqie I/checkSelfPermission: --文件存儲(chǔ): 未授權(quán)
02-26 15:52:07.396 3247-3247/iwangzhe.paizhaocaiqie I/checkSelfPermission: --相機(jī)/拍照: 未授權(quán)
02-26 15:52:07.396 3247-3247/iwangzhe.paizhaocaiqie I/checkSelfPermission: --短信: 未授權(quán)
02-26 15:52:07.396 3247-3247/iwangzhe.paizhaocaiqie I/checkSelfPermission: com.android.launcher.permission.INSTALL_SHORTCUT: 已授權(quán)
02-26 15:52:07.396 3247-3247/iwangzhe.paizhaocaiqie I/checkSelfPermission: android.permission.MODIFY_AUDIO_SETTINGS: 已授權(quán)
02-26 15:52:07.396 3247-3247/iwangzhe.paizhaocaiqie I/checkSelfPermission: --------------------------------5
小米:
02-26 15:58:09.128 7306-7306/iwangzhe.paizhaocaiqie I/checkSelfPermission: --------------------------------5
02-26 15:58:09.131 7306-7306/iwangzhe.paizhaocaiqie I/checkSelfPermission: --文件存儲(chǔ): 未授權(quán)
02-26 15:58:09.131 7306-7306/iwangzhe.paizhaocaiqie I/checkSelfPermission: --相機(jī)/拍照: 未授權(quán)
02-26 15:58:09.132 7306-7306/iwangzhe.paizhaocaiqie I/checkSelfPermission: --短信: 未授權(quán)
02-26 15:58:09.132 7306-7306/iwangzhe.paizhaocaiqie I/checkSelfPermission: com.android.launcher.permission.INSTALL_SHORTCUT: 已授權(quán)
02-26 15:58:09.132 7306-7306/iwangzhe.paizhaocaiqie I/checkSelfPermission: android.permission.MODIFY_AUDIO_SETTINGS: 已授權(quán)
02-26 15:58:09.132 7306-7306/iwangzhe.paizhaocaiqie I/checkSelfPermission: --------------------------------5
通過(guò)上面兩圖可以看出,兩種手機(jī)輸出的結(jié)果都是一樣的,也都是正確的。
只是小米的手機(jī)顯示的狀態(tài)與輸出的結(jié)果有些歧義,對(duì)于標(biāo)準(zhǔn)權(quán)限(音頻調(diào)節(jié)、快捷方式),明明標(biāo)識(shí)著拒絕的圖標(biāo)(紅×),輸出的結(jié)果卻是已授權(quán)(已授權(quán)是正確的結(jié)果)。這可能是小米手機(jī)本身的小問(wèn)題吧,這也可能也是迷惑很多開(kāi)發(fā)者的主要原因。
- 測(cè)試方法2:是否應(yīng)該向用戶(hù)顯示請(qǐng)求權(quán)限的原因說(shuō)明(true/false)
ActivityCompat.shouldShowRequestPermissionRationale(activity, permission);
String[] permissions =new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.CAMERA,
Manifest.permission.READ_SMS,
Manifest.permission.INSTALL_SHORTCUT,
Manifest.permission.MODIFY_AUDIO_SETTINGS};
//測(cè)試
Log.i("checkSelfPermission", "--------------------------------"+permissions.length);
for (String permission : permissions) {
//拒絕且不再提醒、系統(tǒng)默認(rèn)禁用的權(quán)限、用戶(hù)未點(diǎn)過(guò)拒絕的權(quán)限均返回false, 只有用戶(hù)點(diǎn)擊拒絕后,且沒(méi)有勾選不再提醒返回true
if(!ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)){
Log.i("checkSelfPermission @@", PermissionUtils.getInstance().getPermissionName(permission)+ ": false");
}else{
Log.i("checkSelfPermission @@", PermissionUtils.getInstance().getPermissionName(permission)+ ": true");
}
}
Log.i("checkSelfPermission", "--------------------------------"+permissions.length);
運(yùn)行兩個(gè)手機(jī),直接運(yùn)行,默認(rèn)輸出的結(jié)果均一致
小米、三星:
02-26 16:02:59.382 13325-13325/iwangzhe.paizhaocaiqie I/checkSelfPermission: --------------------------------5
02-26 16:02:59.383 13325-13325/iwangzhe.paizhaocaiqie I/checkSelfPermission @@: --文件存儲(chǔ): false
02-26 16:02:59.384 13325-13325/iwangzhe.paizhaocaiqie I/checkSelfPermission @@: --相機(jī)/拍照: false
02-26 16:02:59.384 13325-13325/iwangzhe.paizhaocaiqie I/checkSelfPermission @@: --短信: false
02-26 16:02:59.384 13325-13325/iwangzhe.paizhaocaiqie I/checkSelfPermission @@: com.android.launcher.permission.INSTALL_SHORTCUT: false
02-26 16:02:59.385 13325-13325/iwangzhe.paizhaocaiqie I/checkSelfPermission @@: android.permission.MODIFY_AUDIO_SETTINGS: false
02-26 16:02:59.385 13325-13325/iwangzhe.paizhaocaiqie I/checkSelfPermission: --------------------------------5
允許SD卡存儲(chǔ)、拒絕拍照(未勾選不再提醒)后的輸出結(jié)果,如下:
小米、三星:
02-26 16:07:09.383 16248-16248/iwangzhe.paizhaocaiqie I/checkSelfPermission: --------------------------------5
02-26 16:07:09.385 16248-16248/iwangzhe.paizhaocaiqie I/checkSelfPermission @@: --文件存儲(chǔ): false
02-26 16:07:09.386 16248-16248/iwangzhe.paizhaocaiqie I/checkSelfPermission @@: --相機(jī)/拍照: true
02-26 16:07:09.387 16248-16248/iwangzhe.paizhaocaiqie I/checkSelfPermission @@: --短信: false
02-26 16:07:09.388 16248-16248/iwangzhe.paizhaocaiqie I/checkSelfPermission @@: com.android.launcher.permission.INSTALL_SHORTCUT: false
02-26 16:07:09.390 16248-16248/iwangzhe.paizhaocaiqie I/checkSelfPermission @@: android.permission.MODIFY_AUDIO_SETTINGS: false
02-26 16:07:09.390 16248-16248/iwangzhe.paizhaocaiqie I/checkSelfPermission: --------------------------------5

經(jīng)過(guò)反復(fù)測(cè)試:
當(dāng)用戶(hù)禁用(拒絕且不再提醒)、系統(tǒng)默認(rèn)禁用的權(quán)限、用戶(hù)未點(diǎn)過(guò)拒絕的權(quán)限、用戶(hù)授權(quán)的權(quán)限均返回false;
只有用戶(hù)點(diǎn)擊拒絕后,且沒(méi)有勾選不再提醒的權(quán)限返回true;
7.測(cè)試方法3:判斷程序的某操作是被允許的還是拒絕的還是忽略的
AppOpsManagerCompat.noteProxyOp(activity, op, activity.getPackageName());
String[] permissions =new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.CAMERA,
Manifest.permission.READ_SMS,
Manifest.permission.INSTALL_SHORTCUT,
Manifest.permission.MODIFY_AUDIO_SETTINGS};
//測(cè)試
Log.i("checkSelfPermission", "***********************"+permissions.length);
for (String permission : permissions) {
String op = AppOpsManagerCompat.permissionToOp(permission);
//判斷非空
if (TextUtils.isEmpty(op)) continue;
int result = AppOpsManagerCompat.noteProxyOp(activity, op, activity.getPackageName());
if (result == AppOpsManagerCompat.MODE_IGNORED) {
Log.i("checkSelfPermission", "**" + permission + " false");
} else if(result == AppOpsManagerCompat.MODE_ALLOWED) {
Log.i("checkSelfPermission", "**" + permission + " true");
}else if(result == AppOpsManagerCompat.MODE_DEFAULT) {
Log.i("checkSelfPermission", "**" + permission + " default");
}
}
Log.i("checkSelfPermission", "***********************"+permissions.length);
02-26 16:18:03.016 6714-6714/iwangzhe.paizhaocaiqie I/checkSelfPermission: ***********************5
02-26 16:18:03.017 6714-6714/iwangzhe.paizhaocaiqie I/checkSelfPermission: **android.permission.READ_EXTERNAL_STORAGE true
02-26 16:18:05.546 6714-6714/iwangzhe.paizhaocaiqie I/checkSelfPermission: **android.permission.CAMERA false
02-26 16:18:05.546 6714-6714/iwangzhe.paizhaocaiqie I/checkSelfPermission: **android.permission.READ_SMS false
02-26 16:18:05.547 6714-6714/iwangzhe.paizhaocaiqie I/checkSelfPermission: ***********************5

由上面兩圖看到,我們請(qǐng)求了5個(gè)權(quán)限,但是只輸出的3項(xiàng)權(quán)限,均為危險(xiǎn)權(quán)限,而標(biāo)準(zhǔn)權(quán)限沒(méi)有任何輸出,因?yàn)檗D(zhuǎn)op時(shí)為null了,可能是標(biāo)準(zhǔn)權(quán)限不在轉(zhuǎn)換清單中,所以....。
經(jīng)過(guò)測(cè)試,獲得的危險(xiǎn)權(quán)限授權(quán)狀態(tài)的結(jié)果與checkSelfPermission方法返回的結(jié)果一致。
- 關(guān)于低版本適配
動(dòng)態(tài)權(quán)限是在6.0以上才提供的,所以我們不需要想的太復(fù)雜,只需要在23以上判斷請(qǐng)求動(dòng)態(tài)權(quán)限,23以下按照以往的方式處理即可。
設(shè)計(jì)思路:
- 動(dòng)態(tài)權(quán)限為獨(dú)立的體系,可以設(shè)計(jì)成單獨(dú)的模塊;
- 在Activity中動(dòng)態(tài)權(quán)限的處理盡量簡(jiǎn)潔,應(yīng)封裝權(quán)限的檢查與請(qǐng)求、權(quán)限結(jié)果回調(diào)等操作;
- 應(yīng)考慮權(quán)限請(qǐng)求、處理的可擴(kuò)展性
-
權(quán)限的檢測(cè)和請(qǐng)求可統(tǒng)一封裝為 “權(quán)限請(qǐng)求”,權(quán)限結(jié)果回調(diào)可統(tǒng)一封裝為“權(quán)限結(jié)果處理”
權(quán)限請(qǐng)求.png
通過(guò)上圖,可以看到,權(quán)限判斷被封裝進(jìn)了權(quán)限請(qǐng)求中,所以我們?cè)谡?qǐng)求/判斷權(quán)限時(shí),統(tǒng)一調(diào)用接口IRequestPermissions即可;

通過(guò)上圖,可以看到,權(quán)限結(jié)果處理方案有多種,我們此處采取的是策略模式,以擴(kuò)展更多的方案;
RequestPermissionsResult、RequestPermissionsResultSetApp分別表示不同的處理方案。
那么,我們?cè)偬幚頇?quán)限結(jié)果回調(diào)時(shí),僅需調(diào)用接口IRequestPermissionsResult即可。
先來(lái)看下代碼中是如何調(diào)用的:
第一步:實(shí)例化 “權(quán)限請(qǐng)求”,“權(quán)限結(jié)果處理”
IRequestPermissions requestPermissions = RequestPermissions.getInstance();//動(dòng)態(tài)權(quán)限請(qǐng)求
IRequestPermissionsResult requestPermissionsResult = RequestPermissionsResultSetApp.getInstance();//動(dòng)態(tài)權(quán)限請(qǐng)求結(jié)果處理
第二步:在需要的地方,請(qǐng)求權(quán)限
//請(qǐng)求權(quán)限
private boolean requestPermissions(){
//需要請(qǐng)求的權(quán)限
String[] permissions = {Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.CAMERA};
//開(kāi)始請(qǐng)求權(quán)限
return requestPermissions.requestPermissions(
this,
permissions,
PermissionUtils.ResultCode1);
}
第三步:onRequestPermissionsResult回調(diào)中,統(tǒng)一處理結(jié)果
//用戶(hù)授權(quán)操作結(jié)果(可能授權(quán)了,也可能未授權(quán))
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//用戶(hù)給APP授權(quán)的結(jié)果
//判斷grantResults是否已全部授權(quán),如果是,執(zhí)行相應(yīng)操作,如果否,提醒開(kāi)啟權(quán)限
if(requestPermissionsResult.doRequestPermissionsResult(this, permissions, grantResults)){
//請(qǐng)求的權(quán)限全部授權(quán)成功,此處可以做自己想做的事了
//輸出授權(quán)結(jié)果
Toast.makeText(MainActivity.this,"授權(quán)成功,請(qǐng)重新點(diǎn)擊剛才的操作!",Toast.LENGTH_LONG).show();
}else{
//輸出授權(quán)結(jié)果
Toast.makeText(MainActivity.this,"請(qǐng)給APP授權(quán),否則功能無(wú)法正常使用!",Toast.LENGTH_LONG).show();
}
}
通過(guò)這3步,即可快速實(shí)現(xiàn)動(dòng)態(tài)權(quán)限的所有操作。
而如何檢測(cè)、判斷、請(qǐng)求的權(quán)限,如何引導(dǎo)用戶(hù)設(shè)置權(quán)限等相關(guān)事項(xiàng),開(kāi)發(fā)人員不需要再關(guān)注。
具體的實(shí)現(xiàn):
-
權(quán)限請(qǐng)求
3.png
權(quán)限請(qǐng)求接口
public interface IRequestPermissions {
/**
* 請(qǐng)求權(quán)限
* @param activity 上下文
* @param permissions 權(quán)限集合
* @param resultCode 請(qǐng)求碼
* @return 如果權(quán)限已全部允許,返回true; 反之,請(qǐng)求權(quán)限,在
*/
boolean requestPermissions(Activity activity, String[] permissions, int resultCode);
}
權(quán)限請(qǐng)求實(shí)現(xiàn)類(lèi)
public class RequestPermissions implements IRequestPermissions {
private static RequestPermissions requestPermissions;
public static RequestPermissions getInstance(){
if(requestPermissions == null){
requestPermissions = new RequestPermissions();
}
return requestPermissions;
}
@Override
public boolean requestPermissions(Activity activity, String[] permissions, int resultCode) {
//判斷手機(jī)版本是否23以下,如果是,不需要使用動(dòng)態(tài)權(quán)限
if(Build.VERSION.SDK_INT < 23){
return true;
}
//判斷并請(qǐng)求權(quán)限
return requestNeedPermission(activity,permissions,resultCode);
}
private boolean requestAllPermission(Activity activity, String[] permissions, int resultCode){
//判斷是否已賦予了全部權(quán)限
boolean isAllGranted = CheckPermission.checkPermissionAllGranted(activity, permissions);
if(isAllGranted){
return true;
}
ActivityCompat.requestPermissions(activity, permissions, resultCode);
return false;
}
private boolean requestNeedPermission(Activity activity, String[] permissions, int resultCode){
List<String> list = CheckPermission.checkPermissionDenied(activity, permissions);
if(list.size() == 0){
return true;
}
//請(qǐng)求權(quán)限
String[] deniedPermissions = list.toArray(new String[list.size()]);
ActivityCompat.requestPermissions(activity, deniedPermissions, resultCode);
return false;
}
}
權(quán)限檢測(cè)類(lèi)
public class CheckPermission {
/**
* 檢查是否擁有指定的所有權(quán)限
* @param context 上下文
* @param permissions 權(quán)限數(shù)組
* @return 只要有一個(gè)權(quán)限沒(méi)有被授予, 則直接返回 false,否則,返回true!
*/
public static boolean checkPermissionAllGranted(Context context, String[] permissions) {
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
/**
* 檢查未允許的權(quán)限集合
* @param context 上下文
* @param permissions 權(quán)限集合
* @return 未允許的權(quán)限集合
*/
public static List<String> checkPermissionDenied(Context context, String[] permissions){
List<String> lstPermissions = new ArrayList<>();
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
lstPermissions.add(permission);
}
}
return lstPermissions;
}
}
-
權(quán)限請(qǐng)求結(jié)果處理
4.png
接口
public interface IRequestPermissionsResult {
/**
* 處理權(quán)限請(qǐng)求結(jié)果
* @param activity
* @param permissions 請(qǐng)求的權(quán)限數(shù)組
* @param grantResults 權(quán)限請(qǐng)求結(jié)果數(shù)組
* @return 處理權(quán)限結(jié)果如果全部通過(guò),返回true;否則,引導(dǎo)用戶(hù)去授權(quán)頁(yè)面
*/
boolean doRequestPermissionsResult(Activity activity, @NonNull String[] permissions, @NonNull int[] grantResults);
}
權(quán)限請(qǐng)求結(jié)果實(shí)現(xiàn)類(lèi)(方案一:如果授權(quán)失敗,不做任何處理)
public class RequestPermissionsResult implements IRequestPermissionsResult {
private static RequestPermissionsResult requestPermissionsResult;
public static RequestPermissionsResult getInstance(){
if(requestPermissionsResult == null){
requestPermissionsResult = new RequestPermissionsResult();
}
return requestPermissionsResult;
}
@Override
public boolean doRequestPermissionsResult(Activity activity, @NonNull String[] permissions, @NonNull int[] grantResults) {
boolean isAllGranted = true;
// 判斷是否所有的權(quán)限都已經(jīng)授予了
for (int grant : grantResults) {
if (grant != PackageManager.PERMISSION_GRANTED) {
isAllGranted = false;
break;
}
}
//已全部授權(quán)
if (isAllGranted) {
return true;
}
else {
//什么也不做
}
return false;
}
}
權(quán)限請(qǐng)求結(jié)果實(shí)現(xiàn)類(lèi)(方案二:如果授權(quán)失敗,引導(dǎo)用戶(hù)進(jìn)行應(yīng)用授權(quán))
public class RequestPermissionsResultSetApp implements IRequestPermissionsResult{
private static RequestPermissionsResultSetApp requestPermissionsResult;
public static RequestPermissionsResultSetApp getInstance(){
if(requestPermissionsResult == null){
requestPermissionsResult = new RequestPermissionsResultSetApp();
}
return requestPermissionsResult;
}
@Override
public boolean doRequestPermissionsResult(Activity activity, @NonNull String[] permissions, @NonNull int[] grantResults) {
List<String> deniedPermission = new ArrayList<>();
for (int i=0; i<grantResults.length;i++){
if(grantResults[i] == PackageManager.PERMISSION_DENIED){
deniedPermission.add(permissions[i]);
}
}
//已全部授權(quán)
if (deniedPermission.size() == 0) {
return true;
}
//引導(dǎo)用戶(hù)去授權(quán)
else {
String name = PermissionUtils.getInstance().getPermissionNames(deniedPermission);
SetPermissions.openAppDetails(activity,name);
}
return false;
}
}
引導(dǎo)用戶(hù)去授權(quán)
public class SetPermissions {
/**
* 打開(kāi)APP詳情頁(yè)面,引導(dǎo)用戶(hù)去設(shè)置權(quán)限
* @param activity 頁(yè)面對(duì)象
* @param permissionNames 權(quán)限名稱(chēng)(如是多個(gè),使用\n分割)
*/
public static void openAppDetails(final Activity activity, String permissionNames) {
StringBuilder sb = new StringBuilder();
sb.append(PermissionUtils.PermissionTip1);
sb.append(permissionNames);
sb.append(PermissionUtils.PermissionTip2);
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setMessage(sb.toString());
builder.setPositiveButton(PermissionUtils.PermissionDialogPositiveButton, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setData(Uri.parse("package:" + activity.getPackageName()));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
activity.startActivity(intent);
}
});
builder.setNegativeButton(PermissionUtils.PermissionDialogNegativeButton, null);
builder.show();
}
}
- 公共內(nèi)容提?。ǔA?、方法等)
public class PermissionUtils {
public static int ResultCode1 = 100;//權(quán)限請(qǐng)求碼
public static int ResultCode2 = 200;//權(quán)限請(qǐng)求碼
public static int ResultCode3 = 300;//權(quán)限請(qǐng)求碼
public static String PermissionTip1 = "親愛(ài)的用戶(hù) \n\n軟件部分功能需要請(qǐng)求您的手機(jī)權(quán)限,請(qǐng)?jiān)试S以下權(quán)限:\n\n";//權(quán)限提醒
public static String PermissionTip2 = "\n請(qǐng)到 “應(yīng)用信息 -> 權(quán)限” 中授予!";//權(quán)限提醒
public static String PermissionDialogPositiveButton = "去手動(dòng)授權(quán)";
public static String PermissionDialogNegativeButton = "取消";
private static PermissionUtils permissionUtils;
public static PermissionUtils getInstance(){
if(permissionUtils == null){
permissionUtils = new PermissionUtils();
}
return permissionUtils;
}
private HashMap<String,String> permissions;
public HashMap<String,String> getPermissions(){
if(permissions == null){
permissions = new HashMap<>();
initPermissions();
}
return permissions;
}
private void initPermissions(){
//聯(lián)系人/通訊錄權(quán)限
permissions.put("android.permission.WRITE_CONTACTS","--通訊錄/聯(lián)系人");
permissions.put("android.permission.GET_ACCOUNTS","--通訊錄/聯(lián)系人");
permissions.put("android.permission.READ_CONTACTS","--通訊錄/聯(lián)系人");
//電話權(quán)限
permissions.put("android.permission.READ_CALL_LOG","--電話");
permissions.put("android.permission.READ_PHONE_STATE","--電話");
permissions.put("android.permission.CALL_PHONE","--電話");
permissions.put("android.permission.WRITE_CALL_LOG","--電話");
permissions.put("android.permission.USE_SIP","--電話");
permissions.put("android.permission.PROCESS_OUTGOING_CALLS","--電話");
permissions.put("com.android.voicemail.permission.ADD_VOICEMAIL","--電話");
//日歷權(quán)限
permissions.put("android.permission.READ_CALENDAR","--日歷");
permissions.put("android.permission.WRITE_CALENDAR","--日歷");
//相機(jī)拍照權(quán)限
permissions.put("android.permission.CAMERA","--相機(jī)/拍照");
//傳感器權(quán)限
permissions.put("android.permission.BODY_SENSORS","--傳感器");
//定位權(quán)限
permissions.put("android.permission.ACCESS_FINE_LOCATION","--定位");
permissions.put("android.permission.ACCESS_COARSE_LOCATION","--定位");
//文件存取
permissions.put("android.permission.READ_EXTERNAL_STORAGE","--文件存儲(chǔ)");
permissions.put("android.permission.WRITE_EXTERNAL_STORAGE","--文件存儲(chǔ)");
//音視頻、錄音權(quán)限
permissions.put("android.permission.RECORD_AUDIO","--音視頻/錄音");
//短信權(quán)限
permissions.put("android.permission.READ_SMS","--短信");
permissions.put("android.permission.RECEIVE_WAP_PUSH","--短信");
permissions.put("android.permission.RECEIVE_MMS","--短信");
permissions.put("android.permission.RECEIVE_SMS","--短信");
permissions.put("android.permission.SEND_SMS","--短信");
permissions.put("android.permission.READ_CELL_BROADCASTS","--短信");
}
/**
* 獲得權(quán)限名稱(chēng)集合(去重)
* @param permission 權(quán)限數(shù)組
* @return 權(quán)限名稱(chēng)
*/
public String getPermissionNames(List<String> permission){
if(permission==null || permission.size()==0){
return "\n";
}
StringBuilder sb = new StringBuilder();
List<String> list = new ArrayList<>();
HashMap<String,String> permissions = getPermissions();
for(int i=0; i<permission.size(); i++){
String name = permissions.get(permission.get(i));
if(name!=null && !list.contains(name)){
list.add(name);
sb.append(name);
sb.append("\n");
}
}
return sb.toString();
}
}
效果圖:



Demo下載地址:https://pan.baidu.com/s/1dnaugm
注意:
以上內(nèi)容只是為了大家能清晰的理解動(dòng)態(tài)權(quán)限的使用,Demo可以作為代碼參考,但是不應(yīng)拿到項(xiàng)目中直接使用,因?yàn)椴煌捻?xiàng)目中有不同的要求和限制。使用該Demo中的代碼時(shí),請(qǐng)根據(jù)自己項(xiàng)目的要求,進(jìn)一步優(yōu)化、調(diào)整Demo的代碼(如什么場(chǎng)合使用checkSelfPermission、shouldShowRequestPermissionRationale還是noteProxyOp等)
本案例如有問(wèn)題,請(qǐng)及時(shí)反饋給我們,感謝!


