Android 動(dòng)態(tài)權(quán)限設(shè)計(jì) (權(quán)限的申請(qǐng)與處理)


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ò)程,通常如下:

  1. 檢測(cè)權(quán)限
    ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED
  2. 在Activity中請(qǐng)求權(quán)限
    ActivityCompat.requestPermissions(activity, permissions, resultCode);
  3. APP彈出系統(tǒng)的授權(quán)提示框,讓用戶(hù)授權(quán)(用戶(hù)可授權(quán)、也可取消、也可選擇不再提醒)
  4. 在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ō)中的那么艱難。

  1. 實(shí)驗(yàn)?zāi)康模簻y(cè)試檢測(cè)權(quán)限狀態(tài)的方法
  2. 實(shí)驗(yàn)機(jī)器:小米7.1.2 三星6.0.1
  3. 查詢(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());
  1. 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"/>
三星6.0.1.jpg
小米7.1.2.jpg

通過(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ī)則。

  1. 測(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ā)者的主要原因。

  1. 測(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ī)測(cè)試.png

經(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

4.png

由上面兩圖看到,我們請(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é)果一致。

  1. 關(guān)于低版本適配
    動(dòng)態(tài)權(quán)限是在6.0以上才提供的,所以我們不需要想的太復(fù)雜,只需要在23以上判斷請(qǐng)求動(dòng)態(tài)權(quán)限,23以下按照以往的方式處理即可。

設(shè)計(jì)思路:

  1. 動(dòng)態(tài)權(quán)限為獨(dú)立的體系,可以設(shè)計(jì)成單獨(dú)的模塊;
  2. 在Activity中動(dòng)態(tài)權(quán)限的處理盡量簡(jiǎn)潔,應(yīng)封裝權(quán)限的檢查與請(qǐng)求、權(quán)限結(jié)果回調(diào)等操作;
  3. 應(yīng)考慮權(quán)限請(qǐng)求、處理的可擴(kuò)展性
  4. 權(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即可;

權(quán)限結(jié)果處理.png

通過(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):

  1. 權(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;
    }
}
  1. 權(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();
    }
}
  1. 公共內(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();
    }
}

效果圖:

請(qǐng)求權(quán)限.jpg
請(qǐng)求權(quán)限--帶不再提醒6.jpg
引導(dǎo)用戶(hù)去授權(quán)--包含需要的權(quán)限名稱(chēng).jpg

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í)反饋給我們,感謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容