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