文章目錄
一 、開始之前:
集中式登陸架構設計,此登陸並非登陸請求,而是判斷當前是否登陸狀態,如不是登陸狀態,跳轉登陸界面;否則進行對應的跳轉邏輯 。該業務是一個全局業務,全局業務抽取到一個切面。
如果是OOP思想編程的話,採用SharedPreferenced
來保存isLogined
登陸狀態,如果isLogined == true
跳轉到我的積分、我的專區等等。否則跳轉到LoginActivity
。
二 、實現方式一 :運行時動態代理的方式實現
採用運行時動態代理的方式實現,具體查看代碼CentralizedLoginArchitecture
重點介紹方式二:
三、實現方式二:AspectJ
1. 舉例,有如下需求:
類似需求如:用戶的行爲統計分析,也是一個全局業務。也是可以通過AOP切面的思想來完成。
言歸正傳,開始AspectJ,AspectJ
是面向切面編程的一個框架,拓展了Java的語言,並且定義實現AOP的語法。
2. 名詞解釋:
切入點,PointCut
通知,Advice
連接點,Joint Point, Before After Around
3. 集成AspectJ
集成AspectJ可以通過插件的方式集成,也可以通過gradle直接集成。
1. 插件集成
- 新建主module
app
- 新建android library
aspectj
2. gradle集成
- 項目根build.gradle添加依賴:
值得注意的是,如果在同步項目過程中報錯:// Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } google() jcenter() // mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:3.5.0' // 版本界限:As-3.0.1 + gradle4.4-all (需要配置r17的NDK環境) // 或者:As-3.2.1 + gradle4.6-all (正常使用,無警告) classpath 'org.aspectj:aspectjtools:1.8.9' classpath 'org.aspectj:aspectjweaver:1.8.9' } } allprojects { repositories { maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } google() jcenter() // mavenCentral() } } task clean(type: Delete) { delete rootProject.buildDir }
Received close_notify during handshake
,可以嘗試如上,採用阿里雲的倉庫maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
- 修改app的moudle中build.gradle:
編譯時用Aspect專門的編譯器,不再使用傳統的javac
// 版本界限:As-3.0.1 + gradle4.4-all (需要配置r17的NDK環境) // 或者:As-3.2.1 + gradle4.6-all (正常使用,無警告) buildscript { // 編譯時用Aspect專門的編譯器,不再使用傳統的javac repositories { maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } // mavenCentral() } dependencies { classpath 'org.aspectj:aspectjtools:1.8.9' classpath 'org.aspectj:aspectjweaver:1.8.9' } }
- 整體如下:
apply plugin: 'com.android.application' // 版本界限:As-3.0.1 + gradle4.4-all (需要配置r17的NDK環境) // 或者:As-3.2.1 + gradle4.6-all (正常使用,無警告) buildscript { // 編譯時用Aspect專門的編譯器,不再使用傳統的javac repositories { maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } // mavenCentral() } dependencies { classpath 'org.aspectj:aspectjtools:1.8.9' classpath 'org.aspectj:aspectjweaver:1.8.9' } } android { compileSdkVersion 29 buildToolsVersion "29.0.2" defaultConfig { applicationId "com.example.aspectj" minSdkVersion 15 targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.0.2' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' // 引入aspectj的包 implementation 'org.aspectj:aspectjrt:1.8.13' } /** * 以下groovy代碼不需要太關心,只是作爲一個支持 */ // 版本界限:As-3.0.1 + gradle4.4-all (需要配置r17的NDK環境) // 或者:As-3.2.1 + gradle4.6-all (正常使用,無警告) 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; } } } }
4. 代碼實操
需求:點擊登錄;查看我的專區、我的優惠券、我的積分(查看之前先檢查是否登錄狀態,如是則跳轉相應的功能區,否則跳轉登錄頁面重新登錄);此外都需要做行爲統計分析。
問題:如何實現用戶行爲統計分析?
答:友盟等第三方統計。但如果要求自己實際實現 ,那隻能通過AOP的思想來實現。
統計用戶點擊某功能行爲。(實際項目中,存儲到本地,每隔N天上傳到服務器)。
1. 用戶行爲統計
-
自定義
行爲
註解//用戶點擊痕跡(行爲統計) 自定義註解參看:公開課 IOC容器 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ClickBehavior { String value(); }
-
將
行爲
註解作用在需要進行行爲統計的
代碼上。// 用戶行爲統計 @ClickBehavior(value = "登錄") public void login(View view) { Log.e(TAG, "login: 驗證通過,登錄成功"); }
-
定義
AspectJ
書寫該類時需要注意,不能寫錯一個符號,否則不容易排查。
AspectJ
是由PointCut(切入點)
和Advice(通知)
構成的。 而Advice
有:Before After Around
三種情況- 使用註解
@Aspect
來定義一個切面類。@Aspect public class ClickBehaviorAspect { }
- 通過
@Pointcut
尋找需要處理的切入點
關於通配符// 1. 應用中使用到了哪些註解,放到當前的切入點進行處理(找到需要處理的切入點) // execution 以方法執行時作爲切點,觸發Aspect類。 語法:execution() // * *(..) 通配符,可以處理ClickBehavior這個類所有的方法,也可以指定com.example.aspectj.MainActivity.login()方法作爲切入點 @Pointcut("execution(@com.example.aspectj.annotation.ClickBehavior * *(..))") // public void methodPointCut() { }
* *(..)
可以查看execution(* com.xxx..(…)) - 處理切入點
// 2. 對切入點如何處理, // Around 圍繞切入點如何處理 @Around("methodPointCut()") public Object jointPoint(ProceedingJoinPoint joinPoint) throws Throwable { // 遵循三個規則,1.返回值Object 2.參數ProceedingJoinPoint 3. 拋出異常 Throwable }
- 使用註解
2. 集中式登錄
- 同樣定義
LoginCheck
註解// 檢查是否登錄 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface LoginCheck { }
- 使用
LoginCheck
註解//登錄成功之後 用戶行爲統計 @ClickBehavior(value = "我的積分") @LoginCheck() public void score(View view) { Log.e(TAG, "score: 跳轉我的積分"); startActivity(new Intent(MainActivity.this, OtherActivity.class)); }
- 定義
Aspect
並處理切入點@Aspect public class LoginCheckAspect { private static final String TAG = LoginCheckAspect.class.getSimpleName(); @Pointcut("execution(@com.example.aspectj.annotation.LoginCheck * *(..))") public void methodLoginCheck() { } @Around("methodLoginCheck()") public Object check(ProceedingJoinPoint joinPoint) throws Throwable { Context context = (Context) joinPoint.getThis(); boolean isLogin = false; //該處進行登錄模擬, 實際項目中,從SharedPreference 從讀取 if (isLogin) { Log.e(TAG, "檢測到已登錄"); return joinPoint.proceed();//檢測到登錄,繼續執行後續代碼 } else { Log.e(TAG, "檢測到未登錄"); context.startActivity(new Intent(context, LoginActivity.class)); return null; //未登錄情況下,後續代碼都不需要執行了。 } } }
詳見代碼 AspectJ
四、AspectJ使用過程中碰到的坑
- 臨界版本as3.0.1和gradle4.4-all需要配置ndk的r17環境
- as3.2.1和gradle4.6-all 正常使用,無過時警告
- as3.4.0和gradle5.1.1-all有過時的api警告