【轉(zhuǎn)】運行時權(quán)限請求框架PermissionsDispatcher、RxPermissions和easypermissions的使用和對比

本文出處
炎之鎧csdn博客:http://blog.csdn.net/totond
本文demo地址:https://github.com/totond/PermissionsApplyDemo

前言

Android6.0版本的一個重大改動就是增加了運行時權(quán)限(動態(tài)權(quán)限):一些危險的權(quán)限不單止要在AndroidMainifest文件聲明,還要在運行的時候使用代碼來申請,讓用戶同意才進行授權(quán)。
  由于Android自帶的API使用起來(怎么使用就不寫了,網(wǎng)上很多)比較麻煩,所以網(wǎng)上出現(xiàn)了一大堆簡化這個過程的開源庫,這里選取目前最流行的三個開源庫(GitHub上Star最多)PermissionsDispatcher、RxPermissions和easypermissions進行體驗并對比他們的用法,了解一下這三個庫的功能,方便做出選擇。

本文篇幅較長,如果不想看下面對這三個庫的的使用的話,可以直接跳到結(jié)論。

準備工作

這里的demo使用一個Activity來測試每一種動態(tài)權(quán)限請求框架,分別測試它們的請求單個權(quán)限和請求多個權(quán)限的功能:
[圖片上傳失敗...(image-90d7f3-1527944264600)]

這里檢查權(quán)限的方法我采用了一個工具類封裝(由于PermissionsDispatcher、RxPermissions都沒帶有單純檢查權(quán)限的功能,只有easypermissions有,這里用一個工具類封裝一下檢查權(quán)限的方法,返回我想要的字符串):

public class PermissionsLogUtils {
    private static StringBuffer logStringBuffer = new StringBuffer();
    // 查看權(quán)限是否已申請
    public static String checkPermissions(Context context,String... permissions) {
        logStringBuffer.delete(0,logStringBuffer.length());
        for (String permission : permissions) {
            logStringBuffer.append(permission);
            logStringBuffer.append(" is applied? \n     ");
            logStringBuffer.append(isAppliedPermission(context,permission));
            logStringBuffer.append("\n\n");
        }
        return logStringBuffer.toString();
    }

    //使用EasyPermissions查看權(quán)限是否已申請
    public static String easyCheckPermissions(Context context,String ... permissions) {
        logStringBuffer.delete(0,logStringBuffer.length());
        for (String permission : permissions) {
            logStringBuffer.append(permission);
            logStringBuffer.append(" is applied? \n     ");
            logStringBuffer.append(EasyPermissions.hasPermissions(context,permission));
            logStringBuffer.append("\n\n");
        }
        return logStringBuffer.toString();
    }

    // 查看權(quán)限是否已申請
    private static boolean isAppliedPermission(Context context,String permission) {
        return context.checkSelfPermission(permission) ==
                PackageManager.PERMISSION_GRANTED;
    }
}

然后,還要在AndroidMainifest文件聲明demoAPP用到的權(quán)限,不在這里申明的話,無論后面在代碼怎么動態(tài)申請,返回的結(jié)果都是權(quán)限拒絕并不再詢問。

基于注解的PermissionsDispatcher

GitHub地址:https://github.com/hotchemi/PermissionsDispatcher
目前Star數(shù):4.7k

集成方式

在app的build.gradle文件里:

dependencies {
  compile('com.github.hotchemi:permissionsdispatcher:${latest.version}') {
      // if you don't use android.app.Fragment you can exclude support for them
      exclude module: "support-v13"
  }
  annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:2.4.0'
}

注意是把${latest.version}這整個換成最新版本號。

或者直接使用插件添加依賴,怎么用插件?下面有講。

使用

PermissionsDispatcher是基于注解來寫的庫,基本原理就是你給你寫的一個方法加上一個注解,然后它就會在適當(dāng)?shù)臅r候調(diào)用這個被注解的方法(這種方法很有趣,讓代碼變得簡潔和清晰,以后可以學(xué)一下)。
  目前PermissionsDispatcher支持5個注解,先看看GitHub主頁的介紹:


image
  • @RuntimePermissions注解:這是必須使用的注解,用于標注在你想要申請權(quán)限的Activity或者Fragment上,如demo里面的PermissionsDispatcherActivity:
@RuntimePermissions
public class PermissionsDispatcherActivity extends AppCompatActivity implements View.OnClickListener {
}
  • @NeedsPermission注解:這也是必須使用的注解,用于標注在你要獲取權(quán)限的方法,注解括號里面有參數(shù),傳入想要申請的權(quán)限。也就是說你獲取了相應(yīng)的權(quán)限之后就會執(zhí)行這個方法
    //獲取單個權(quán)限
    @NeedsPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
    public void getSingle() {
        Toast.makeText(this, "getSingle", Toast.LENGTH_SHORT).show();
    }

    //獲取多個權(quán)限
    @NeedsPermission({Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO})
    public void getMulti() {
        Toast.makeText(this, "getMulti", Toast.LENGTH_SHORT).show();
    }
  • @OnShowRationale注解:這個不是必須的注解,用于標注申請權(quán)限前需要執(zhí)行的方法,注解
    括號里面有參數(shù),傳入想要申請的權(quán)限,而且這個方法還要傳入一個PermissionRequest對象,這個對象有兩種方法:proceed()讓權(quán)限請求繼續(xù),cancel()讓請求中斷。也就是說,這個方法會攔截你發(fā)出的請求,這個方法用于告訴用戶你接下來申請的權(quán)限是干嘛的,說服用戶給你權(quán)限。
 @OnShowRationale({Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO})
    //給用戶解釋要請求什么權(quán)限,為什么需要此權(quán)限
    void showRationale(final PermissionRequest request) {
        new AlertDialog.Builder(this)
                .setMessage("使用此功能需要WRITE_EXTERNAL_STORAGE和RECORD_AUDIO權(quán)限,下一步將繼續(xù)請求權(quán)限")
                .setPositiveButton("下一步", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        request.proceed();//繼續(xù)執(zhí)行請求
                    }
                }).setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        request.cancel();//取消執(zhí)行請求
                    }
                })
                .show();
    }

    @OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)
        //給用戶解釋要請求什么權(quán)限,為什么需要此權(quán)限
    void showSingleRationale(final PermissionRequest request) {
        new AlertDialog.Builder(this)
                .setMessage("使用此功能需要WRITE_EXTERNAL_STORAGE,下一步將繼續(xù)請求權(quán)限")
                .setPositiveButton("下一步", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        request.proceed();//繼續(xù)執(zhí)行請求
                    }
                }).setNegativeButton("取消", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                request.cancel();//取消執(zhí)行請求
            }
        })
                .show();
    }
  • @OnPermissionDenied注解:這個也不是必須的注解,用于標注如果權(quán)限請求失敗,但是用戶沒有勾選不再詢問的時候執(zhí)行的方法,注解括號里面有參數(shù),傳入想要申請的權(quán)限。也就是說,我們可以在這個方法做申請權(quán)限失敗之后的處理,如像用戶解釋為什么要申請,或者重新申請操作等。
    @OnPermissionDenied({Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO})//一旦用戶拒絕了
    public void multiDenied() {
        Toast.makeText(this, "已拒絕一個或以上權(quán)限", Toast.LENGTH_SHORT).show();
    }

    @OnPermissionDenied(Manifest.permission.WRITE_EXTERNAL_STORAGE)//一旦用戶拒絕了
    public void StorageDenied() {
        Toast.makeText(this, "已拒絕WRITE_EXTERNAL_STORAGE權(quán)限", Toast.LENGTH_SHORT).show();
    }
  • @OnNeverAskAgain注解:這個也不是必須的注解,用于標注如果權(quán)限請求失敗,而且用戶勾選不再詢問的時候執(zhí)行的方法,注解括號里面有參數(shù),傳入想要申請的權(quán)限。也就是說,我們可以在這個方法做申請權(quán)限失敗并選擇不再詢問之后的處理。例如,可以告訴作者想開啟權(quán)限的就從手機設(shè)置里面開啟。

    注意,有些系統(tǒng)的不再詢問勾選項是要用戶拒絕授權(quán)一次才顯示出來的。


    @OnNeverAskAgain({Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO})//用戶選擇的不再詢問
    public void multiNeverAsk() {
        Toast.makeText(this, "已拒絕一個或以上權(quán)限,并不再詢問", Toast.LENGTH_SHORT).show();
    }
    @OnNeverAskAgain(Manifest.permission.WRITE_EXTERNAL_STORAGE)//用戶選擇的不再詢問
    public void StorageNeverAsk() {
        Toast.makeText(this, "已拒絕WRITE_EXTERNAL_STORAGE權(quán)限,并不再詢問", Toast.LENGTH_SHORT).show();
    }

注意,這些注解的方法都不能是private,原因看下面。

使用PermissionsDispatcher除了要實現(xiàn)注解之外,還要重寫Activity的onRequestPermissionsResult()方法,在里面讓一個PermissionsDispatcher執(zhí)行回調(diào)。這個PermissionsDispatcher是什么來的呢?
  原來只要我們實現(xiàn)了@RuntimePermissions@NeedsPermission這兩個必須的注解之后,再build一次project之后,編譯器就會在在app\build\intermediates\classes\debug目錄下與被注解的Activity同一個包下生成一個輔助類,名稱為 “被注解的Activity的名稱+PermissionsDispatcher” 的輔助類,用來調(diào)用被注解的Activity的方法(就是因為這個所以被注解的方法不能private,private方法的作用域不在其他的類)。所以,第一次用的話,要注解好之后,build一次,下面的方法里面的PermissionsDispatcherActivityPermissionsDispatcher才不會令A(yù)S報紅。

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        PermissionsDispatcherActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
    }

最后,申請權(quán)限的時候,調(diào)用輔助類的方法(名字從下面可以看出是被@OnPermissionDenied注解的方法加上WithCheck,參數(shù)是這個Activity或者Fragment)就行了:

    //申請單個權(quán)限
    PermissionsDispatcherActivityPermissionsDispatcher.getSingleWithCheck(this);
    //申請多個權(quán)限
    PermissionsDispatcherActivityPermissionsDispatcher.getMultiWithCheck(this);

插件

覺得這么多注解要自己一個一個弄不夠方便,PermissionsDispatcher還在AndroidStudio做了插件,只要在setting設(shè)置里的plugins界面里搜索PermissionsDispatcher就可以安裝了,安裝完重啟一下就能使用:

  • 在所需的Activity或者Fragment的代碼里面右鍵,選擇Generate,然后就可以選擇Generate Runtime Permissions…(生成動態(tài)權(quán)限的生成)或者下面的Add PermissionsDispatcher dependencies(添加PermissionsDispatcher依賴)


    image
  • 點擊Generate Runtime Permissions…出現(xiàn)如下界面,輸入方法名字就能生成,很簡單粗暴:


    image

生成了這個,如果你沒onRequestPermissionsResult和@RuntimePermissions的話也會幫你加上:

    @NeedsPermission(Manifest.permission.CALL_PHONE)
    void call() {
    }

    @OnShowRationale(Manifest.permission.CALL_PHONE)
    void callshow(final PermissionRequest request) {
    }

谷歌推出的easypermissions

GitHub地址:https://github.com/googlesamples/easypermissions
目前Star數(shù):3.5k

集成方式

在app的build.gradle文件里:

dependencies {
    compile 'pub.devrel:easypermissions:0.4.2'
}

使用

easypermissions是谷歌給出的一個運行時權(quán)限申請庫(連谷歌自己都覺得自己的API用起來麻煩),下面我們來開始使用(下面步驟除了最后一步申請權(quán)限之外不分先后):

1.重寫要申請權(quán)限的Activity或者Fragment的onRequestPermissionsResult()方法,在里面調(diào)用EasyPermissions.onRequestPermissionsResult(),實現(xiàn)回調(diào)。

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
    }

2.讓需要申請權(quán)限的Activity或者Fragment實現(xiàn)EasyPermissions.PermissionCallbacks接口,重寫里面的方法:

public class EasyPermissionsActivity extends AppCompatActivity EasyPermissions.PermissionCallbacks{
}

  • onPermissionsGranted(int requestCode, List<String> list)方法:當(dāng)權(quán)限被成功申請的時候執(zhí)行回調(diào),requestCode是代表你權(quán)限請求的識別碼,list里面裝著申請的權(quán)限的名字:
    @Override
    public void onPermissionsGranted(int requestCode, List<String> perms) {
        switch (requestCode){
            case 0:
                Toast.makeText(this, "已獲取WRITE_EXTERNAL_STORAGE權(quán)限", Toast.LENGTH_SHORT).show();
                break;
            case 1:
                Toast.makeText(this, "已獲取WRITE_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE權(quán)限", Toast.LENGTH_SHORT).show();
                break;
        }
    }
  • onPermissionsDenied(int requestCode, List<String> perms)方法:當(dāng)權(quán)限申請失敗的時候執(zhí)行的回調(diào),參數(shù)意義同上。在這個方法里面,官方還建議用EasyPermissions.somePermissionPermanentlyDenied(this, perms)方法來判斷是否有權(quán)限被勾選了不再詢問并拒絕,還提供了一個AppSettingsDialog來給我們使用,在這個對話框里面解釋了APP需要這個權(quán)限的原因,用戶按下是的話會跳到APP的設(shè)置界面,可以去設(shè)置權(quán)限(是不是很不要臉_),這個Dialog可以使用默認的樣式new AppSettingsDialog.Builder(this).build().show(),也可以定制,像下面的一樣:
    @Override
    public void onPermissionsDenied(int requestCode, List<String> perms) {
        //處理權(quán)限名字字符串
        StringBuffer sb = new StringBuffer();
        for (String str : perms){
            sb.append(str);
            sb.append("\n");
        }
        sb.replace(sb.length() - 2,sb.length(),"");

        switch (requestCode){
            case 0:
                Toast.makeText(this, "已拒絕權(quán)限" + perms.get(0), Toast.LENGTH_SHORT).show();
                break;
            case 1:
                Toast.makeText(this, "已拒絕WRITE_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE權(quán)限"+ perms.get(0), Toast.LENGTH_SHORT).show();
                break;
        }
        if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
            Toast.makeText(this, "已拒絕權(quán)限" + sb + "并不再詢問" , Toast.LENGTH_SHORT).show();
            new AppSettingsDialog
                    .Builder(this)
                    .setRationale("此功能需要" + sb + "權(quán)限,否則無法正常使用,是否打開設(shè)置")
                    .setPositiveButton("好")
                    .setNegativeButton("不行")
                    .build()
                    .show();
        }
    }

3.(可選)檢查權(quán)限

easypermissions提供了EasyPermissions.hasPermissions(Context context, @NonNull String… perms)方法來檢測一個或者多個權(quán)限是否被允許(當(dāng)有一個權(quán)限被拒絕就會返回false),可能是因為Android自帶的checkSelfPermission()比較方便(或者沒這個必要?),PermissionsDispatcher和RxPermissions沒有實現(xiàn)這個查詢功能。這里我把它放到工具類里面封裝了使用:

    //使用EasyPermissions查看權(quán)限是否已申請
    public static String easyCheckPermissions(Context context,String ... permissions) {
        logStringBuffer.delete(0,logStringBuffer.length());
        for (String permission : permissions) {
            logStringBuffer.append(permission);
            logStringBuffer.append(" is applied? \n     ");
            logStringBuffer.append(EasyPermissions.hasPermissions(context,permission));
            logStringBuffer.append("\n\n");
        }
        return logStringBuffer.toString();
    }

4.(可選)添加@AfterPermissionGranted()注解

要傳入的參數(shù)是int類型的requestCode被這個注解標注的方法,當(dāng)這個requestCode的請求成功的時候,會執(zhí)行這個方法。其實就相當(dāng)于在onPermissionsGranted()調(diào)用這個方法而已:

    @AfterPermissionGranted(0)
    private void afterGet(){
        Toast.makeText(this, "已獲取權(quán)限,讓我們干愛干的事吧!", Toast.LENGTH_SHORT).show();
    }

5.調(diào)用申請權(quán)限

最后,就是調(diào)用EasyPermissions.requestPermissions()方法來申請權(quán)限了:


image
    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_check:
                String str = PermissionsLogUtils.easyCheckPermissions(this,
                        Manifest.permission.RECORD_AUDIO,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE);
                tv_log.setText(str);
                break;
            case R.id.btn_getSingle:
                EasyPermissions.requestPermissions(this,
                        "接下來需要獲取WRITE_EXTERNAL_STORAGE權(quán)限",
                        R.string.yes,
                        R.string.no,
                        0,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE);
                break;
            case R.id.btn_getMulti:
                EasyPermissions.requestPermissions(this,
                        "接下來需要獲取WRITE_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE權(quán)限",
                        R.string.yes,
                        R.string.no,
                        1,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE,
                        Manifest.permission.RECORD_AUDIO);
                break;
        }

這個流程比較簡單:


image

基于RxJava的RxPermissions

GitHub地址:https://github.com/tbruyelle/RxPermissions
目前Star數(shù):3.8k

集成方式

在app的build.gradle文件里:

//Rxjava1.x用這個
dependencies {
    compile 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar'
}
//Rxjava2.x用這個
dependencies {
    compile 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.4@aar'
}

如果沒有加入RxJava的還要加入它,我這里使用的是RxJava2,所以加上:

    compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
    compile 'io.reactivex.rxjava2:rxjava:2.1.0'

使用

下面的代碼例子都是使用的RxJava2,和1版本有些不同,不過會用RxJava的基本都能看懂吧。

RxPermissions的使用比較簡單清晰,就在申請權(quán)限的時候使用一個方法,再里面實現(xiàn)邏輯就行了:

  • 申請單個或者多個權(quán)限,不在乎是否不再詢問和哪個權(quán)限申請失敗,只要有一個失敗就執(zhí)行失敗操作:
    //請求權(quán)限
    private void requestRxPermissions(String... permissions) {
        RxPermissions rxPermissions = new RxPermissions(this);
        rxPermissions.request(permissions).subscribe(new Consumer<Boolean>() {
            @Override
            public void accept(@NonNull Boolean granted) throws Exception {
                if (granted){
                    Toast.makeText(RxPermissionsActivity.this, "已獲取權(quán)限", Toast.LENGTH_SHORT).show();
                }else {
                    Toast.makeText(RxPermissionsActivity.this, "已拒絕一個或以上權(quán)限", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }
  • 申請多個權(quán)限,在乎是否不再詢問和哪個權(quán)限申請失敗:
    private void requestEachRxPermission(String... permissions) {
        RxPermissions rxPermissions = new RxPermissions(this);
        rxPermissions.requestEach(permissions).subscribe(new Consumer<Permission>() {
            @Override
            public void accept(@NonNull Permission permission) throws Exception {
                if (permission.granted) {
                    Toast.makeText(RxPermissionsActivity.this, "已獲取權(quán)限"+ permission.name , Toast.LENGTH_SHORT).show();
                } else if (permission.shouldShowRequestPermissionRationale){
                    //拒絕權(quán)限請求
                    Toast.makeText(RxPermissionsActivity.this, "已拒絕權(quán)限"+ permission.name , Toast.LENGTH_SHORT).show();
                } else {
                    // 拒絕權(quán)限請求,并不再詢問
                    // 可以提醒用戶進入設(shè)置界面去設(shè)置權(quán)限
                    Toast.makeText(RxPermissionsActivity.this, "已拒絕權(quán)限"+ permission.name +"并不再詢問", Toast.LENGTH_SHORT).show();
                }
            }
        });

    }

總結(jié)

共同點

三者都簡化了Android6.0申請運行時權(quán)限的流程,比使用Android自帶的API方便很多,可擴展性高。

不同點

功能 PermissionsDispatcher easypermissions RxPermissions
單獨檢查權(quán)限功能
申請權(quán)限前提示操作 有,可以自定義操作,彈出Dialog、Toast、SnackBar等等都行 有,而且定制了Dialog 無,需要自己實現(xiàn)
不再提示時的處理操作 有,而且可以使用Dialog讓用戶選擇跳到APP設(shè)置界面
一次申請多個權(quán)限時,對單個失敗的權(quán)限處理操作
結(jié)合RxJava
不能把方法私有

后話

了解到這些之后,我們應(yīng)該就可以按照自己的需求來選擇用什么樣的動態(tài)權(quán)限請求庫了。

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

相關(guān)閱讀更多精彩內(nèi)容

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