• Android9.0动态运行时权限源码分析及封装改造<四>打造自己的权限申请框架下


    跟离上一次https://www.cnblogs.com/webor2006/p/13344912.html的权限申请框架学习又过去一年多了,还差这篇的收尾,这次把它给完结了,不能半途而废。

    编译时自动生成文件:

    生成代码编写:

    我们已经把所有标有注解的方法都收集起来了,接下来则可以根据咱们收集的这些信息来编写代码生成的逻辑,回到这块:

    要想生成代码,需要使用到这个对象:

    然后接下来则遍历咱们收集的方法集合来进行类的生成,如下:

    其中MethodInfo中有几个私有方法需要将其公开化:

    编译看效果:

    annotationProcessor注册:

    此时需要回到app module中,这样注册一下:

    编译:

    接下来编译看一下会不会报错,报错了,而且是一个经典的错:

    com.android.tools.r8.a: Invoke-customs are only supported starting with Android O (--min-api 26)

    解决起来也很简单,在gradle中配置一个JDK版本为8既可,如下:

    再次编译,发现完全木有生成。。其实是这里的依赖有点问题,更改一下:

    再次编译,类成功生成:

    ,不错还是报了个错:

    呃,生成的类怎么首字母多了一个“$”呢? 其实是这块写得有问题:

    这里这样修改就可以了:

    然后还发现生成方法时的代码写得有点问题,顺便也给改了:

    再编译,文件生成正常了:

    很明显少了一个参数了,所以又报错了。。

    解决也很简单,把接口定义改一下:

    然后咱们在生成代码处也得对应的修改一下:

    再编译,就正常啦:

    abstractProcessor远程断点调试:

    在继续往下进行代码编写之前,这里先暂停学习一个开发技巧,我们也知道在编写AnnotationProcessor如果出现问题是很难定位错误的对吧,其实它也是有调试技巧的,所以接下来看一下如何进行它的代码调试。

    1、gradle.properties文件下增加:

    org.gradle.daemon=true
    org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5105

    2、项目的teminal中运行gradle --deamon开启守护进程:

    (base) xiongweideMacBook-Pro:permissionstudy xiongwei$ ./gradlew --daemon
    Starting a Gradle Daemon, 4 incompatible Daemons could not be reused, use --status for details
    
    > Task :help
    
    Welcome to Gradle 6.1.1.
    
    To run a build, run gradlew <task> ...
    
    To see a list of available tasks, run gradlew tasks
    
    To see a list of command-line options, run gradlew --help
    
    To see more detail about a task, run gradlew help --task <task>
    
    For troubleshooting, visit https://help.gradle.org
    
    BUILD SUCCESSFUL in 10s
    1 actionable task: 1 executed

    我本机gradle的版本为:

     

    3、配置调试程序:

     

    点击ok之后,在运行这块需要切换至此:

     

    4、点击调试启动按钮,然后在项目的terminal中运行gradle clean assembleDebug运行项目:

    然后打几个断点:

     

    然后再执行一下它:

    此时看一下效果:

    成功debug了,这样对于这块的问题排查就有一个非常好的方式了。

    权限请求类库封装:

    目前注解处理类的逻辑已经处理好了,接下来则需要封装咱们自己的权限请求类来调用它们了。

    1、将libpermissionhelper改为module:

    目前它是一个java library:

    为啥要改呢?因为它里面需要用到Android的一个类,比如Activity,Frgment,如下:

    2、新建文件:

    package com.permissionstudy.libpermissionhelper;
    
    import android.app.Activity;
    
    import androidx.fragment.app.Fragment;
    
    public class PermissionHelper {
        public static void requestPermission(Activity activity, String[] permission, int requestCode) {
            doRequestPermission(activity, permission, requestCode);
        }
    
        public static void requestPermission(Fragment fragment, String[] permission, int requestCode) {
            doRequestPermission(fragment.getActivity(), permission, requestCode);
        }
    
        private static void doRequestPermission(Activity activity, String[] permission, int requestCode) {
            //TODO
        }
    }

    3、6.0以下系统直接通过:

    我们知道,Android的动态权限是在6.0以后才提出的,所以对于6.0及以下的默认都是全通过的,所以先来处理这个条件:

    那它里面具体逻辑呢?其实就是应该找到注解处理器扫描自动生成的这个类:

    代码也比较容易,直接贴出:

    package com.permissionstudy.libpermissionhelper;
    
    import android.app.Activity;
    import android.os.Build;
    
    import androidx.fragment.app.Fragment;
    
    public class PermissionHelper {
        private static final String SUFFIX = "$$PermissionProxy";
    
        public static void requestPermission(Activity activity, String[] permission, int requestCode) {
            doRequestPermission(activity, permission, requestCode);
        }
    
        public static void requestPermission(Fragment fragment, String[] permission, int requestCode) {
            doRequestPermission(fragment.getActivity(), permission, requestCode);
        }
    
        private static void doRequestPermission(Activity activity, String[] permission, int requestCode) {
            if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
                //6.0及以下系统直接通过
                doExecuteGrant(activity, requestCode, permission);
                return;
            }
        }
    
        //授权成功
        private static void doExecuteGrant(Activity activity, int requestCode, String[] permission) {
            PermissionProxy proxy = findProxy(activity);
            proxy.granted(requestCode, activity, permission);
        }
    
        //找到Activity注解处理器生成的类
        private static PermissionProxy findProxy(Activity activity) {
            Class<? extends Activity> aClass = activity.getClass();
            try {
                Class<?> forName = Class.forName(aClass.getName() + SUFFIX);
                PermissionProxy proxy = (PermissionProxy) forName.newInstance();
                return proxy;
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
            return null;
        }
    }

    4、处理6.0以后系统权限:

    不需要弹窗提示:

    接下来则来处理6.0以后系统的权限逻辑了,当然是需要主动申请才行,而申请时可能想弹一个说明框,所以先做一个是否要弹框说明的逻辑:

    需要弹窗提示:

    首先对用户已经拒绝的权限进行一个弹窗,所以需要从用户申请权限中先要过滤出已经被用户拒绝的权限,如下:

    那当有需要解释的拒绝权限,那怎么弹窗呢?其实是想调用到Activity的这个方法:

    所以注解处理器生成的代理类就发挥作用了,如下:

    也就是会调到这:

    另外,此提示弹窗显示之后,当用户点击“好的”按钮时,应该再次发起被拒权限的申请,所以现在的问题Activity的PermissionHelper类怎么进行一个交互,很显然用接口回调的方式,新建一个接口:

    然后PermissionProxy.rational()接口中就得增加一个这个回调参数了:

     

    它一动,一系列的连锁反应也就来了,首先是Activity:

    然后,在注解代码生成器中也得变一下:

    然后再PermissionHelper的调用处就可以这样写了:

    这里重新编译一下,确保代理类没问题:

    真正发起权限申请:

    逻辑也比较简单:

    运行: 

    一切就绪,接下来咱们调用一下咱们的封装类,来测度一下权限申请的效果:

    注意,记得对应的在Manifest.xml中也声明一下,不然你要申请的权限是不会真正进行申请的:

    当权限被拒绝时,咱们应该也给用户一个提示,如下:

    而当权限被用户拒绝之后,则需要给出一个已拒绝权限的列表:

    另外,在这个回调中,也得把逻辑让咱们的PermissionHelper来处理了:

    其处理逻辑为:

    其中这里面增加了一个方法:

    好,接下来咱们来运行看一下效果,发现没任何效果。。原因是有个笔误:

    修改一下:

    那,再运行,发现还是不对。。

    我全新安装的APP,而且机型是大于6.0的系统,咋就直接提示申请成功了。。有问题,这时因为咱们的PermissionHelper有几个代码还是写得有问题,下面先来调整一下:

    对于需要弹框的权限和真正发起权限申请的权限是需要区分开的:

    好,下面再运行看一下:

    嗯,木问题,弹出了我们测试时的三个权限的申请框了,接下来分几种场景来测一下,看有没有其它的bug:

    1、允许一个权限,拒绝剩下的二个权限:

     

    这里看出一个问题了,我明显是允许了写入sdcard的权限,拒绝了相机和手机状态读取的权限,为啥在权限授权提示中,貌似反了:

    但是,如果我退出APP再进来,再提示就对了:

    那接下来解决我们在授权时的结果提示的这个问题,经过debug,发现原因了:

    其原因就是这个判断条件出问题了:

    改为它:

    此时重新来一次,app卸载安装一下:

    嗯,修复了,不过还有一个细节,这里再调整一下,就是对于申请成功的权限,我们也把哪些申请成功的权限打出来,目前只提示了一个“申请成功”:

    再运行:

    2、再打开app,看拒绝的权限是否会再弹框提示?

    那,此时我再退出app,重新打开app,那之前咱们拒绝的权限还会再次被弹窗提示出来么?试一下:

    木问题。

    3、把剩下的所有权限都允许:

    接下来这一步又出问题了,点击“好的”,发现死循环了:

    原因也是由于笔误,改一下:

    应该是改成它:

    好,再次运行看一下:

    貌似完美了,最后再整体走一遍流程:

    总结:

    至此,整个权限申请框架就已经编写完成了,回忆一下编译时的流程:

     

    而运行时的流程为:

    最后把PermissionHelper类的整个源码贴出来供参考:

    package com.permissionstudy.libpermissionhelper;
    
    import android.app.Activity;
    import android.content.pm.PackageManager;
    import android.os.Build;
    
    import androidx.core.app.ActivityCompat;
    import androidx.fragment.app.Fragment;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class PermissionHelper {
        private static final String SUFFIX = "$$PermissionProxy";
    
        public static void requestPermission(Activity activity, String[] permission, int requestCode) {
            doRequestPermission(activity, permission, requestCode);
        }
    
        public static void requestPermission(Fragment fragment, String[] permission, int requestCode) {
            doRequestPermission(fragment.getActivity(), permission, requestCode);
        }
    
        private static void doRequestPermission(Activity activity, String[] permission, int requestCode) {
            if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
                //6.0及以下系统直接通过
                doExecuteGrant(activity, requestCode, permission);
                return;
            }
            boolean rational = shouldShowPermissionRational(activity, requestCode, permission);
            if (rational) {
                //如果需要弹窗,则下面的真正发起权限申请的逻辑则不应该执行
                return;
            }
            doRealRequestPermission(activity, permission, requestCode);
        }
    
        //真正发起权限申请
        private static void doRealRequestPermission(Activity activity, String[] permission, int requestCode) {
            List<String> deniedPermissions = findDeniedPermissions(activity, permission);
            if (deniedPermissions.size() > 0) {
                String[] denied = new String[deniedPermissions.size()];
                deniedPermissions.toArray(denied);
                ActivityCompat.requestPermissions(activity, denied, requestCode);
            } else {
                //如果没有需要授权的权限,则就代表所有权限都申请成功了
                doExecuteGrant(activity, requestCode, permission);
            }
        }
    
        private static boolean shouldShowPermissionRational(final Activity activity, final int requestCode, final String[] permission) {
            PermissionProxy proxy = findProxy(activity);
            //需要弹框说明:将用户已经拒绝的权限给出提示
            List<String> shouldShowRationalePermissions = findShouldShowRationalePermissions(activity, permission);
            if (!shouldShowRationalePermissions.isEmpty()) {
                //调用到Activity中被@PermissionRational注解修饰的方法
                String[] denied = new String[shouldShowRationalePermissions.size()];
                shouldShowRationalePermissions.toArray(denied);
                return proxy.rational(requestCode, activity, denied, new PermissionRationalCallback() {
                    @Override
                    public void onRationalExecute() {
                        //重新发起被用户拒绝了的权限申请
                        doRealRequestPermission(activity, permission, requestCode);
                    }
                });
            }
            return false;
        }
    
        //从用户申请的权限中过滤出需要弹窗提示的权限
        private static List<String> findShouldShowRationalePermissions(Activity activity, String[] permissions) {
            List<String> rational = new ArrayList<>();
            for (String permission : permissions) {
                if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
                    //系统判断一下看是否该权限需要解释,需要解释的才放到集合中
                    rational.add(permission);
                }
            }
            return rational;
        }
    
        //从用户申请的权限中过滤出已经拒绝了的
        private static List<String> findDeniedPermissions(Activity activity, String[] permissions) {
            List<String> deniedPermissions = new ArrayList<>();
            for (String permission : permissions) {
                if (ActivityCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) {
                    //系统判断一下看是否该权限需要解释,需要解释的才放到集合中
                    deniedPermissions.add(permission);
                }
            }
            return deniedPermissions;
        }
    
        //授权成功
        private static void doExecuteGrant(Activity activity, int requestCode, String[] permission) {
            PermissionProxy proxy = findProxy(activity);
            proxy.granted(requestCode, activity, permission);
        }
    
        //授权失败
        private static void doExecuteDenied(Activity activity, int requestCode, String[] permission) {
            PermissionProxy proxy = findProxy(activity);
            proxy.denied(requestCode, activity, permission);
        }
    
        //找到Activity注解处理器生成的类
        private static PermissionProxy findProxy(Activity activity) {
            Class<? extends Activity> aClass = activity.getClass();
            try {
                Class<?> forName = Class.forName(aClass.getName() + SUFFIX);
                PermissionProxy proxy = (PermissionProxy) forName.newInstance();
                return proxy;
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        //权限结果回调
        public static void onRequestPermissionsResult(Activity activity, int requestCode, String[] permissions, int[] grantResults) {
            List<String> grantPermissions = new ArrayList<>();
            List<String> deniedPermissions = new ArrayList<>();
            for (int i = 0; i < grantResults.length; i++) {
                String permission = permissions[i];
                if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
                    deniedPermissions.add(permission);
                } else {
                    grantPermissions.add(permission);
                }
            }
            if (grantPermissions.size() > 0) {
                String[] grant = new String[grantPermissions.size()];
                grantPermissions.toArray(grant);
                doExecuteGrant(activity, requestCode, grant);
            }
            if (deniedPermissions.size() > 0) {
                String[] denied = new String[deniedPermissions.size()];
                deniedPermissions.toArray(denied);
                doExecuteDenied(activity, requestCode, denied);
            }
        }
    }
  • 相关阅读:
    shell习题第12题:批量创建用户
    shell习题第11题:输入数字执行命令
    二、python数据类型、字符编码、文件处理
    C语言之控制语言:分支和跳转
    c语言之控制语句:循环
    C语言之运算符、表达式和语句
    Python集合及其运算
    Python文本处理
    Python迭代器与格式化
    Python装饰器
  • 原文地址:https://www.cnblogs.com/webor2006/p/13569834.html
Copyright © 2020-2023  润新知