背景
最近收到了一些運(yùn)營(yíng)需求束莫,需要對(duì)一些數(shù)據(jù)進(jìn)行埋點(diǎn)懊烤,剛剛收到需求的時(shí)候矗愧,只有一個(gè)方案那就是去所有的頁(yè)面添加監(jiān)聽(tīng)灶芝,增加想要添加的業(yè)務(wù)數(shù)據(jù), 那龐大的工作量以及超強(qiáng)的耦合度唉韭,想想都頭大夜涕。不禁的心疼起自己的頭發(fā)。后面知道了原來(lái)可以面向切面編程属愤。接下來(lái)就是我使用aspectj的實(shí)戰(zhàn)女器。
實(shí)戰(zhàn)指項(xiàng)目配置
- 第一步 項(xiàng)目的build.gradle的dependencies中加入
classpath 'org.aspectj:aspectjtools:1.8.9'
classpath 'org.aspectj:aspectjweaver:1.8.9'
- 第二步 在APP的build.gradle的dependencies中引入以下代碼
implementation 'org.aspectj:aspectjrt:1.8.9'
- 第三步在app的build.gradle的android的目錄下加入
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
final def log = project.logger
android.applicationVariants.all { variant ->
// if (!variant.buildType.isDebuggable()) {
// log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
// return
// }// 這段代碼會(huì)導(dǎo)致在release下失效
JavaCompile javaCompile = variant.javaCompiler
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
}
}
}
上面那一段是在網(wǎng)上看別人的文章搬過(guò)來(lái)的,當(dāng)時(shí)有個(gè)bug就是 住诸,正式包切面代碼怎么都注入不成功驾胆,后面才發(fā)現(xiàn)了這段代碼涣澡。注釋掉下面這段代碼之后就成功了。
if (!variant.buildType.isDebuggable()) {
log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
return
}
到這里AOP的配置就完成了 丧诺。接下來(lái)我將貼出我根據(jù)需求所完成的相關(guān)業(yè)務(wù)代碼
1. 統(tǒng)計(jì)頁(yè)面停留時(shí)長(zhǎng)入桂。
這里的統(tǒng)計(jì)使用了極光推送的統(tǒng)計(jì) ,官方文檔就是需要每個(gè)頁(yè)面的開(kāi)始以及結(jié)束的生命周期里加上兩句話 驳阎,我們的項(xiàng)目反正有100多個(gè)頁(yè)面抗愁,如果每個(gè)頁(yè)面都去加的話,那工作量 額... 不好說(shuō)呵晚。我們還是看看切面真么實(shí)現(xiàn)頁(yè)面監(jiān)聽(tīng)吧
實(shí)現(xiàn)代碼
@Aspect
public class TraceAspect {
private static final String TAG = "aop_page_trace";
@After("execution(* android.app.Activity.onResume(..)) )")
public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
Signature signature = joinPoint.getSignature();
System.out.println (TAG + " 切面的點(diǎn)執(zhí)行開(kāi)始 context==" + (Context) joinPoint.getThis()) ;
System.out.println (TAG + "切面的點(diǎn)執(zhí)行開(kāi)始類(lèi)名" + signature.getDeclaringType().getCanonicalName());
JAnalyticsInterface.onPageStart((Context) joinPoint.getThis(), signature.getDeclaringType().getCanonicalName());
}
@After("execution(* android.app.Activity.onPause(..)) ")
public void onActivityMethodDestory(JoinPoint joinPoint) throws Throwable {
Signature signature = joinPoint.getSignature();
JAnalyticsInterface.onPageEnd((Context) joinPoint.getThis(), signature.getDeclaringType().getCanonicalName());
System.out.println (TAG +" 切面的點(diǎn)執(zhí)行銷(xiāo)毀 context==" + (Context) joinPoint.getThis());
System.out.println (TAG +"切面的點(diǎn)執(zhí)行銷(xiāo)毀類(lèi)名" + signature.getDeclaringType().getCanonicalName());
}
execution 一般指定方法的執(zhí)行,在往后因?yàn)闆](méi)有注解和訪問(wèn)權(quán)限的限制蜘腌,所以這里什么也沒(méi)寫(xiě),返回值用代替饵隙,說(shuō)明可以用任何返回值撮珠,android.app.Activity.on代表函數(shù)名稱的全路徑,后面的一個(gè)型號(hào)代表on后面可以接任何東西金矛,后面的(..)代表其參數(shù)可以為任何值芯急。
@After指在jPoint()之后執(zhí)行,我為了不影響之前代碼的執(zhí)行所以一般都用這個(gè)通配符绷柒。
2. 按鈕點(diǎn)擊 或者頁(yè)面跳轉(zhuǎn)的的相關(guān)埋點(diǎn)
我先說(shuō)一下思路志于。 我這里借助到了注解,第一步自定義注解废睦,第二步在需要埋點(diǎn)的地方引用當(dāng)前注解伺绽。第三步在切面檢查引用注解的地方找到切點(diǎn),第四步嗜湃,再切點(diǎn)處加入自己想要的邏輯代碼
自定義注解的代碼如下
/***
點(diǎn)擊的注解
2019-10-30
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface ClickAnnotate {
int type() default 0; //我根據(jù)需求定義了一個(gè)type參數(shù) 進(jìn)行標(biāo)記奈应,以便于找到切點(diǎn)后 進(jìn)行業(yè)務(wù)判斷
監(jiān)聽(tīng)點(diǎn)擊或跳轉(zhuǎn)的切面代碼如下
@Aspect
public class ClickAspect {
private static final String TAG = "aop_click";
private String intention = IntentionAty.class.getCanonicalName();
@Pointcut("execution(@com.aoptest.annotation.ClickAnnotate * *(..))")
public void clickP() {
}
@After("clickP()")
public void insertCodeBlock(JoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = ((MethodSignature) joinPoint.getSignature());
Method method = methodSignature.getMethod();
if (method.isAnnotationPresent(ClickAnnotate.class)) {
ClickAnnotate clickAnnotate = method.getAnnotation(ClickAnnotate.class);
int type = clickAnnotate.type();
switch (type) {
case Constants.AOP_HOME_MENNU://這個(gè)常量是自定義的
String menCode = "";
if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {
if (joinPoint.getArgs()[0] instanceof String) {
menCode = (String) joinPoint.getArgs()[0];
}
}//joinPoint.getArgs() 就是你切入方法的地方的參數(shù) ,一定要注意數(shù)組越界問(wèn)題
if (TextUtils.isEmpty(menCode)) return;
switch (menCode) {
case "MENU_HOME_HYGL":
Log.i(TAG, "insertCodeBlock: 點(diǎn)擊了會(huì)員管理");
break;
case "MENU_HOME_YXDD":
Log.i(TAG, "insertCodeBlock: 點(diǎn)擊了意向訂單");
break;
}
break;
}
}
}
}
到這里购披,切面需要做的事情就完成了杖挣,接下來(lái)就是只需要調(diào)用注解,表明需要切入代碼的地方就行了刚陡。
/**
*
* @param menuCode 菜單對(duì)應(yīng)的編碼
*/
@ClickAnnotate(type = Constants.AOP_HOME_MENNU)//這里的常量和上面的切面對(duì)應(yīng)
private void setMenuJump(String menuCode) {
Class<?> className = null;
if (menuCode.equals("MENU_HOME_YXDD")) {
className = IntentionAty.class;
}
if (className != null) {
startActivity(new Intent(mContext, className));
}
}
根據(jù)以上步驟調(diào)用之后惩妇,切面就會(huì)檢查調(diào)用@ClickAnnotate注解的所有方法,找到切點(diǎn)筐乳,然后就注入相關(guān)代碼歌殃。我這里考慮到復(fù)用,所以在注解里面定義了一個(gè)type用于區(qū)分蝙云。
總結(jié)
在項(xiàng)目中氓皱。如果使用了混淆的話,一定要注意注解不要被混淆了,否則會(huì)導(dǎo)致切面不執(zhí)行的波材。經(jīng)過(guò)這個(gè)需求之后股淡,我發(fā)現(xiàn)AOP真的是好香香喔, AOP 大大降低了代碼的耦合度廷区,挺高了代碼的可維護(hù)性唯灵。關(guān)于AOP相關(guān)注解以及表達(dá)式的寫(xiě)法可以參考 AOP常用的注解以及概念,這里講到了相關(guān)概念隙轻,我就不搬過(guò)來(lái)了早敬。