Android入门第66天-使用AOP

Posted TGITCIC

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android入门第66天-使用AOP相关的知识,希望对你有一定的参考价值。

开篇

        这篇恐怕又是一篇补足网上超9成关于这个领域实际都是错的、用不起来的一个知识点了。网上太多太多教程和案例用的是一个叫hujiang的AOP组件-com.hujiang.aspectjx:gradle-android-plugin-aspectjx

        首先这些错的文章我不知道是怎么来的,其次那些案例真的运行成功过吗?它们用的都是aspectjx功能,是为了兼容Kotlin。

        还是那句话,Java开发和Kotlin还有Flutter开发到底有什么区别?整一堆无用功、把框架搞得很复杂、一堆错误的无用的代码混在一起,这才是本质。开发了好用什么语言都可以开发出好东西来,开发了不好就只剩下了:我用的开发语言是最先进的。因此我们坚持使用Java。

        扯回来,我们继续来看AOP功能在Android中的使用,由于Android是Java语言,因此它也支持aspectj功能,用法和在spring系里使用一模一样的简单。只不过都是被了这个叫:com.hujiang.aspectjx:gradle-android-plugin-aspectjx搞了太复杂了,甚至有人几天都调不通一个AOP功能。

        因此本篇就是交给大家正确的、纯净的、纯真的AOP使用方法。

AOP的使用场景

        AOP使用场景太多了,如:拦截方法打印日志,特别是:APP里有很多activity,它要求用户必须登录才可以打开,如:用户积分、我的历史订单等。对于这些界面,我们要求如果用户不登录那么APP自动跳到登录页。

        这种操作就是用的AOP功能去实现的。

如何使用一个AOP来判断用户打开界面前是否已经登录

        一个activity从进入到展示界面在onCreate方法里一般会经历这么几个方法,这里的initMain()就是用来展示界面的。

我们为了做这个界面在展示前判断用户是否已经登录,我们会这么“切”一刀。

然后把这个判断用户是否已经登录做成一个和后端交互的API,整个是否登录的判断逻辑如下截图:

动手使用AOP实现

        我们假设后台的这个接口如下所示(这边用了上一篇讲到的retrofit2+okhttp3+rxjava)。

package com.mkyuan.aset.mall.android.login.api;

import com.mkyuan.aset.mall.android.login.model.LoginResponseBean;

import io.reactivex.Observable;
import okhttp3.RequestBody;
import retrofit2.http.Body;
import retrofit2.http.Header;
import retrofit2.http.POST;

public interface LoginAPI 
    @POST("/api/account/checkLoginUT")
    Observable <LoginResponseBean> checkLoginUT(@Header("ut") String ut);

        所以我们现在开始动手制作我们的AOP了。

先引入AOP包

        我们这边不会使用网上的已经不维护的、一堆问题的“com.hujiang.aspectjx:gradle-android-plugin-aspectjx”。

        请直接使用“org.aspectj:aspectjrt:1.9.6”。为此

第一步:编程全局build.gradle

加入以下语句:

dependencies 
        classpath 'org.aspectj:aspectjtools:1.9.6'

第二步:编辑我们的模块级别的build.gradle文件

先引入aspectjrt包

    implementation 'org.aspectj:aspectjrt:1.9.6' //引入 aspectj

然后再要在同一个build.gradle中加入如下语句

//使得aop生效
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;
            
        
    

加完后的build.gradle长这个样

/**
 * following plugins is the original version
 */
/*
plugins 
    id 'com.android.application'

*/
/**
 * by using aspectj must ad following lines tart
 */
apply plugin: 'com.android.application'
//apply plugin: 'android-aspectjx' //暂时注了
/**
 * by using aspectj must ad following lines tart
 */
android 
    namespace 'com.mkyuan.aset.mall.android'
    compileSdk 32

    defaultConfig 
        applicationId "com.mkyuan.aset.mall.android"
        minSdk 27
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    

    buildTypes 
        release 
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        
    
    compileOptions 
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    
    dataBinding 
        enabled = true
    

    //aspectjx 
    //    exclude "**/module-info.class"
    //    exclude "META-INF.versions.9.module-info"
    //    exclude "META-INF/versions/9/*.class"
    //    exclude 'com.google', 'com.squareup', 'org.apache','com.taobao','com.ut'
    //    exclude 'androidx', 'com.squareup', 'com.alipay', 'org.apache', 'org.jetbrains.kotlin',
    //    "module-info", 'versions.9'
    //



dependencies 
    implementation 'org.aspectj:aspectjrt:1.9.6' //引入 aspectj
    implementation 'com.github.bumptech.glide:glide:4.11.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
    implementation 'org.aspectj:aspectjrt:1.9.6'

    implementation 'io.github.youth5201314:banner:2.2.2'
    //com.google.android.material.theme
    //implementation 'com.google.android.material:material:<version>'
    //retrofit2
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    //日志拦截器
    implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
    //rxjava
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
    implementation 'io.reactivex.rxjava2:rxjava:2.2.12'
    //gson
    implementation 'com.google.code.gson:gson:2.8.7'
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'


//使得aop生效
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;
            
        
    

制作AOP相关的代码

Login.java-定义一个基于方法切面的annotation

package com.mkyuan.aset.mall.android.util.aop.login;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//不需要回调的处理
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Login 

LoginAspect.java -使用Around模式对含有@Login的方法进行切面

package com.mkyuan.aset.mall.android.util.aop.login;

import android.util.Log;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

@Aspect
public class LoginAspect 
    private static final String TAG = "LoginAspect";

    @Pointcut("execution(@com.mkyuan.aset.mall.android.util.aop.login.Login  * *(..))")
    public void executionCheckLogin() 
    

    //不带回调的注解处理
    @Around("executionCheckLogin()")
    public void loginJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable 
        Log.i(TAG, ">>>>>>走进AOP方法");
        Signature signature = joinPoint.getSignature();
        if (!(signature instanceof MethodSignature)) 
            throw new RuntimeException("该注解只能用于方法上");
        
        Login login = ((MethodSignature) signature).getMethod().getAnnotation(Login.class);
        if (login == null)
            return;
        //判断当前是否已经登录

        LoginManager.isLogin(new LoginCheckCallBack() 
            @Override
            public void changeValue(boolean loginResult) 
                if(loginResult) 
                    Log.i(TAG, ">>>>>>用户己登录走入下一步");
                    try 
                        joinPoint.proceed();
                     catch (Throwable e) 
                        Log.e(TAG,e.getMessage(),e);
                    
                else
                    //如果未登录,去登录页面
                    Log.i(TAG, ">>>>>>用户未登录去登录页面");
                    LoginManager.gotoLoginPage();
                
            
        );
    

LoginManager.java

        内含有和后台是否登录接口交互以及判断用户如果已经登录那么继续“走下去-打开界面”,否则跳到Login登录界面的逻辑

package com.mkyuan.aset.mall.android.util.aop.login;

import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;

import com.google.gson.Gson;
import com.mkyuan.aset.mall.android.home.MainActivity;
import com.mkyuan.aset.mall.android.login.LoginActivity;
import com.mkyuan.aset.mall.android.login.SmsLoginActivity;
import com.mkyuan.aset.mall.android.login.api.LoginAPI;
import com.mkyuan.aset.mall.android.login.model.LoginResponseBean;
import com.mkyuan.aset.mall.android.network.BaseObserver;
import com.mkyuan.aset.mall.android.network.NetworkApi;
import com.mkyuan.aset.mall.android.util.ContextUtils;
import com.mkyuan.aset.mall.android.util.SharedPreferenceHelper;
import com.mkyuan.aset.mall.android.util.activity.ActivityCollector;

import java.util.Map;

import okhttp3.FormBody;
import okhttp3.MediaType;
import okhttp3.RequestBody;

public class LoginManager 
    private static final String TAG = "LoginAspect";

    public static void isLogin(LoginCheckCallBack loginCheckCallBack) 
        Context ctx = null;
        try 
            ctx = ContextUtils.getCurApplicationContext();
            SharedPreferenceHelper spHelper = new SharedPreferenceHelper(ctx);
            Map<String, String> data = spHelper.read();
            if (data.get("ut") != null && data.get("ut").trim().length() > 0) 
                String utValue = data.get("ut");
                //开始调用okhttp3, retrofit, rxjava框架
                LoginAPI loginAPI = NetworkApi.createService(LoginAPI.class);
                //loginAPI.checkLoginUT().
                loginAPI.checkLoginUT(utValue).compose(NetworkApi.applySchedulers(new BaseObserver<LoginResponseBean>() 
                    @Override
                    public void onSuccess(LoginResponseBean loginResponseBean) 
                        Log.i(TAG, ">>>>>>" + new Gson().toJson(loginResponseBean));
                        int returnCode = loginResponseBean.getCode();
                        String returnMsg = loginResponseBean.getMessage();
                        if (returnCode == 0) 
                            //result = true;
                            loginCheckCallBack.changeValue(true);
                            Log.i(TAG,
                                    ">>>>>>get verifiedCode->" + loginResponseBean.getData());
                            //startActivity(new Intent(SmsLoginActivity.this, MainActivity
                            // .class));
                         else 
                            loginCheckCallBack.changeValue(false);
                        
                    

                    @Override
                    public void onFailure(Throwable e) 
                        Log.e(TAG, ">>>>>>Network Error: " + e.toString(), e);
                        loginCheckCallBack.changeValue(false);
                    
                ));
             else 
                loginCheckCallBack.changeValue(false);
            
         catch (Exception e) 
            Log.e(TAG, ">>>>>>isLogin error: " + e.getMessage());
            loginCheckCallBack.changeValue(false);
        
    

    public static void gotoLoginPage() 
        Context ctx = null;
        try 
            ctx = ContextUtils.getCurApplicationContext();
            Intent intent = new Intent();
            intent.setClass(ctx, LoginActivity.class);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            ctx.startActivity(intent);
            ActivityCollector.getInstance().quitCurrentProcess("com.mkyuan.aset.mall.android.home.MainActivity");
         catch (Exception e) 
            Log.e(TAG, ">>>>>>gotoLoginPage error: " + e.getMessage(), e);
        
    

使用这个AOP

        在MainActivity代码里有一个initMain方法,渲染界面的逻辑全部在这个initMain方法里。

接着我们来看这个initMain()方法。在方法前我们加入了自定义的“切入点”。

效果演示

第一次打开APP,用户未登录,因此直接被跳到了Login界面

然后输入手机,点获取验证码

按提交,登录成功。

然后关闭APP,再次打开APP

由于之前用户已经登录过了,因此AOP直接会把用户带入到主页

结合着我上一篇:retrofit2+okhttp3+rxjava自己不妨动动手试试看吧

以上是关于Android入门第66天-使用AOP的主要内容,如果未能解决你的问题,请参考以下文章

Spring入门第十八课

Spring入门第二十三课

Spring入门第十九课

Spring入门第二十课

Spring入门第二十一课

Android零基础入门第66节:RecyclerView点击事件处理