日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

AOP实现Android集中式登录架构

發(fā)布時間:2025/6/17 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 AOP实现Android集中式登录架构 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

未經(jīng)同意禁止抄襲,如需轉(zhuǎn)載請在顯要位置標注

前言

登錄應該是應用開發(fā)中一個很常見的功能,一般在應用中有兩種登錄,一種是一進入應用就必須登錄才能使用(如微信和QQ等),另一種是需要登錄的時候才會去登錄(如淘寶京東等)。我在工作中遇到的大部分是第二種情況,針對于第二種的登錄,我之前都是通過if(){}else()去判斷是否登錄的,但是這樣項目結(jié)構(gòu)龐大了之后就會使代碼臃腫。因為判斷用戶登錄狀態(tài)是一個頻次很高的操作,所以針對這方面我就考慮有沒有一種方案既能很方便的判斷登錄狀態(tài)又使代碼很簡潔。

想來想去方案有兩種,一種是hook到AMS攔截startActivity中的intent,在啟動activity的時候判斷是否登錄,如果沒有對intent做動態(tài)替換,另一種就是通過AOP實現(xiàn)方法添加判斷登錄代碼片段。hook對系統(tǒng)有兼容性,需要考慮到各個版本的api是否改動,而aop的實現(xiàn)方式與版本沒有任何兼容性問題,所以最后就采用了aop的方式去實現(xiàn)app集中式登錄。

集中式登錄架構(gòu)的使用

為什么我先講架構(gòu)的使用,是因為你只有知道了使用這種架構(gòu)是多么方便,才會有興趣去了解如何實現(xiàn)這種架構(gòu)。好了,先來用demo給大家演示一下!

看完gif后,你是不是覺得這不就是一個很簡單的demo,通過判斷登陸狀態(tài)跳轉(zhuǎn)不同的頁面嘛,有什么難的啊!demo是很簡單,但你繼續(xù)往下看代碼,就會覺著這個代碼實現(xiàn)是多么酷了!下面看代碼:
我們在Application里進行初始化(初始化之后才能接收登錄事件,所以越早越好)。

public class MyApplication extends Application {@Overridepublic void onCreate() {super.onCreate();LoginSDK.getInstance().init(this, new ILogin() {@Overridepublic void login(Context applicationContext, int userDefine) {switch (userDefine) {case 0:startActivity(new Intent(applicationContext, LoginActivity.class));break;case 1:Toast.makeText(applicationContext, "您還沒有登錄,請登錄后執(zhí)行", Toast.LENGTH_SHORT).show();break;case 2:new AlertDialog.Builder(MyApplication.this)...break;default:Toast.makeText(applicationContext, "執(zhí)行失敗,因為您還沒有登錄!", Toast.LENGTH_SHORT).show();break;}}@Overridepublic boolean isLogin(Context applicationContext) {return SharePreferenceUtil.getBooleanSp(SharePreferenceUtil.IS_LOGIN, applicationContext);}});}} 復制代碼

可以看到初始化方法實現(xiàn)了ILogin接口,ILogin接口有兩個方法,第一個login()用于接收登錄事件,第二個方法isLogin是判斷登錄狀態(tài),這兩個方法留給用戶自己實現(xiàn),提高架構(gòu)的可用性。我們所有的登錄請求都會回調(diào)到ILogin接口,這也意味著登錄事件只有一個統(tǒng)一的入口,這也就是我們集中式登錄架構(gòu)的核心好處了。

好了,我們先來使用以下。

例子1:
//demo演示1 跳轉(zhuǎn)到需要過濾登錄的Activity @LoginFilter(userDefine = 0) public void onClick(View view) {startActivity(new Intent(this, SecondActivity.class)); } 復制代碼

上面代碼就是監(jiān)聽一個Button的點擊事件,然后加入注解@LoginFilter,看方法實現(xiàn)只是跳轉(zhuǎn)到SecondActivity,并沒有登錄邏輯的判斷,但通過這個注解我們就可以在運行時檢測是否登錄,如果沒有登錄就會中斷方法的執(zhí)行,轉(zhuǎn)而調(diào)用MyApplication里init()方法中我們自己實現(xiàn)的login()方法,login(Context applicationContext, int userDefine)方法中userDefine是留給用戶自定義的一個值,為了區(qū)別使用哪種登錄方式。是不是很簡單?再來看例子二:

例子2:

如果我們嫌棄在需要判斷登錄狀態(tài)的按鈕上加入@LoginFilter()注解麻煩,而是想實現(xiàn)啟動一個Activity自動判斷是否登錄,如果沒有登錄就回調(diào)到我們的ILogin接口,那么你只需要創(chuàng)建一個LoginFilterActivity如下:

//demo演示2 直接過濾登陸,不需要加注解,則繼承LoginFilterActivity public class LoginFilterActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);if (!LoginAssistant.getInstance().getiLogin().isLogin(getApplicationContext())) {//TODO: 你可以想做什么就做什么,在這里我讓頁面結(jié)束,并給用戶提示Toast.makeText(this, "沒有登錄!", Toast.LENGTH_SHORT).show();finish();}}} 復制代碼

然后我們讓需要登錄才能進入的Activity繼承自LoginFilterActivity就可以了。假如UserActivity繼承了LoginFilterActivity,當用戶沒有登錄的時候,我們啟動UserActivity的時候便會回調(diào)到我們的ILogin接口,是不是很方便,這就是我們今天要講的集中式登錄架構(gòu)。

下面,我們來講一講如何實現(xiàn)這個架構(gòu)。

AOP原理

我們先來了解一下AOP,因為這個架構(gòu)是基于AOP編程實現(xiàn)的。

  • 什么是AOP

關于AOP是什么,這里我簡單介紹一下,AOP是Aspect Oriented Programming的縮寫,即面向切面編程,與面向?qū)ο缶幊?oop)是兩種不同的思維方式,也可以看做是對oop的一種補充。傳統(tǒng)的oop開發(fā)會提倡功能模塊化等,而aop適合于針對某一類型的問題統(tǒng)一處理。AOP思想的講解不是我們本篇文章的重點,如果有同學對AOP思想不是很理解,這里我推薦一篇文章,講得很不錯Java AOP & Spring AOP 原理和實現(xiàn)

  • AspectJ介紹

AspectJ是一個面向切面編程的一個框架,它擴展了java語言,并定義了實現(xiàn)AOP的語法。我們知道,在將.java文件編譯為.class文件時默認使用javac編譯工具,而AspectJ會有一套符合java字節(jié)碼編碼規(guī)范的編譯工具來替代javac,在將.java文件編譯為.class文件時,會動態(tài)的插入一些代碼來做到對某一類特定東西的統(tǒng)一處理。我舉個例子,比如在應用中有很多個button的onClick事件需要檢測是否登錄,如果沒有登錄則需要去登錄之后才能繼續(xù)執(zhí)行,針對這一類型的問題,相對笨一點的做法就是在每一個onClick方法中都顯式的去判斷登錄狀態(tài),這樣不免過于麻煩。而我們用AOP的方式實現(xiàn)的話,就需要在每一個onClick方法上加入一個標注,讓編譯器在編譯時能識別到這個標注,然后根據(jù)標注來生成一些代碼檢測登錄狀態(tài)。好了,如果有同學對AOP還不是很理解的話也不用急,下面我會用例子來給大家演示如何使用AOP實現(xiàn)統(tǒng)一的集中式登錄。

AOP實現(xiàn)集中式登錄

  • aspectj環(huán)境搭建

首先,我們導入AspectJ的jar包,AspectJ的jar網(wǎng)上一搜就有,也可以直接去我demo里面拿,LoginArchitecture AOP實現(xiàn)集中式登錄 github鏈接點我。demo里jar包導入:
好了,導入jar后還需要在app.gradle配置如下:

buildscript {repositories {mavenCentral()}dependencies {classpath 'org.aspectj:aspectjtools:1.8.8'classpath 'org.aspectj:aspectjweaver:1.8.8'} } 復制代碼

然后在文件末尾添加如下代碼:

import org.aspectj.bridge.IMessage import org.aspectj.bridge.MessageHandler import org.aspectj.tools.ajc.Main //標注1 final def log = project.logger final def variants = project.android.applicationVariantsvariants.all { variant ->//標注2if (!variant.buildType.isDebuggable()) {log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")return;}//標注3JavaCompile javaCompile = variant.javaCompilejavaCompile.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);//標注4for (IMessage message : handler.getMessages(null, true)) {switch (message.getKind()) {case IMessage.ABORT:case IMessage.ERROR:case IMessage.FAIL:log.error message.message, message.thrownbreak;case IMessage.WARNING:log.warn message.message, message.thrownbreak;case IMessage.INFO:log.info message.message, message.thrownbreak;case IMessage.DEBUG:log.debug message.message, message.thrownbreak;}}} } 復制代碼

關于上面這一大片代碼就是對aspectj的配置,先看標注1,獲取log打印工具和構(gòu)建配置,然后標注2判斷是否debug,如果打release把return去掉就可以,標注3處意思是使aspectj配置生效,標注4就是為了在編譯時打印信息如警告、error等等,這些東西在網(wǎng)上也有很多,大家如果不理解,可以去搜索一下,這里不再詳細解釋。

  • 切面代碼編寫

好了,配置完上面的內(nèi)容之后,我們就開始編寫代碼了,首先,定義一個注解LoginFilter,用來注解方法,以便在編譯期被編譯器檢測到需要做切面的方法。

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface LoginFilter {int userDefine() default 0;} 復制代碼

大家看到我在注解里加了個userDefine,就是為了給用戶提供自定義實現(xiàn),如根據(jù)userDifine值不同做不同的登錄處理。

然后,編寫LoginSDK文件用于初始化和接收登錄事件,代碼如下:

public class LoginSDK {public void init(Context context, ILogin iLogin) {applicationContext = context.getApplicationContext();LoginAssistant.getInstance().setApplicationContext(context);LoginAssistant.getInstance().setiLogin(iLogin);}//...} 復制代碼

然后,新建LoginFilterAspect.java文件用來處理加入LoginFilter注解的方法,對這些方法做統(tǒng)一的切面處理。

@Aspect public class LoginFilterAspect {private static final String TAG = "LoginFilterAspect";@Pointcut("execution(@com.xsm.loginarchitecture.lib_login.annotation.LoginFilter * *(..))")public void loginFilter() {}@Around("loginFilter()")public void aroundLoginPoint(ProceedingJoinPoint joinPoint) throws Throwable {//標注1ILogin iLogin = LoginAssistant.getInstance().getiLogin();if (iLogin == null) {throw new NoInitException("LoginSDK 沒有初始化!");}//標注2Signature signature = joinPoint.getSignature();if (!(signature instanceof MethodSignature)) {throw new AnnotationException("LoginFilter 注解只能用于方法上");}MethodSignature methodSignature = (MethodSignature) signature;LoginFilter loginFilter = methodSignature.getMethod().getAnnotation(LoginFilter.class);if (loginFilter == null) {return;}Context param = LoginAssistant.getInstance().getApplicationContext();//標注3if (iLogin.isLogin(param)) {joinPoint.proceed();} else {iLogin.login(param, loginFilter.userDefine());}} }復制代碼

代碼并不多,我們來一一解釋。首先看loginFilter方法,這個方法上加入@Pointcut注解,并指定了LoginFilter注解的路徑,@Pointcut注解包括aroundLoginPoint()方法上的@Around注解等都是AspectJ定義的API。@Pointcut注解代表切入點,具體就是指哪些方法需要被執(zhí)行"AOP"。execution()里指定了LoginFilter注解的路徑,即加入LoginFilter注解的方法就是需要處理的切面。@Around注解表示這個方法執(zhí)行時機的前后都可以做切面處理,常用到的還有@Before、@After等等。@Before即方法執(zhí)行前做處理,@After反之。
好了,aroundLoginPoint(ProceedingJoinPoint joinPoint)方法就是對切面的具體實現(xiàn)了,這里ProceedingJoinPoint參數(shù)意為環(huán)繞通知,這個類里面可以獲取到方法的簽名等各種信息。

標注1

首先看標注1處,我們先獲取用戶實現(xiàn)的ILogin類,如果沒有調(diào)用init()設置初始化就拋出異常。

標注2

標注2處先得到方法的簽名methodSignature,然后得到@LoginFilter注解,如果注解為空,就不再往下走。

標注3

然后看標注3,調(diào)用iLogin的isLogin()方法判斷是否登錄,這個isLogin是留給使用者自己實現(xiàn)的,如果登錄,就會繼續(xù)執(zhí)行方法體調(diào)用方法直到完成,如果沒有登錄,調(diào)用ilogin的login方法,并把userDefine傳過去,login方法是用戶自己實現(xiàn)的。

好了,切面代碼的處理介紹完了,這個時候我們build一下項目,會在項目下\build\intermediates\classes\debug文件夾生成經(jīng)過AspectJ編譯器編譯后的.class文件,我們看下上面例子1中的方法skip(View v)方法,編譯成class文件的方法體變成了如下這樣:

@LoginFilterpublic void onClick(View view) {JoinPoint var3 = Factory.makeJP(ajc$tjp_0, this, this, view);skip_aroundBody1$advice(this, view, var3, LoginFilterAspect.aspectOf(), (ProceedingJoinPoint)var3);} 復制代碼

可以看到我們的點擊事件方法已經(jīng)被植入了一些代碼,而原來startActivity(new Intent(this, SecondActivity.class));也不見了,實際上這里是把我們方法的執(zhí)行給封裝了,這里會在運行期,目標類加載后,為接口動態(tài)生成代理類,將切面織入到代理類中,從而實現(xiàn)對方法進行統(tǒng)一的處理。注:這里面有個小插曲,就是我在演示的時候

另外,評論中有同學提出單點登錄機制處理麻煩,于是我在LoginSDK中加入后臺token驗證失效統(tǒng)一接入入口,我貼出用法:

LoginSDK.getInstance().serverTokenInvalidation(TOKEN_INVALIDATION); 復制代碼

想要詳細了解的同學可以參考demo。

小結(jié)

到這里,是不是覺得通過切面處理登錄很簡單,實際上我們只要熟悉了切面編程的API,便可以利用這么簡單的方法對一批擁有某項特征的東西做特定處理。本項目的demo我放在了github,如果對本篇文章感興趣的同學可以clone下來自己熟悉之后,運用到項目中。demo地址,歡迎star,我的github還有許多有意思的庫,歡迎參觀哦

聯(lián)系方式: xiasem@163.com

總結(jié)

以上是生活随笔為你收集整理的AOP实现Android集中式登录架构的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。