赞
踩
1.1 Android中使用@AspectJ来编写代码。
它在代码的编译期间扫描目标程序,根据切点(@PointCut)匹配,将开发者编写的Aspect程序编织(Weave)到目标程序的.class文件中,对目标程序作了重构(重构单位是JoinPoint),目的就是建立目标程序与Aspect程序的连接(获得执行的对象、方法、参数等上下文信息),从而达到AOP的目的。
1.2 Gradle 配置
classpath 'org.aspectj:aspectjtools:1.9.1'
classpath 'org.aspectj:aspectjweaver:1.9.1'
implementation 'org.aspectj:aspectjrt:1.9.1'
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了。
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); }
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
以学习和做笔记为目的,如有不对,不足之处请帮忙指出,谢谢
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。