如何利用切面的思想實(shí)現(xiàn)集中式登錄?AspectJ
AspectJ 介紹
AspectJ是一個(gè)面向切面編程的框架,它擴(kuò)展了Java語(yǔ)言答倡。AspectJ定義了AOP語(yǔ)法,它有一個(gè)專門(mén)的編譯器用來(lái)生成遵守Java字節(jié)編碼規(guī)范的Class文件驴党。AspectJ還支持原生的Java,只需要加上AspectJ提供的注解即可获茬。在Android開(kāi)發(fā)中港庄,一般就用它提供的注解和一些簡(jiǎn)單的語(yǔ)法就可以實(shí)現(xiàn)絕大部分功能上的需求了倔既。
Pointcut(切入點(diǎn))
告訴代碼注入工具,在何處注入一段特定代碼的表達(dá)式鹏氧。例如渤涌,在哪些 joint points 應(yīng)用一個(gè)特定的 Advice。切入點(diǎn)可以選擇唯一一個(gè)把还,比如執(zhí)行某一個(gè)方法实蓬,也可以有多個(gè)選擇,比如吊履,標(biāo)記了一個(gè)定義成@DebguTrace 的自定義注解的所有方法安皱。
Advice(通知)
注入到class文件中的代碼。典型的 Advice 類型有 before艇炎、after 和 around酌伊,分別表示在目標(biāo)方法執(zhí)行之前、執(zhí)行后和完全替代目標(biāo)方法執(zhí)行的代碼缀踪。 除了在方法中注入代碼居砖,也可能會(huì)對(duì)代碼做其他修改,比如在一個(gè)class中增加字段或者接口驴娃。
Joint point(連接點(diǎn))
程序中可能作為代碼注入目標(biāo)的特定的點(diǎn)奏候,例如一個(gè)方法調(diào)用或者方法入口。
Android 中使用Gradle集成 AspectJ
在Android中集成AspectJ唇敞,主要思想就是hook Apk打包過(guò)程蔗草,使用AspectJ提供的工具來(lái)編譯.class文件。
1、配置我們項(xiàng)目根目錄中的build.gradle
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
// 版本界限:As-3.0.1 + gradle4.4-all (需要配置r17的NDK環(huán)境)
// 或者:As-3.2.1 + gradle4.6-all (正常使用宝踪,無(wú)警告)
classpath 'org.aspectj:aspectjtools:1.8.9'
classpath 'org.aspectj:aspectjweaver:1.8.9'
}
注意:
版本界限:As-3.0.1 + gradle4.4-all (需要配置r17的NDK環(huán)境)
或者:As-3.2.1 + gradle4.6-all (正常使用婿崭,無(wú)警告)
或者:As-3.4.0 + gradle5.1.1-all (有過(guò)時(shí)的API警告)
2、配置app目錄中的build.gradle
apply plugin: 'com.android.application'
//添加的第一處需要添加的代碼 編譯時(shí)用Aspect專門(mén)的編譯器狠轻,不再使用傳統(tǒng)的javac
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.aspectj:aspectjtools:1.8.9'
classpath 'org.aspectj:aspectjweaver:1.8.9'
}
}
...
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
//添加的第二處需要添加的代碼 引入包
implementation 'org.aspectj:aspectjrt:1.8.13'
}
//添加的第三處處需要添加的代碼 主要做支持
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.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;
}
}
}
}
AspectJ的使用
登錄中我們需要做兩點(diǎn)
- 判斷是否登錄
- 做用戶行為統(tǒng)計(jì)
如下代碼是自定義注解,用于用戶行為統(tǒng)計(jì)和用戶登錄檢測(cè)彬犯。如果想要學(xué)習(xí)自定義注解向楼,大家可以看一下這篇博客Android 自定義注解(Annotation)
package com.aop.login.annotation;
// 用戶點(diǎn)擊痕跡(行為統(tǒng)計(jì))
@Target(ElementType.METHOD) // 目標(biāo)作用在方法之上
@Retention(RetentionPolicy.RUNTIME)//注解不僅被保存到class文件中,jvm加載class文件之后谐区,仍然存在
public @interface ClickBehavior {
String value();
}
package com.aop.login.annotation;
// 用戶登錄檢測(cè)
@Target(ElementType.METHOD) // 目標(biāo)作用在方法之上
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginCheck {
}
@Aspect // 定義切面類
public class LoginCheckAspect {
private final static String TAG = "TAG";
// 1湖蜕、應(yīng)用中用到了哪些注解,放到當(dāng)前的切入點(diǎn)進(jìn)行處理(找到需要處理的切入點(diǎn))
// execution宋列,以方法執(zhí)行時(shí)作為切點(diǎn)昭抒,觸發(fā)Aspect類
// * *(..)) 可以處理ClickBehavior這個(gè)類所有的方法
@Pointcut("execution(@com.aop.login.annotation.LoginCheck * *(..))")
public void methodPointCut() {
}
// 2、對(duì)切入點(diǎn)如何處理
@Around("methodPointCut()")
public Object jointPotin(ProceedingJoinPoint joinPoint) throws Throwable {
Context context = (Context) joinPoint.getThis();
if (true) { // 從SharedPreferences中讀取
Log.e(TAG, "檢測(cè)到已登錄!");
return joinPoint.proceed();
} else {
Log.e(TAG, "檢測(cè)到未登錄灭返!");
Toast.makeText(context, "請(qǐng)先登錄盗迟!", Toast.LENGTH_SHORT).show();
context.startActivity(new Intent(context, LoginActivity.class));
return null; // 不再執(zhí)行方法(切入點(diǎn))
}
}
}
@Aspect // 定義切面類
public class ClickBehaviorAspect {
private final static String TAG = "TAG";
// 1、應(yīng)用中用到了哪些注解熙含,放到當(dāng)前的切入點(diǎn)進(jìn)行處理(找到需要處理的切入點(diǎn))
// execution罚缕,以方法執(zhí)行時(shí)作為切點(diǎn),觸發(fā)Aspect類
// * *(..)) 可以處理ClickBehavior這個(gè)類所有的方法
@Pointcut("execution(@com.aop.login.annotation.ClickBehavior * *(..))")
public void methodPointCut() {}
// 2怎静、對(duì)切入點(diǎn)如何處理
@Around("methodPointCut()")
public Object jointPotin(ProceedingJoinPoint joinPoint) throws Throwable {
// 獲取簽名方法
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
// 獲取方法所屬的類名
String className = methodSignature.getDeclaringType().getSimpleName();
// 獲取方法名
String methodName = methodSignature.getName();
// 獲取方法的注解值(需要統(tǒng)計(jì)的用戶行為)
String funName = methodSignature.getMethod().getAnnotation(ClickBehavior.class).value();
// 統(tǒng)計(jì)方法的執(zhí)行時(shí)間邮弹、統(tǒng)計(jì)用戶點(diǎn)擊某功能行為。(存儲(chǔ)到本地蚓聘,每過(guò)x天上傳到服務(wù)器)
long begin = System.currentTimeMillis();
Log.e(TAG, "ClickBehavior Method Start >>> ");
Object result = joinPoint.proceed(); // MainActivity中切面的方法
long duration = System.currentTimeMillis() - begin;
Log.e(TAG, "ClickBehavior Method End >>> ");
Log.e(TAG, String.format("統(tǒng)計(jì)了:%s功能腌乡,在%s類的%s方法,用時(shí)%d ms",
funName, className, methodName, duration));
return result;
}
}
MainActivity.java
public class MainActivity extends Activity {
private final static String TAG = "TAG";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
// 登錄點(diǎn)擊事件(用戶行為統(tǒng)計(jì))
@ClickBehavior("登錄")
public void login(View view) {
Log.e(TAG, "模擬接口請(qǐng)求……驗(yàn)證通過(guò)或粮,登錄成功导饲!");
}
// 我的專區(qū)點(diǎn)擊事件(用戶行為統(tǒng)計(jì))
@ClickBehavior("我的專區(qū)")
@LoginCheck
public void area(View view) {
Log.e(TAG, "開(kāi)始跳轉(zhuǎn)到 -> 我的專區(qū) Activity");
startActivity(new Intent(this, AreaActivity.class));
}
// 我的優(yōu)惠卷點(diǎn)擊事件(用戶行為統(tǒng)計(jì))
@ClickBehavior("我的優(yōu)惠券")
@LoginCheck
public void coupon(View view) {
Log.e(TAG, "開(kāi)始跳轉(zhuǎn)到 -> 我的優(yōu)惠券 Activity");
startActivity(new Intent(this, CouponActivity.class));
}
// 我的積分點(diǎn)擊事件(用戶行為統(tǒng)計(jì))
@ClickBehavior("我的積分")
@LoginCheck
public void score(View view) {
Log.e(TAG, "開(kāi)始跳轉(zhuǎn)到 -> 我的積分 Activity");
startActivity(new Intent(this, ScoreActivity.class));
}
}