網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
前言
場(chǎng)景如下:用戶第一次下載App,點(diǎn)擊進(jìn)入首頁(yè)列表,點(diǎn)擊個(gè)人頁(yè)面,需要校驗(yàn)登錄,然后跳轉(zhuǎn)到登錄頁(yè)面,注冊(cè)/登錄完成跳轉(zhuǎn)到個(gè)人頁(yè)面。
非常常見的場(chǎng)景,正常我們開發(fā)就只能判斷是否已經(jīng)登錄,如果未登錄就跳轉(zhuǎn)到登錄,然后登錄完成之后怎么繼續(xù)執(zhí)行?如何封裝?有哪些方式?其實(shí)很多人并不清楚。
這里做一個(gè)系列總結(jié)一下,看看公共有多少種方式實(shí)現(xiàn),你們使用的是哪一種方案,或者說(shuō)你們覺得哪一種方案最好用。
這一次分享的是全網(wǎng)最多的方案 ,面向切面 AOP 的方式。你去某度一搜,Android攔截登錄 最多的結(jié)果就是AOP實(shí)現(xiàn)登錄攔截的功能,既然大家都推薦,我們就來(lái)看看它到底如何?
一、了解面向切面AOP
我們學(xué)習(xí)Java的開始,我們一直就知道 OOP 面向?qū)ο螅鋵?shí) AOP 面向切面,是對(duì)OOP的一個(gè)補(bǔ)充,AOP采取橫向收取機(jī)制,取代了傳統(tǒng)縱向繼承體系重復(fù)性代碼,把某一類問(wèn)題集中在一個(gè)地方進(jìn)行處理,比如處理程序中的點(diǎn)擊事件、打印日志等。
AOP是編程思想就是把業(yè)務(wù)邏輯和橫切問(wèn)題進(jìn)行分離,從而達(dá)到解耦的目的,提高代碼的重用性和開發(fā)效率。OOP的精髓是把功能或問(wèn)題模塊化,每個(gè)模塊處理自己的家務(wù)事。但在現(xiàn)實(shí)世界中,并不是所有功能都能完美得劃分到模塊中。AOP的目標(biāo)是把這些功能集中起來(lái),放到一個(gè)統(tǒng)一的地方來(lái)控制和管理。
我記得我最開始接觸 AOP 還是在JavaEE的框架SSH的學(xué)習(xí)中,AspectJ框架,開始流行于后端,現(xiàn)在在Android開發(fā)的應(yīng)用中也越來(lái)越廣泛了,Android中使用AspectJ框架的應(yīng)用也有很多,比如點(diǎn)擊事件防抖,埋點(diǎn),權(quán)限申請(qǐng)等等不一而足,這里不展開說(shuō)明,畢竟我們這一期不是專門講AspectJ的應(yīng)用的。
簡(jiǎn)單的說(shuō)一下AOP的重點(diǎn)概念(摘抄):
- 前置通知(Before):在目標(biāo)方法被調(diào)用之前調(diào)用通知功能。
- 后置通知(After):在目標(biāo)方法完成之后調(diào)用通知,此時(shí)不會(huì)關(guān)心方法的輸出是什么。
- 返回通知(After-returning):在目標(biāo)方法成功執(zhí)行之后調(diào)用通知。
- 異常通知(After-throwing):在目標(biāo)方法拋出異常后調(diào)用通知。
- 環(huán)繞通知(Around):通知包裹了被通知的方法,在被通知的方法調(diào)用之前和調(diào)用之后執(zhí)行自定義的行為。
- 連接點(diǎn):是在應(yīng)用執(zhí)行過(guò)程中能夠插入切面的一個(gè)點(diǎn)。
- 切點(diǎn): 切點(diǎn)定義了切面在何處要織入的一個(gè)或者多個(gè)連接點(diǎn)。
- 切面:是通知和切點(diǎn)的結(jié)合。通知和切點(diǎn)共同定義了切面的全部?jī)?nèi)容。
- 引入:引入允許我們向現(xiàn)有類添加新方法或?qū)傩浴?/li>
- 織入:是把切面應(yīng)用到目標(biāo)對(duì)象,并創(chuàng)建新的代理對(duì)象的過(guò)程。切面在指定的連接點(diǎn)被織入到目標(biāo)對(duì)象中。在目標(biāo)對(duì)象的生命周期中有多個(gè)點(diǎn)可以進(jìn)行織入:
- 編譯期: 在目標(biāo)類編譯時(shí),切面被織入。這種方式需要特殊的編譯器。AspectJ的織入編譯器就是以這種方式織入切面的。
- 類加載期:切面在目標(biāo)加載到JVM時(shí)被織入。這種方式需要特殊的類加載器(class loader)它可以在目標(biāo)類被引入應(yīng)用之前增強(qiáng)該目標(biāo)類的字節(jié)碼。
- 運(yùn)行期: 切面在應(yīng)用運(yùn)行到某個(gè)時(shí)刻時(shí)被織入。一般情況下,在織入切面時(shí),AOP容器會(huì)為目標(biāo)對(duì)象動(dòng)態(tài)地創(chuàng)建一個(gè)代理對(duì)象。SpringAOP就是以這種方式織入切面的。
簡(jiǎn)單理解就是把一個(gè)方法拿出來(lái),在這個(gè)方法執(zhí)行前,執(zhí)行后,做一些特別的操作。關(guān)于AOP的基本使用推薦大家看看大佬的教程:
深入理解Android之AOP
不多BB,我們直接看看Android中如何使用AspectJ實(shí)現(xiàn)AOP邏輯,實(shí)現(xiàn)攔截登錄的功能。
二、集成AOP框架
Java項(xiàng)目集成
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打印工具和構(gòu)建配置
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);
//在編譯時(shí)打印信息如警告、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項(xiàng)目集成
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'
項(xiàng)目build.gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'android-aspectjx'
android {
...
// AOP 配置
aspectjx {
// 排除一些第三方庫(kù)的包名(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版本兼容問(wèn)題
AGP版本4.0以上不支持 推薦使用3.6.1
kotlin兼容問(wèn)題 :
基本都是推薦使用 com.hujiang.aspectjx
編譯版本兼容問(wèn)題:
4.0以上使用KT編譯版本為Java11需要改為Java8
組件化兼容問(wèn)題:
如果在library的moudle中自定義的注解, 想要通過(guò)AspectJ來(lái)攔截織入, 那么這個(gè)@Aspect類必須和自定義的注解在同一moudle中, 否則是沒(méi)有效果的
等等...
難點(diǎn)就在集成,如何在指定版本的Gradle,Kotlin項(xiàng)目中集成成功。只要集成成功了,使用到是簡(jiǎn)單了。
三、定義注解實(shí)現(xiàn)功能
定義標(biāo)記的注解
//不需要回調(diào)的處理
@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() {
}
//不帶回調(diào)的注解處理
@Around("Login()")
public void loginJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
YYLogUtils.w("走進(jìn)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;
//判斷當(dāng)前是否已經(jīng)登錄
if (LoginManager.isLogin()) {
joinPoint.proceed();
} else {
//如果未登錄,去登錄頁(yè)面
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>()
}
}
其實(shí)邏輯很簡(jiǎn)單,就是判斷是否登錄,看是放行還是跳轉(zhuǎn)到登錄頁(yè)面
使用的邏輯也是很簡(jiǎn)單,把需要處理的邏輯使用方法抽取,并標(biāo)記注解即可
override fun init() {
mBtnCleanToken.click {
SP().remove(Constants.KEY_TOKEN)
toast("清除成功")
}
mBtnProfile.click {
//不帶回調(diào)的登錄方式
gotoProfilePage2()
}
}
@Login
private fun gotoProfilePage2() {
gotoActivity<ProfileDemoActivity>()
}
效果:
這..這和我使用Token自己手動(dòng)判斷有什么區(qū)別,完成登錄之后還得我再點(diǎn)一次按鈕,當(dāng)然了這只是登錄攔截,我想要的是登錄成功之后繼續(xù)之前的操作,怎么辦?
其實(shí)使用AOP的方式的話,我們可以使用消息通知的方式,比如LiveBus FlowBus之類的間接實(shí)現(xiàn)這個(gè)效果。
我們先單獨(dú)的定義一個(gè)注解
//需要回調(diào)的處理用來(lái)觸發(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() {
}
//帶回調(diào)的注解處理
@Around("LoginCallback()")
public void loginCallbackJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
YYLogUtils.w("走進(jìn)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;
//判斷當(dāng)前是否已經(jīng)登錄
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();
}
}
//不帶回調(diào)的注解處理
@Around("Login()")
public void loginJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
YYLogUtils.w("走進(jìn)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;
//判斷當(dāng)前是否已經(jīng)登錄
if (LoginManager.isLogin()) {
joinPoint.proceed();
} else {
//如果未登錄,去登錄頁(yè)面
LoginManager.gotoLoginPage();
}
}
}
在去登錄頁(yè)面之前注冊(cè)一個(gè)LiveEventBus事件,當(dāng)?shù)卿浲瓿芍蟀l(fā)出通知,這里就直接放行調(diào)用注解的方法。即可完成繼續(xù)執(zhí)行的操作。
使用:
override fun init() {
mBtnCleanToken.click {
SP().remove(Constants.KEY_TOKEN)
toast("清除成功")
}
mBtnProfile.click {
//不帶回調(diào)的登錄方式
gotoProfilePage()
}
}
@LoginCallback
private fun gotoProfilePage() {
gotoActivity<ProfileDemoActivity>()
}
效果:
總結(jié)
從上面的代碼我們就基于AOP思想實(shí)現(xiàn)了登錄攔截功能,以后我們對(duì)于需要用戶登錄之后才能使用的功能只需要在對(duì)應(yīng)的方法上添加指定的注解即可完成邏輯,徹底擺脫傳統(tǒng)耗時(shí)耗力的開發(fā)方式。
需要注意的是AOP框架雖然使用起來(lái)很方便,能幫我們輕松完成函數(shù)插樁功能,但是它也有自己的缺點(diǎn)。
AspectJ 在實(shí)現(xiàn)時(shí)會(huì)包裝自己的一些類,不僅會(huì)影響切點(diǎn)方法的性能,還會(huì)導(dǎo)致安裝包體積的增大。
最關(guān)鍵的是對(duì)Kotlin不友好,對(duì)高版本AGP不友好,所以大家在使用時(shí)需要仔細(xì)權(quán)衡是否適合自己的項(xiàng)目。如有需求可以運(yùn)行源碼查看效果。源碼在此!
原文鏈接:https://juejin.cn/post/7132643283083198501
相關(guān)推薦
- 2022-07-31 Android?中的類文件和類加載器詳情_Android
- 2022-07-24 python單向鏈表實(shí)例詳解_python
- 2022-03-16 C#中獲取二維數(shù)組的行數(shù)和列數(shù)以及多維數(shù)組各個(gè)維度的長(zhǎng)度_C#教程
- 2023-01-15 Keras中Conv1D的使用及說(shuō)明_python
- 2022-12-04 Python中Yield的基本用法及Yield與return的區(qū)別解析_python
- 2022-07-03 kafka?rabbitMQ及rocketMQ隊(duì)列的消息可靠性保證分析_相關(guān)技巧
- 2021-12-07 基于Kubernetes實(shí)現(xiàn)前后端應(yīng)用的金絲雀發(fā)布(兩種方案)_C#教程
- 2022-06-12 python實(shí)現(xiàn)微信小程序的多種支付方式_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支