前言

Android6.0(API 级别 23)版本的一个重大改动就是增加了运行时权限(动态权限):一些危险的权限不止要在AndroidMainifest文件中声明,还要在运行的时候使用代码来提醒用户去开通,让用户同意才能进行授权,这样做增加了安全性,但是给开发人员带来了麻烦,于是就出现了一些用于简化运行时权限的处理框架,如 PermissionsDispatcher,RxPermissions,easypermissions 等,而我们今天要讲的就是大多数人都在用的PermissionsDispatcher开源库。

GitHub开源地址:https://github.com/hotchemi/PermissionsDispatcher

PermissionsDispatcher简介

PermissionsDispatcher是一个用注解方式来处理Android6.0运行时权限的库,旨在高效处理权限问题。

PermissionsDispatcher使用

1、添加依赖

  • 当你项目中使用的Android Gradle Plugin版本大于2.2时,只需一步引入:

app的gradle文件中添加:

ependencies { 
     compile 'com.github.hotchemi:permissionsdispatcher:3.1.0' 
     annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:3.1.0' 
}
  • 当Android Gradle Plugin版本小于2.2时,需如下两步引入:

project的gradle文件中添加:(当前Gradle Plugin版本2.1.2 < 2.2)

dependencies {
        classpath 'com.android.tools.build:gradle:2.1.2'
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }

app的gradle文件中添加:

apply plugin: 'com.android.application'
apply plugin: 'android-apt'
   
dependencies {
      compile 'com.github.hotchemi:permissionsdispatcher:2.4.0'
      apt 'com.github.hotchemi:permissionsdispatcher-processor:2.4.0'
}

2、在AndroidManifest.xml文件中配置应用所需要的权限

3、添加注解

3.1、@RuntimePermissions注解:必需的注解,它用来注册一个Activity或者Fragment,使他们可以处理权限:

@RuntimePermissions
public class MainActivity extends AppCompatActivity { }

3.2、@NeedsPermission注解:必需的注解,在需要获取权限才能执行的方法上添加注释,用来获取权限:

单个权限的写法

@NeedsPermission(Manifest.permission.CALL_PHONE) 
void siglePermission(){

}

多个权限的写法

@NeedsPermission({Manifest.permission.CALL_PHONE,Manifest.permission.WRITE_EXTERNAL_STORAGE}) 
void mulPermission(){ 

}

3.3、@OnShowRationale注解:这个不是必须的注解,只有当用户第一次选择了禁止并且没有勾选不再询问复选框时才会被后来调用。
调用时机:当第一次用户点击拍照按钮弹出拍照权限申请对话框时,当时用户没有选择允许,而是点击了禁止,那么后面再次点击该拍照按钮后,首先调用该注解标注的方法向用户解释为什么需要该权限,然后用户点击下一步,又重新进入到拍照权限申请对话框页面,如果此时用户继续禁止权限,那么下次再次点击拍照按钮,还是继续先弹出解释对话框,选择下一步之后再弹出权限申请对话框,如此往复,直到用户在权限申请时选择了允许。
括号里面有参数,传入想要申请的权限,而且这个方法还要传入一个PermissionRequest对象,这个对象有两种方法:proceed()让权限请求继续,cancel()让请求中断。也就是说,这个方法会拦截你发出的请求,这个方法用于告诉用户你接下来申请的权限是干嘛的,说服用户给你权限。

@OnShowRationale({Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO})
    //给用户解释要请求什么权限,为什么需要此权限
    void showRationale(final PermissionRequest request) {
        new AlertDialog.Builder(this)
                .setMessage("使用此功能需要WRITE_EXTERNAL_STORAGE和RECORD_AUDIO权限,下一步将继续请求权限")
                .setPositiveButton("下一步", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        request.proceed();//继续执行请求
                    }
                }).setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        request.cancel();//取消执行请求
                    }
                })
                .show();
    }
 
    @OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)
        //给用户解释要请求什么权限,为什么需要此权限
    void showSingleRationale(final PermissionRequest request) {
        new AlertDialog.Builder(this)
                .setMessage("使用此功能需要WRITE_EXTERNAL_STORAGE,下一步将继续请求权限")
                .setPositiveButton("下一步", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        request.proceed();//继续执行请求
                    }
                }).setNegativeButton("取消", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                request.cancel();//取消执行请求
            }
        })
                .show();
    }

3.4、@OnPermissionDenied注解:这个也不是必须的注解,用于标注如果权限请求失败,但是用户没有勾选不再询问的时候执行的方法
调用的时机:当弹出权限申请时,用户选择禁止和在解释为什么需要该权限的对话框点击取消时都会触发。
注解括号里面有参数,传入想要申请的权限。也就是说,我们可以在这个方法做申请权限失败之后的处理,如像用户解释为什么要申请,或者重新申请操作等。

@OnPermissionDenied({Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO})//一旦用户拒绝了
    public void multiDenied() {
        Toast.makeText(this, "已拒绝一个或以上权限", Toast.LENGTH_SHORT).show();
    }
 
 
    @OnPermissionDenied(Manifest.permission.WRITE_EXTERNAL_STORAGE)//一旦用户拒绝了
    public void StorageDenied() {
        Toast.makeText(this, "已拒绝WRITE_EXTERNAL_STORAGE权限", Toast.LENGTH_SHORT).show();
    }

3.5、@OnNeverAskAgain注解:这个也不是必须的注解,用于标注如果权限申请时用户点击禁止并且勾选不再询问的时候执行的方法,
调用时机:当用户在第一次弹出权限申请对话框时选择禁止同时勾选了不再询问复选框时,以后每次点击该按钮,就会先执行该方法中定义的对话框,权限解释对话框已不会再出现。
注解括号里面有参数,传入想要申请的权限。也就是说,我们可以在这个方法做申请权限失败并选择不再询问之后的处理。例如,可以告诉作者想开启权限的就从手机设置里面开启。

@OnNeverAskAgain({Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO})//用户选择的不再询问
    public void multiNeverAsk() {
        Toast.makeText(this, "已拒绝一个或以上权限,并不再询问", Toast.LENGTH_SHORT).show();
    }
    @OnNeverAskAgain(Manifest.permission.WRITE_EXTERNAL_STORAGE)//用户选择的不再询问
    public void StorageNeverAsk() {
        Toast.makeText(this, "已拒绝WRITE_EXTERNAL_STORAGE权限,并不再询问", Toast.LENGTH_SHORT).show();
    }

注意,以上所有被注解的方法都不能是被private修饰的

4、make project自动生成辅助类

自动生成的辅助类的名称为: 被注解的Activity的名称+PermissionsDispatcher

只要我们实现了@RuntimePermissions和@NeedsPermission这两个必须的注解之后,再build一次project之后,编译器就会在在app\build\intermediates\classes\debug目录下与被注解的Activity同一个包下生成一个辅助类,用来调用被注解的Activity的方法(就是因为这个所以被注解的方法不能private,private方法的作用域不在其他的类)

5、重写Activity的onRequestPermissionsResult()方法

重写该方法之后,当弹出授权对话框时,我们点击允许授权成功时,会自动执行注解@NeedsPermission所标注的方法里面的逻辑。

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
      super.onRequestPermissionsResult(requestCode, permissions, grantResults);
      // 执行回调
      MainActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
}

6、最后,申请权限的时候,调用辅助类的方法

当要调用使用权限的方法的时候,不直接调用我们直接添加了@NeedsPermission的方法,而是调用这个辅助类中自动生成的方法,名字从下面可以看出是被@NeedsPermission注解的方法加上WithCheck,参数是这个Activity或者Fragment:

//申请单个权限
MainActivityPermissionsDispatcher.siglePermissionWithCheck(this);
 //申请多个权限
MainActivityPermissionsDispatcher.mulPermissionWithCheck(this);

到此,PermissionsDispatcher的使用步骤就介绍完了。

使用示例:

@RuntimePermissions
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    // 执行该方法需要权限 -- 录音权限
    @NeedsPermission(Manifest.permission.RECORD_AUDIO)
    void startRecord(String msg) {
        Toast.makeText(MainActivity.this, msg+"222222222222222222222", Toast.LENGTH_SHORT).show();
    }

	// 该方法用于开启权限之后自动执行接下来的逻辑
	@Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        SplashActivity3PermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);//将回调交给代理类处理
    }
	
    // 向用户解释为什么需要该权限,,,在第一次弹出权限申请时拒绝之后,再次申请权限时触发
    @OnShowRationale(Manifest.permission.RECORD_AUDIO)
    void showRationaleForRecord(final PermissionRequest request){
        new AlertDialog.Builder(this)
                .setPositiveButton("好的", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // 调出系统申请权限的弹窗会执行@NeedsPermissio对应的方法
                        request.proceed();
                    }
                })
                .setNegativeButton("不给", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // 会执行@OnPermissionDenied对应的方法
                        request.cancel();
                    }
                })
                .setCancelable(false)
                .setMessage("挑战需要录音权限,应用将要申请录音权限")
                .show();
    }

    // 没有赋予权限时执行
    @OnPermissionDenied(Manifest.permission.RECORD_AUDIO)
    void showRecordDenied(){
        Toast.makeText(MainActivity.this, "拒绝录音权限将无法进行挑战", Toast.LENGTH_SHORT).show();
    }

    // 如果在系统申请弹窗中勾选了不在提示并且拒绝,会调用@OnNeverAskAgain的方法
    @OnNeverAskAgain(Manifest.permission.RECORD_AUDIO)
    void onRecordNeverAskAgain() {
        new AlertDialog.Builder(this)
                .setPositiveButton("好的", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // 打开系统设置权限
                        dialog.cancel();
                    }
                })
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.cancel();
                    }
                })
                .setCancelable(false)
                .setMessage("您已经禁止了录音权限,是否现在去开启")
                .show();
    }

    public void order(View view) {
       MainActivityPermissionsDispatcher.startRecordWithCheck(this,"我是通过权限验证之后的!");
    }
}

PermissionsDispatcher插件的使用

觉得这么多注解要自己一个一个弄不够方便,PermissionsDispatcher还在AndroidStudio做了插件,只要在setting设置里的plugins界面里搜索PermissionsDispatcher就可以安装了,安装完重启一下就能使用:

Android如何在申请权限时添加说明 安卓开发 请求权限_安卓项目实战系列


在所需的Activity或者Fragment的代码里面右键,选择Generate,然后就可以选择Generate Runtime Permissions…(生成动态权限的生成)。

Android如何在申请权限时添加说明 安卓开发 请求权限_ide_02


点击Generate Runtime Permissions…出现如下界面,输入方法名字就能生成:

Android如何在申请权限时添加说明 安卓开发 请求权限_Android如何在申请权限时添加说明_03