Android 使用AspectJ实现权限申请和埋点上传

一 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. 在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
            }
        }
    }

同步一下就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);
}

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

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

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());
    }

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);
    }
}

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);
}

三 使用AspectJ埋点上传

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

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

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

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) {}

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 ();

2,

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

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);

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");
}

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

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值