当前位置:   article > 正文

Android 使用AspectJ实现权限申请和埋点上传_androdid中使用aspect实现权限结果

androdid中使用aspect实现权限结果

一 Android 使用AspectJ

1.1 Android中使用@AspectJ来编写代码。
它在代码的编译期间扫描目标程序,根据切点(@PointCut)匹配,将开发者编写的Aspect程序编织(Weave)到目标程序的.class文件中,对目标程序作了重构(重构单位是JoinPoint),目的就是建立目标程序与Aspect程序的连接(获得执行的对象、方法、参数等上下文信息),从而达到AOP的目的。

1.2 Gradle 配置

  1. 1.在工程的 build.gradle中配置
classpath 'org.aspectj:aspectjtools:1.9.1'
classpath 'org.aspectj:aspectjweaver:1.9.1'
  • 1
  1. 在APP的 build.gradle中配置
implementation 'org.aspectj:aspectjrt:1.9.1'
    1. 在APP的 build.gradle中 所有{}外和dependencies {}平级的地方写入
    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.9",
                             "-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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    同步一下就ok了。

    二 使用AspectJ写一个权限申请框架

    2.1. 首先写一个注解类,这个注解类是为了标记在一个需要申请权限的方法上,通过注解找到这个方法的切点@PointCut,获取到这个切入点之后再根据@Around获取到这个切入的方法,可以通过这个切入的方法,在这个方法的执行前后做一下其它的操作。

    2.2. 比如我们需要去SD卡读取文件,我们通过一个方法去读取文件信息,我们在这个方法上面添加我们这个权限的注解就可以去申请权限了。

    /**
     * 权限申请
     * @param 
     */
    @Permission(value = Manifest.permission.READ_EXTERNAL_STORAGE,requestCode = 1)
    public void getSdcard() {
        Log.d("test----","打印了");
    }
    
    
    /**
     * 用户拒绝权限申请的回调方法
     * @param
     */
    @PermissionCancle(requstCode = 1)
    private void requestPermissionFailed() {
        Toast.makeText(this, "用户拒绝了权限", Toast.LENGTH_SHORT).show();
    }
    
    
    /**
     * 权限申请失败的回调方法
     * @param
     */
    @PermissionDenied(requstCode = 1)
    private void requestPermissionDenied() {
        Toast.makeText(this, "权限申请失败,不再询问", Toast.LENGTH_SHORT).show();
        PermissionUtil.startAndroidSettings(this);
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    2.3. 新建一个Permission注解类,和一个权限取消和权限申请失败的的注解
    这两个注解分别标记相应执行的在这里插入代码片回调方法

    @Target(ElementType.METHOD)   //作用域为方法
    @Retention(RetentionPolicy.RUNTIME)   //生命周期是运行时
    public @interface Permission {
        String [] value();   //权限值
        int requestCode();   
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface PermissionCancle {
        int requstCode();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface PermissionDenied {
        int requstCode();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.4. 创建一个@Aspect 标注的织入类通过扫描权限的注解得到切点,再通过切点拿到方法,在这个方法前注入权限申请的代码。通过 @Pointcut这个注解可以声明一个切入点;“execution(@Permission * *(…))&& @annotation(permission)” 是一个通配符语法,表示匹配程序中所有被@Permission注解标记的并且传递了一个permission参数的方法。得到这个方法后可通过@Before(“getPermissin(permission)”) 或者@After(“getPermissin(permission)”)对这个方法前后织入一些代码逻辑,比如方法得执行时间差。这里通过@Around(“getPermissin(permission)”)表示替换这个方法并且执行这个方法。在读写操作前替换成这个方法去执行权限申请得操作。

    @Aspect
    public class PermissionAspectJ {
        /**
         * 声明切入点
         * @param permission  注解中的参数
         */
        @Pointcut("execution(@Permission * *(..))&& @annotation(permission)")
        public void getPermissin(Permission permission){}
    
        /**
         * 获取切入的方法
         * @param point
         * @param permission
         * @throws Throwable
         */
        @Around("getPermissin(permission)")
        public void getPointMethod(final ProceedingJoinPoint point, Permission permission) throws Throwable {
            Context context = null;
            //获取上下文对象
            final Object thisContext=point.getThis();
    
    
            if(thisContext instanceof Context){
                context= (Context) thisContext;
            }else if(thisContext instanceof Fragment){
                context=((Fragment) thisContext).getActivity();
            }
            //判断权限和上下文 是否为null
            if(context==null ||permission==null ||permission.value().length<=0){
                return;
            }
            //获取权限数据
            String [] permissinValue=permission.value();
            final int requstCode=permission.requestCode();
            PermissionUtil.launchActivity(context, permissinValue, requstCode, new PermissionRequstCallback() {
                @Override
                public void permissionSuccess() {
                    //权限申请成功 执行切入的方法
                    try {
                        point.proceed();
                    } catch (Throwable throwable) {
                        throwable.printStackTrace();
                    }
                }
    
                @Override
                public void permissionCancle() {
                   PermissionUtil.invokeAnnotation(thisContext, PermissionCancle.class,requstCode);
                }
    
                @Override
                public void permissionDenied() {
                    PermissionUtil.invokeAnnotation(thisContext, PermissionDenied.class,requstCode);
                }
            });
            Log.d("test--permission--"," "+  point.getThis().getClass().getCanonicalName());
            Log.d("test--permission--"," "+  point.getThis().getClass().getName());
        }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    2.5 由于权限申请有一个onRequestPermissionsResult 回调方法去根据相应的信息处理UI,植入类是不能去回调这个方法,必须是在Activity里面才能回调,所以需要创建一个透明的Activity去处理回调。织入类跳转到透明Activity并且把权限值和响应码传入并传递一个回调接口,透明Activity中onRequestPermissionsResult 执行后执行回调接口,织入类回调方法再通过反射原理执行权限申请的返回值方法。

    package com.fishman.zxy.maspectj.permission.activity;
    
    import android.content.Intent;
    import android.os.Bundle;
    
    import androidx.annotation.NonNull;
    import androidx.annotation.Nullable;
    import androidx.appcompat.app.AppCompatActivity;
    import androidx.core.app.ActivityCompat;
    
    
    import com.fishman.zxy.maspectj.permission.utils.PermissionUtil;
    
    public class TransparentActivity extends AppCompatActivity {
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            initView();
            initDada();
        }
    
    
    
        private void initView() {
        }
        private void initDada() {
            Intent intent=getIntent();
            if(intent!=null){
                String [] value=intent.getStringArrayExtra(PermissionUtil.REQUEST_PERMISSIONS);
                int requstCode=intent.getIntExtra(PermissionUtil.REQUEST_CODE,PermissionUtil.REQUEST_CODE_DEFAULT);
                if(value==null||value.length<=0||requstCode==-1||PermissionUtil.permissionRequstCallback==null){
                    finish();
                    return;
                }
                //判断是否授权
                if(PermissionUtil.hasPermissionRequest(this,value)){
                    PermissionUtil.permissionRequstCallback.permissionSuccess();
                    finish();
                    return;
                }
                //去申请权限
                ActivityCompat.requestPermissions(this,value,requstCode);
            }
    
    
        }
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            //权限申请成功
             if(PermissionUtil.requestPermissionSuccess(grantResults)){
                 PermissionUtil.permissionRequstCallback.permissionSuccess();
                 finish();
                 return;
             }
             //权限拒绝,不在提示
             if(PermissionUtil.shouldShowRequestPermissionRationale(this,permissions)){
                 PermissionUtil.permissionRequstCallback.permissionDenied();
                 finish();
                 return;
             }
             //权限拒绝
             PermissionUtil.permissionRequstCallback.permissionCancle();
             finish();
    
    
        }
    
        @Override
        public void finish() {
            super.finish();
            overridePendingTransition(0,0);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    2.6 PermissionActivity 处理通过反射的回调方法,进行UI的更新和数据的交互

    /**
     * 用户拒绝权限申请的回调方法
     * @param
     */
    @PermissionCancle(requstCode = 1)
    private void requestPermissionFailed() {
        Toast.makeText(this, "用户拒绝了权限", Toast.LENGTH_SHORT).show();
    }
    
    
    /**
     1. 权限申请失败的回调方法
     2. @param
     */
    @PermissionDenied(requstCode = 1)
    private void requestPermissionDenied() {
        Toast.makeText(this, "权限申请失败,不再询问", Toast.LENGTH_SHORT).show();
        PermissionUtil.startAndroidSettings(this);
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    三 使用AspectJ埋点上传

    3.1
    埋点上传
    埋点上传时为了统计和分析数据,对用户的行为事件进行埋点布置,对这些数据进行分析,进一步优化产品和指导运营。

    3.2
    埋点上传的三种方式
    传统埋点:开发者直接在客户端埋点
    可视化埋点:首先埋点服务平台与埋点客户机做关联, 包括客户机包含的埋点模块扫描当前整个客户端页面的控件,形成控件树,并将当前页面截图,发送给埋点服务端平台。然后服务器通过发送页面位置来进行控件埋点
    无埋点:所谓的无埋点,其实也就是全埋点, 实现原理也很简单, 客户端添加扫描代码, 为每个扫描到的控件添加监听事件。 当事件被触发后,记录日志。
    3.3
    通过AspectJ搭建一个无埋点的埋点上传。埋点有各种类型,比如:点击事件,功能事件,异常事件,页面事件。所以首先我们需要定义一个注解的基类,通过这个注解可以表示每个注解的行为类型,根据不同的类型进行分类上传或者统计。再定义一个上传用户行为的注解,通过这个注解标记可以上传被标记的方法所做的行为参数等。

    /**
     * 用来标记每个注解的作用类型
     */
    @Target (ElementType.ANNOTATION_TYPE)   //作用域是注解上
    @Retention (RetentionPolicy.RUNTIME)
    public @interface AnnotationBase {
        // 类型
        String type();
        //类型对应的ID
        String actionId();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    **
     * 标记,上传用户行为的统计的注解
     */
    @Target (ElementType.METHOD)     //作用域是方法上
    @Retention (RetentionPolicy.RUNTIME)
    @AnnotationBase (type = "EVENT",actionId = "1001")  //每个行为的类型和ID
    public @interface UploadPointData {
       String url() default "http://xxx.com/uplodPoint";
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3.4 和权限申请一样还是需要新建一个AspectJ织入类

    /**
     * 这个类将会被aspectj编译
     */
    @Aspect
    public class UploadAspectj {
        //切点,切入程序中所有被这个注解标记的方法
        @Pointcut("execution (@com.fishman.zxy.maspectj.uplodPoint.annotation.UploadPointData * *(..))")
        public void uplodPoint() {
        }
    
        //把方法切下来
        @Around("uplodPoint()")
        public void executionUploadPoint(ProceedingJoinPoint point) {}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3.5 在executionUploadPoint方法中做相应的处理,第一步需要得到这个行为统计的类型type 和actionId ,第二步需要拿到这个行为的参数,第三步,得到行为的参数和行为的类型和ID就可以通过http上传到服务断进行统计

    1,

    //获取到方法的反射对象
    MethodSignature signature = (MethodSignature) point.getSignature ();
    Method method = signature.getMethod ();
    UploadPointData annotation1 = method.getAnnotation (UploadPointData.class);
    String url =annotation1.url ();
    //获取到方法的所有注解
    Annotation[] annotations = method.getAnnotations ();
    //获取到方法的所有接收的参数注解
    Annotation[] parameAnnotations=getMethodParameAnnotations(method);
    if(annotations==null||annotations.length<=0){
        //执行原有的方法
        backProceed(point);
    }
    //创建一个AnnotationBase
    AnnotationBase annotationBase=null;
    //遍历这个方法的所有注解‘
    for (Annotation annotation:annotations) {
        //获取到类型
        Class<? extends Annotation> annotationType = annotation.annotationType ();
        //获取到这个方法上的注解上的注解
        annotationBase=annotationType.getAnnotation (AnnotationBase.class);
        if(annotationBase==null){
         break;
        }
    }
    if(annotationBase==null){
        //执行原有的方法
        backProceed(point);
    }
    //获取注解得类型和id
    String type = annotationBase.type ();
    String actionId = annotationBase.actionId ();
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    2,

    //获取方法得参数值
    Object[] args = point.getArgs ();
    //获取key对应得参数值得json数据
    JSONObject jsonObject=getData(parameAnnotations,args);
    • 1
    • 2
    • 3

    3,

    //把收集起来的数据上传到服务器
    String msg = "上传埋点: " + "type: " + type + "  actionId:  " + actionId + "  data: " + jsonObject.toString();
    Log.d("-------------->",msg);
    PointBody pointBody=new PointBody ();
    pointBody.setType (type);
    pointBody.setActionId (actionId);
    pointBody.setData (jsonObject.toString());
    sendData(url,pointBody);
    //执行原有的方法
    backProceed(point);
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3.6  通过注解的方式标记需要上传的行为,可以得到行为的方法和类型进行上传 不影响相信的业务逻辑。

    @UploadPointData
    private void setUserToUi(@ParamesAnnotation (key="name") String userId,@ParamesAnnotation (key="pwd") String password) {
        bt_Ui.setText ("name=  "+userId+"    pwd="+password);
    }
    
    public void setUi(View view) {
        setUserToUi("userId","password");
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    demo 链接 https://github.com/zhouxingyi/MAspectj.git
    以学习和做笔记为目的,如有不对,不足之处请帮忙指出,谢谢

    声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/266740
    推荐阅读
    相关标签
      

    闽ICP备14008679号