本文出處:
炎之鎧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主頁的介紹:

-
@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)限了:

@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;
}
這個流程比較簡單:

基于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)限請求庫了。

