Android面向切面基于AOP實現(xiàn)登錄攔截的場景示例
前言
場景如下:用戶第一次下載App,點擊進入首頁列表,點擊個人頁面,需要校驗登錄,然后跳轉到登錄頁面,注冊/登錄完成跳轉到個人頁面。
非常常見的場景,正常我們開發(fā)就只能判斷是否已經登錄,如果未登錄就跳轉到登錄,然后登錄完成之后怎么繼續(xù)執(zhí)行?如何封裝?有哪些方式?其實很多人并不清楚。
這里做一個系列總結一下,看看公共有多少種方式實現(xiàn),你們使用的是哪一種方案,或者說你們覺得哪一種方案最好用。
這一次分享的是全網最多的方案 ,面向切面 AOP 的方式。你去某度一搜,Android攔截登錄 最多的結果就是AOP實現(xiàn)登錄攔截的功能,既然大家都推薦,我們就來看看它到底如何?
一、了解面向切面AOP
我們學習Java的開始,我們一直就知道 OOP 面向對象,其實 AOP 面向切面,是對OOP的一個補充,AOP采取橫向收取機制,取代了傳統(tǒng)縱向繼承體系重復性代碼,把某一類問題集中在一個地方進行處理,比如處理程序中的點擊事件、打印日志等。
AOP是編程思想就是把業(yè)務邏輯和橫切問題進行分離,從而達到解耦的目的,提高代碼的重用性和開發(fā)效率。OOP的精髓是把功能或問題模塊化,每個模塊處理自己的家務事。但在現(xiàn)實世界中,并不是所有功能都能完美得劃分到模塊中。AOP的目標是把這些功能集中起來,放到一個統(tǒng)一的地方來控制和管理。
我記得我最開始接觸 AOP 還是在JavaEE的框架SSH的學習中,AspectJ框架,開始流行于后端,現(xiàn)在在Android開發(fā)的應用中也越來越廣泛了,Android中使用AspectJ框架的應用也有很多,比如點擊事件防抖,埋點,權限申請等等不一而足,這里不展開說明,畢竟我們這一期不是專門講AspectJ的應用的。
簡單的說一下AOP的重點概念(摘抄):
- 前置通知(Before):在目標方法被調用之前調用通知功能。
- 后置通知(After):在目標方法完成之后調用通知,此時不會關心方法的輸出是什么。
- 返回通知(After-returning):在目標方法成功執(zhí)行之后調用通知。
- 異常通知(After-throwing):在目標方法拋出異常后調用通知。
- 環(huán)繞通知(Around):通知包裹了被通知的方法,在被通知的方法調用之前和調用之后執(zhí)行自定義的行為。
- 連接點:是在應用執(zhí)行過程中能夠插入切面的一個點。
- 切點: 切點定義了切面在何處要織入的一個或者多個連接點。
- 切面:是通知和切點的結合。通知和切點共同定義了切面的全部內容。
- 引入:引入允許我們向現(xiàn)有類添加新方法或屬性。
- 織入:是把切面應用到目標對象,并創(chuàng)建新的代理對象的過程。切面在指定的連接點被織入到目標對象中。在目標對象的生命周期中有多個點可以進行織入:
- 編譯期: 在目標類編譯時,切面被織入。這種方式需要特殊的編譯器。AspectJ的織入編譯器就是以這種方式織入切面的。
- 類加載期:切面在目標加載到JVM時被織入。這種方式需要特殊的類加載器(class loader)它可以在目標類被引入應用之前增強該目標類的字節(jié)碼。
- 運行期: 切面在應用運行到某個時刻時被織入。一般情況下,在織入切面時,AOP容器會為目標對象動態(tài)地創(chuàng)建一個代理對象。SpringAOP就是以這種方式織入切面的。
簡單理解就是把一個方法拿出來,在這個方法執(zhí)行前,執(zhí)行后,做一些特別的操作。關于AOP的基本使用推薦大家看看大佬的教程:
不多BB,我們直接看看Android中如何使用AspectJ實現(xiàn)AOP邏輯,實現(xiàn)攔截登錄的功能。
二、集成AOP框架
Java項目集成
buildscript { repositories { mavenCentral() } dependencies { classpath 'org.aspectj:aspectjtools:1.8.9' classpath 'org.aspectj:aspectjweaver:1.8.9' } }
組件build.gradle
dependencies { implementation 'org.aspectj:aspectjrt:1.9.6' } import org.aspectj.bridge.IMessage import org.aspectj.bridge.MessageHandler import org.aspectj.tools.ajc.Main // 獲取log打印工具和構建配置 final def log = project.logger final def variants = project.android.applicationVariants variants.all { variant -> if (!variant.buildType.isDebuggable()) { // 判斷是否debug,如果打release把return去掉就可以 log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.") // return; } // 使aspectj配置生效 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); //在編譯時打印信息如警告、error等等 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; } } } }
Kotlin項目集成
dependencies { classpath 'com.android.tools.build:gradle:3.6.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.10'
項目build.gradle
apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' apply plugin: 'android-aspectjx' android { ... // AOP 配置 aspectjx { // 排除一些第三方庫的包名(Gson、 LeakCanary 和 AOP 有沖突) exclude 'androidx', 'com.google', 'com.squareup', 'com.alipay', 'com.taobao', 'org.apache', 'org.jetbrains.kotlin', "module-info", 'versions.9' } } ependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'org.aspectj:aspectjrt:1.9.5' }
集成AOP踩坑: zip file is empty
和第三方包有沖突,比如Gson,OkHttp等,需要配置排除一下第三方包,
gradle版本兼容問題
AGP版本4.0以上不支持 推薦使用3.6.1
kotlin兼容問題 :
基本都是推薦使用 com.hujiang.aspectjx
編譯版本兼容問題:
4.0以上使用KT編譯版本為Java11需要改為Java8
組件化兼容問題:
如果在library的moudle中自定義的注解, 想要通過AspectJ來攔截織入, 那么這個@Aspect類必須和自定義的注解在同一moudle中, 否則是沒有效果的
等等...
難點就在集成,如何在指定版本的Gradle,Kotlin項目中集成成功。只要集成成功了,使用到是簡單了。
三、定義注解實現(xiàn)功能
定義標記的注解
//不需要回調的處理 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Login { }
定義處理類
@Aspect public class LoginAspect { @Pointcut("@annotation(com.guadou.kt_demo.demo.demo3_bottomtabbar_fragment.aop.Login)") public void Login() { } //不帶回調的注解處理 @Around("Login()") public void loginJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable { YYLogUtils.w("走進AOP方法-Login()"); Signature signature = joinPoint.getSignature(); if (!(signature instanceof MethodSignature)){ throw new RuntimeException("該注解只能用于方法上"); } Login login = ((MethodSignature) signature).getMethod().getAnnotation(Login.class); if (login == null) return; //判斷當前是否已經登錄 if (LoginManager.isLogin()) { joinPoint.proceed(); } else { //如果未登錄,去登錄頁面 LoginManager.gotoLoginPage(); } }
object LoginManager { @JvmStatic fun isLogin(): Boolean { val token = SP().getString(Constants.KEY_TOKEN, "") YYLogUtils.w("LoginManager-token:$token") val checkEmpty = token.checkEmpty() return !checkEmpty } @JvmStatic fun gotoLoginPage() { commContext().gotoActivity<LoginDemoActivity>() } }
其實邏輯很簡單,就是判斷是否登錄,看是放行還是跳轉到登錄頁面
使用的邏輯也是很簡單,把需要處理的邏輯使用方法抽取,并標記注解即可
override fun init() { mBtnCleanToken.click { SP().remove(Constants.KEY_TOKEN) toast("清除成功") } mBtnProfile.click { //不帶回調的登錄方式 gotoProfilePage2() } } @Login private fun gotoProfilePage2() { gotoActivity<ProfileDemoActivity>() }
效果:
這..這和我使用Token自己手動判斷有什么區(qū)別,完成登錄之后還得我再點一次按鈕,當然了這只是登錄攔截,我想要的是登錄成功之后繼續(xù)之前的操作,怎么辦?
其實使用AOP的方式的話,我們可以使用消息通知的方式,比如LiveBus FlowBus之類的間接實現(xiàn)這個效果。
我們先單獨的定義一個注解
//需要回調的處理用來觸發(fā)用戶登錄成功后的后續(xù)操作 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface LoginCallback { }
修改定義的切面類
@Aspect public class LoginAspect { @Pointcut("@annotation(com.guadou.kt_demo.demo.demo3_bottomtabbar_fragment.aop.Login)") public void Login() { } @Pointcut("@annotation(com.guadou.kt_demo.demo.demo3_bottomtabbar_fragment.aop.LoginCallback)") public void LoginCallback() { } //帶回調的注解處理 @Around("LoginCallback()") public void loginCallbackJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable { YYLogUtils.w("走進AOP方法-LoginCallback()"); Signature signature = joinPoint.getSignature(); if (!(signature instanceof MethodSignature)){ throw new RuntimeException("該注解只能用于方法上"); } LoginCallback loginCallback = ((MethodSignature) signature).getMethod().getAnnotation(LoginCallback.class); if (loginCallback == null) return; //判斷當前是否已經登錄 if (LoginManager.isLogin()) { joinPoint.proceed(); } else { LifecycleOwner lifecycleOwner = (LifecycleOwner) joinPoint.getTarget(); LiveEventBus.get("login").observe(lifecycleOwner, new Observer<Object>() { @Override public void onChanged(Object integer) { try { joinPoint.proceed(); LiveEventBus.get("login").removeObserver(this); } catch (Throwable throwable) { throwable.printStackTrace(); LiveEventBus.get("login").removeObserver(this); } } }); LoginManager.gotoLoginPage(); } } //不帶回調的注解處理 @Around("Login()") public void loginJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable { YYLogUtils.w("走進AOP方法-Login()"); Signature signature = joinPoint.getSignature(); if (!(signature instanceof MethodSignature)){ throw new RuntimeException("該注解只能用于方法上"); } Login login = ((MethodSignature) signature).getMethod().getAnnotation(Login.class); if (login == null) return; //判斷當前是否已經登錄 if (LoginManager.isLogin()) { joinPoint.proceed(); } else { //如果未登錄,去登錄頁面 LoginManager.gotoLoginPage(); } } }
在去登錄頁面之前注冊一個LiveEventBus事件,當登錄完成之后發(fā)出通知,這里就直接放行調用注解的方法。即可完成繼續(xù)執(zhí)行的操作。
使用:
override fun init() { mBtnCleanToken.click { SP().remove(Constants.KEY_TOKEN) toast("清除成功") } mBtnProfile.click { //不帶回調的登錄方式 gotoProfilePage() } } @LoginCallback private fun gotoProfilePage() { gotoActivity<ProfileDemoActivity>() }
效果:
總結
從上面的代碼我們就基于AOP思想實現(xiàn)了登錄攔截功能,以后我們對于需要用戶登錄之后才能使用的功能只需要在對應的方法上添加指定的注解即可完成邏輯,徹底擺脫傳統(tǒng)耗時耗力的開發(fā)方式。
需要注意的是AOP框架雖然使用起來很方便,能幫我們輕松完成函數插樁功能,但是它也有自己的缺點。
AspectJ 在實現(xiàn)時會包裝自己的一些類,不僅會影響切點方法的性能,還會導致安裝包體積的增大。
最關鍵的是對Kotlin不友好,對高版本AGP不友好,所以大家在使用時需要仔細權衡是否適合自己的項目。如有需求可以運行源碼查看效果。源碼在此!
以上就是Android面向切面基于AOP實現(xiàn)登錄攔截的場景示例的詳細內容,更多關于Android AOP登錄攔截的資料請關注腳本之家其它相關文章!
相關文章
使用Composing?builds提升Android編譯速度
這篇文章主要介紹了使用Composing?builds提升Android編譯速度示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-06-06Android 多層嵌套后的 Fragment 懶加載實現(xiàn)示例
這篇文章主要介紹了Android 多層嵌套后的 Fragment 懶加載實現(xiàn)示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-04-04Android音視頻開發(fā)只硬件解碼組件MediaCodec講解
在Android開發(fā)中提供了實現(xiàn)音視頻編解碼工具MediaCodec,針對對應音視頻解碼類型通過該類創(chuàng)建對應解碼器就能實現(xiàn)對數據進行解碼操作。本文通過示例詳細講解了MediaCodec的使用,需要的可以參考一下2023-01-01Android使用Fragment實現(xiàn)兼容手機和平板的程序
這篇文章主要介紹了Android使用Fragment實現(xiàn)兼容手機和平板的程序,幫助大家更好的理解和學習使用Android開發(fā),感興趣的朋友可以了解下2021-04-04