动态权限申请框架

API 23之前的版本都是自动获取权限,而从 Android 6.0 开始添加了权限申请的需求,更加安全。并且目前android系统已经发展到10.0,加上国家对用户隐私的保护,了解这一技术不可避免。google也为我们提供了RxPermission这一框架RxPermission,有兴趣的可以自行了解。本文将采用AspectJ的方式实现。

一、AspectJ集成

1、工程的build.gradle配置如下:
dependencies {
        classpath 'com.android.tools.build:gradle:3.5.2'
     
        classpath 'org.aspectj:aspectjtools:1.8.6'
    }
2、项目的build.gradle配置如下:

(1)添加依赖

implementation 'org.aspectj:aspectjrt:1.8.13'

(2)添加aspect编译相关
下面这一段代码,是在主module下的配置,如果想要在library中使用,可以自行查看如何配置。

import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

final def log = project.logger
final def variants = project.android.applicationVariants
//在构建工程时,执行编织
variants.all { variant ->
    if (!variant.buildType.isDebuggable()) {
        log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
        return;
    }

    JavaCompile javaCompile = variant.javaCompile
    javaCompile.doLast {
        String[] args = ["-showWeaveInfo",
                         "-1.8",
                         "-inpath", javaCompile.destinationDir.toString(),
                         "-aspectpath", javaCompile.classpath.asPath,
                         "-d", javaCompile.destinationDir.toString(),
                         "-classpath", javaCompile.classpath.asPath,
                         "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]
        log.debug "ajc args: " + Arrays.toString(args)

        MessageHandler handler = new MessageHandler(true);
        new Main().run(args, handler);
        for (IMessage message : handler.getMessages(null, true)) {
            switch (message.getKind()) {
                case IMessage.ABORT:
                case IMessage.ERROR:
                case IMessage.FAIL:
                    log.error message.message, message.thrown
                    break;
                case IMessage.WARNING:
                    log.warn message.message, message.thrown
                    break;
                case IMessage.INFO:
                    log.info message.message, message.thrown
                    break;
                case IMessage.DEBUG:
                    log.debug message.message, message.thrown
                    break;
            }
        }
    }
}

二、核心代码

1、创建权限申请注解
/**
 * 
 *     author  : QB
 *     time    : 2019/10/21
 *     version : v1.0.0
 *     desc    : 权限和请求码
 * 
*/
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Permission { /** * 请求权限 */ String[] value(); /** * 请求码 */ int requestCode(); }
2、创建权限申请结果回调接口
/**
 * 
 *     author  : QB
 *     time    : 2019/10/21
 *     version : v1.0.0
 *     desc    : 权限申请结果回调
 * 
*/
public interface PermissionRequestCallback { /** * 申请权限成功 */ void permissionSuccess(); /** * 申请权限失败,用户点击拒绝了 */ void permissionCanceled(); /** * 申请权限失败,用户点击了不再询问 */ void permissionDenied(); }
3、创建透明的activity,进行权限申请

(1)判断权限是否已经申请

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Intent intent = getIntent();

        if (intent != null) {
            String[] permissions = intent.getStringArrayExtra(REQUEST_PERMISSIONS);
            int requestCode = intent.getIntExtra(REQUEST_CODE, REQUEST_CODE_DEFAULT);
            //三者一项不符合,结束当前页面
            if (permissions == null || requestCode == -1 || requestCallback == null) {
                this.finish();
                return;
            }
            //判断是否已经授权了
            if(PermissionUtils.hasPermissionRequest(this,permissions)){
                requestCallback.permissionSuccess();
                this.finish();
                return;
            }
            //开始申请权限
            ActivityCompat.requestPermissions(this,permissions,requestCode);
        }
    }

判断权限是否已经申请,并将结果进行回调。如果没有申请,开始申请权限。

(2)权限申请系统回调

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

        //再次判断是否权限真正的申请成功
        if(PermissionUtils.requestPermissionSuccess(grantResults)){
            requestCallback.permissionSuccess();
            this.finish();
            return;
        }

        //权限拒绝,并且用户勾选了不在提示
        if(PermissionUtils.shouldShowRequestPermissionRationale(this,permissions)){
            requestCallback.permissionCanceled();
            this.finish();
            return;
        }

        //权限拒绝
        requestCallback.permissionDenied();
        this.finish();
    }

回调权限申请结果。

4、AspectJ拦截Permission注解的方法

核心代码:

	//启动透明的activity
        DialogActivity.launchDialog(context, permission.value(), permission.requestCode(), new PermissionRequestCallback() {
            @Override
            public void permissionSuccess() {

                //代码继续执行下去
                try {
                    joinPoint.proceed();
                } catch (Throwable throwable) {
                    throwable.printStackTrace();
                }
            }

            @Override
            public void permissionCanceled() {
                //反射执行permissionCanceled()方法
                PermissionUtils.invokeAnnotation(object,PermissionFailed.class);
            }

            @Override
            public void permissionDenied() {
                //反射执行permissionDenied()方法
                PermissionUtils.invokeAnnotation(object,PermissionDenied.class);

                //跳转到系统设置界面
                PermissionUtils.startAndroidSettings(finalContext);
            }
        });

首先拿到当前activity的上下文,然后用它来启动这个权限申请的透明的activity。得到申请的回调结果。三种情况:

  • 申请成功:程序继续执行 joinPoint.proceed();
  • 申请被拒绝:反射执行被PermissionFailed注解的方法
  • 申请被决绝,并且勾选不再询问:反射执行被PermissionDenied注解的方法,并跳转到应用设置界面。
5、PermissionFailed和PermissionDenied注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionFailed {
    /**
     * 请求码
     */
    int requestCode();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionDenied {
    /**
     * 请求码
     */
    int requestCode();
}
6、反射
public static void invokeAnnotation(Object object, Class annotationClass) {
        // 获取 object 的 Class对象
        Class<?> objectClass = object.getClass();

        // 遍历 所有的方法
        Method[] methods = objectClass.getDeclaredMethods();
        for (Method method : methods) {
            method.setAccessible(true); // 让虚拟机,不要去检测 private

            // 判断方法 是否有被 annotationClass注解的方法
            boolean annotationPresent = method.isAnnotationPresent(annotationClass);

            if (annotationPresent) {
                // 当前方法 代表包含了 annotationClass注解的
                try {
                    method.invoke(object);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }
    }
7、跳转到设置界面

由于不同的手机厂商,跳转到设置界面的代码是不一样的,所以,这里要做适配。适配采用依赖倒置原则。所谓的依赖倒置是面向过程,不面向细节的。
(1)创建依赖接口

public interface ISetting {
    Intent getStartSettingsIntent(Context context);
}

(2)针对不同手机进行实现
动态权限申请框架_第1张图片
(3)跳转实现

public static void startAndroidSettings(Context context) {

        // 拿到当前手机品牌制造商,来获取 具体细节
        Class aClass = permissionMenu.get(Build.MANUFACTURER.toLowerCase());

        if (aClass == null) {
            aClass = permissionMenu.get(MANUFACTURER_DEFAULT);
        }

        try {
            Object newInstance = aClass.newInstance(); // new OPPOStartSettings()

            ISetting iMenu = (ISetting) newInstance; // ISetting iMenu = (ISetting) oPPOStartSettings;

            // 高层 面向抽象,而不是具体细节
            Intent startActivityIntent = iMenu.getStartSettingsIntent(context);

            if (startActivityIntent != null) {
                context.startActivity(startActivityIntent);
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }

三、使用

在目标activity中添加如下代码。

@Permission(value = "android.permission.READ_EXTERNAL_STORAGE", requestCode = 1)
    private void requestPermission() {
        Toast.makeText(this, "权限申请成功", Toast.LENGTH_SHORT).show();
    }

    @PermissionFailed(requestCode = 1)
    private void requestPermissionFailed() {
        Toast.makeText(this, "权限申请失败", Toast.LENGTH_SHORT).show();
    }

    @PermissionDenied(requestCode = 1)
    private void requestPermissionDenied() {
        Toast.makeText(this, "权限申请失败,不再询问", Toast.LENGTH_SHORT).show();
    }

四、效果

动态权限申请框架_第2张图片
代码传送门源码下载。

你可能感兴趣的:(android架构师)