王學崗csdn移動架構(6)———————Android AOP架構設計

1,AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。AOP在編程歷史上可以說是里程碑式的,對OOP編程是一種十分有益的補充。AOP像OOP一樣,只是一種編程方法論,AOP並沒有規定說,實現AOP協議的代碼,要用什麼方式去實現。OOP側重靜態,名詞,狀態,組織,數據,載體是空間;AOP側重動態,動詞,行爲,調用,算法,載體是時間;
2,介紹:AspectJ是一個面向切面編程的框架。AspectJ是對java的擴展,而且是完全兼容java的,AspectJ定義了AOP語法,它有一個專門的編譯器用來生成遵守Java字節編碼規範的Class文件。AspectJ還支持原生的Java,只需要加上AspectJ提供的註解即可。在Android開發中,一般就用它提供的註解和一些簡單的語法就可以實現絕大部分功能上的需求了。
在這裏插入圖片描述
在這裏插入圖片描述
在Java文件轉化爲class文件的時候,可以對這個步驟進行操控,最終使得class文件與Java源文件有所不同

進入代碼環節。
比如我們有一個需求,統計某個模塊的耗時時間,一般情況下,我們都會這麼寫

package com.example.aopjiagoushejitest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import android.view.View;

import java.util.Random;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void mShake(View view) {
        long begin = System.currentTimeMillis();
        //隨機休眠兩毫秒以內
        SystemClock.sleep(new Random().nextInt(2000));
        long duringTime = System.currentTimeMillis() - begin;
        Log.i("zhang_xin",duringTime+"");
    }

    public void mVideo(View view) {
        long begin = System.currentTimeMillis();
        //隨機休眠兩毫秒以內
        SystemClock.sleep(new Random().nextInt(2000));
        long duringTime = System.currentTimeMillis() - begin;
        Log.i("zhang_xin",duringTime+"");

    }

    public void mAudio(View view) {
        long begin = System.currentTimeMillis();
        //隨機休眠兩毫秒以內
        SystemClock.sleep(new Random().nextInt(2000));
        long duringTime = System.currentTimeMillis() - begin;
        Log.i("zhang_xin",duringTime+"");

    }

    public void saySomething(View view) {
        long begin = System.currentTimeMillis();
        //隨機休眠兩毫秒以內
        SystemClock.sleep(new Random().nextInt(2000));
        long duringTime = System.currentTimeMillis() - begin;
        Log.i("zhang_xin",duringTime+"");

    }
}

佈局就是四個按鈕,這樣寫是不是太麻煩了,我們現在換成我們的面向切面編程。
首先配置我們的環境

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"


    defaultConfig {
        applicationId "com.example.aopjiagoushejitest"
        //版本至少爲24
        minSdkVersion 24
        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.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
    //添加依賴
    implementation 'org.aspectj:aspectjrt:1.9.4'
}
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.9",
                         "-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;
            }
        }
    }
}

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        google()
        jcenter()
        
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.3'
        //配置自己的構建環境
        classpath 'org.aspectj:aspectjtools:1.9.4'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
        
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

定義的註解

package com.example.aopjiagoushejitest;

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 BehaviorTrace {
    String value();
}

MainActivity

package com.example.aopjiagoushejitest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import android.view.View;

import java.lang.annotation.Retention;
import java.util.Random;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    @BehaviorTrace("shake")//切入點
    public void mShake(View view) {
    }
    @BehaviorTrace("video")//切入點
    public void mVideo(View view) {
    }
    @BehaviorTrace("audio")//切入點
    public void mAudio(View view) {
    }
    @BehaviorTrace("something")//切入點
    public void saySomething(View view) {
    }
}

關鍵類

package com.example.aopjiagoushejitest;

import android.os.SystemClock;
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.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

import java.util.Random;

//Aspect切入面:是切入點和通知的結合,加了這個標記,這個類就是一個切面了
@Aspect
public class BehaviorTraceAspect {
    //在切面中定義些規則
    //1,知道哪些切入點需要進行性能統計,即哪些方法添加了@BehaviorTrace註解
    //execution(註解名   註解用的地方)第一個*表示所有的類,第二個*表示所有的方法 ..表示無限參數
    @Pointcut("execution(@com.example.aopjiagoushejitest.BehaviorTrace *  *(..))")
    public void methodAnnottatedWithBehaviorTrace() {
    //添加該註解BehaviorTrace的地方都會執行這個方法
    }
    //2,對進入切面的註解(內容)如何處理
    //@Before 在切入點之前運行
//    @After 在切入點之後運行
    //@Around 在切入點前後都運行
    @Around("methodAnnottatedWithBehaviorTrace()")
    public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
        //通過鏈接點拿到方法的簽名
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        //拿到類名
        String className = methodSignature.getDeclaringType().getSimpleName();
        //拿到方法名
        String methodName = methodSignature.getName();
        //拿到註解對應的值
        String annoationName = methodSignature.getMethod().getAnnotation(BehaviorTrace.class).value();
        long begin = System.currentTimeMillis();
        Object result = joinPoint.proceed();//表示當前方法(有註釋的方法)的執行
        //隨機休眠兩毫秒以內
        SystemClock.sleep(new Random().nextInt(2000));
        long duringTime = System.currentTimeMillis() - begin;
        Log.d("zhang_xin", String.format("%s功能:%s類的%s方法執行了,用時%d ms",
                annoationName, className, methodName, duringTime));
        return result;
    }
}

大家回憶下我們使用的淘寶APP,當我們點擊某些按鈕的時候,他會提示你還未登錄,你需要登錄。我們常用的做法是你點擊按鈕之前判斷用戶是否登錄,如果沒有登錄就跳轉到登錄界面。這樣就會造成很多地方都會寫這個判斷邏輯。我們現在通過我們的AOP編程來實現這個功能,我們在需要登錄的功能作爲切入點,把這些切入點放入一個切面內統一管理。
我們現在先用動態代理的方式來做登錄

package com.example.logintest;

public interface ILogin {
    void toLogin();
}

package com.example.logintest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import java.lang.reflect.Proxy;

public class MainActivity extends AppCompatActivity implements ILogin{
     private ILogin proxyLogin;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        /**
         * 第一個參數:類加載器
         * 第二個參數:代理對象的目標類
         * 第三個參數:回調處理類
         */
       proxyLogin = (ILogin) Proxy.newProxyInstance(this.getClassLoader(),new Class[]{ILogin.class},new MyHandler(this,this));
    }

    public void me(View view) {
        proxyLogin.toLogin();
    }

    public void play(View view) {
        proxyLogin.toLogin();
    }

    @Override
    public void toLogin() {
        Toast.makeText(this,"已登錄,跳轉到個人中心界面",Toast.LENGTH_LONG).show();
    }
}

package com.example.logintest;

import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

public class LoginActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
    }

    public void loginEvent(View view) {
        SharePreferenceUtil.setBooleanSp(SharePreferenceUtil.IS_LOGIN, true, this);
        Toast.makeText(this, "登陸成功", Toast.LENGTH_SHORT).show();
        finish();
    }
}

package com.example.logintest;

import android.content.Context;
import android.content.Intent;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyHandler  implements InvocationHandler {
    private Object target;
    private Context context;

    public MyHandler(Object target, Context context) {
        this.target = target;
        this.context = context;
    }

    /**
     * 攔截Object對象的所有方法
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object ob = null;
        //如果已經登陸了
        if(SharePreferenceUtil.getBooleanSp(SharePreferenceUtil.IS_LOGIN,context)){
            //調用攔截的方法
            ob = method.invoke(target,args);
        }else{
            Intent intent = new Intent(context,LoginActivity.class);
            context.startActivity(intent);
        }
        return ob;
    }
}

package com.example.logintest;

import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;

public class SharePreferenceUtil {
    public static final String IS_LOGIN = "isLogin";

    public static boolean getBooleanSp(String key, Context context) {
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        return preferences.getBoolean(key, false);
    }

    public static void clearSharePref(String key, Context context) {
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        SharedPreferences.Editor editor = preferences.edit();
        editor.remove(key);
        editor.apply();
    }

    /* Boolean */
    public static void setBooleanSp(String key, Boolean value, Context context) {
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        SharedPreferences.Editor editor = prefs.edit();
        editor.putBoolean(key, value);
        editor.apply();
    }

}

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".LoginActivity">

    <Button
        android:text="登錄"
        android:onClick="loginEvent"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="me"
        android:text="我的淘寶"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="play"
        android:text="購買"/>


</LinearLayout>

我們看下AOP架構如何實現登錄

package com.dn_alan.myapplication.annotation;

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 LoginFilter {
    int userDefine() default 0;
}

package com.example.aopjiagoushejitest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    //這裏我就直接定義一個變量控制登錄與否,就不寫SharedPreference了
    public static boolean isLogin ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }


    public void quit(View view) {
    }
    //如果已經登錄,跳轉到我的購物車界面,這裏我就直接彈出一個吐絲
    //如果沒有登錄,跳轉到登錄界面
    @LoginFilter
    public void myShoppingCar(View view) {
        Toast.makeText(this,"這是我的購物車界面",Toast.LENGTH_LONG).show();
        isLogin = false;
    }
}

package com.example.aopjiagoushejitest;

import android.content.Context;

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.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

/**
 * 登錄切面
 */
@Aspect
public class LoginFilterAspect {
    @Pointcut("execution(@com.example.aopjiagoushejitest.LoginFilter * *(..))")
    public void loginFilter() {
    }

    @Around("loginFilter()")
    public void arroundLoginFilter(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        ILogin iLogin = LoginAssistant.getInstance().getiLogin();
        if (iLogin == null) {
            return;
        }
        Signature signature = proceedingJoinPoint.getSignature();
        if (signature instanceof MethodSignature) {
            MethodSignature methodSignature = (MethodSignature) signature;
            LoginFilter loginFilter = methodSignature.getMethod().getAnnotation(LoginFilter.class);
            if(loginFilter!=null){
                Context param = LoginAssistant.getInstance().getApplicationContext();
                if (iLogin.isLogin(param)) {
                    proceedingJoinPoint.proceed();
                } else {
                    iLogin.login(param, loginFilter.userDefine());
                }
            }else{
                //拋異常
            }
        }else{
            //拋異常,這裏省去
        }
    }
}

上面三各類是主要的類,看下其它的類在

package com.example.aopjiagoushejitest;

import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;


public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        LoginSDK.getInstance().init(this, iLogin);
    }

    ILogin iLogin = new ILogin() {
        @Override
        public void login(Context contextApplication, int define) {
            switch (define) {
                case 0:
                    Intent intent = new Intent(contextApplication,LoginActivity.class);
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    startActivity(intent);
                    break;
                case 1:
                    Toast.makeText(contextApplication, "您還沒有登錄,請登陸後執行", Toast.LENGTH_SHORT).show();
                    break;
                default:
                    Toast.makeText(contextApplication, "您還沒有登錄,請登陸後執行", Toast.LENGTH_SHORT).show();
                    break;
            }
        }

        @Override
        public boolean isLogin(Context contextApplication) {
            return MainActivity.isLogin;
        }

        @Override
        public void clearLoginStatue(Context contextApplication) {
                MainActivity.isLogin = false;
        }
    };
}

package com.example.aopjiagoushejitest;

import android.content.Context;

public class LoginSDK {
    private Context contextApplication;
    private LoginSDK() {
    }

    private static class Helper {
        private static LoginSDK loginSDK = new LoginSDK();
    }

    public static LoginSDK getInstance() {
        return Helper.loginSDK;
    }

    public void init(Context context,ILogin iLogin){
        contextApplication = context.getApplicationContext();
        LoginAssistant.getInstance().setApplicationContext(context);
        LoginAssistant.getInstance().setiLogin(iLogin);
    }
    /**
     * 單點登錄,驗證token失效的統一接入入口
     */
    public void serverTokenInvalidation(int userDefine) {
        LoginAssistant.getInstance().serverTokenInvalidation(userDefine);
    }
}

package com.example.aopjiagoushejitest;

import android.content.Context;

public class LoginAssistant {
    private LoginAssistant() {
    }

    private static class Helper {
        private static LoginAssistant loginAssistant = new LoginAssistant();
    }

    public static LoginAssistant getInstance() {
        return Helper.loginAssistant;
    }

    private ILogin iLogin;
    private Context applicationContext;

    public ILogin getiLogin() {
        return iLogin;
    }

    public void setiLogin(ILogin iLogin) {
        this.iLogin = iLogin;
    }

    public Context getApplicationContext() {
        return applicationContext;
    }

    public void setApplicationContext(Context applicationContext) {
        this.applicationContext = applicationContext;
    }
    public void serverTokenInvalidation(int userDefine){
        if(iLogin == null){
            return;
        }
        iLogin.clearLoginStatue(applicationContext);
        iLogin.login(applicationContext,userDefine);
    }
}

package com.example.aopjiagoushejitest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;

public class LoginActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
    }

    public void login(View view) {
        MainActivity.isLogin = true;
        this.finish();
    }
}

package com.example.aopjiagoushejitest;

import android.content.Context;

public interface ILogin {
      void login(Context contextApplication,int define);
      boolean isLogin(Context contextApplication);
      void clearLoginStatue(Context contextApplication);
}

AspectJ 切面註解中五種通知註解:@Before、@After、@AfterRunning、@AfterThrowing、@Around

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章