天天看點

使用ASM實作方法攔截架構,再也不用寫重複代碼了。MehodInterceptor注意事項調試原理方法的複制

MehodInterceptor

項目位址

MehodInterceptor

序言

MehodInterceptor是一個使用ASM來動态修改位元組碼,以達到方法攔截。通過該架構,可以控制某個方法是否執行。

比如某些業務有一些通用的判斷邏輯:比如彈出确認提示,判斷使用者是否登入,判斷APP是否具有某些權限。隻有這些判斷通過,才會執行該方法。否則不執行。

這些通用的邏輯,現在可以通過注解的方式添加到方法上。

比如這樣:

使用ASM實作方法攔截架構,再也不用寫重複代碼了。MehodInterceptor注意事項調試原理方法的複制

不再需要寫其他的代碼,最後的效果是這樣的。

使用ASM實作方法攔截架構,再也不用寫重複代碼了。MehodInterceptor注意事項調試原理方法的複制

使用

該架構已經釋出到 mavenCentral()了。隻需要在根目錄的gradle內建。

buildscript {
    repositories {
        google()
        //架構所在的中央倉庫
        mavenCentral()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.1.0"
        //本架構
        classpath 'io.github.zhuguohui:method-interceptor:1.0.1'

    }
}
           

在需要使用的module中如下配置即可

apply plugin:"com.zhuguohui.methodinterceptor"

methodInterceptor {
    include= ["com.example.myapplication"]
    handlers= [
            //處理提醒
            "com.example.myapplication.handler.confirm.Confirm":"com.example.myapplication.handler.confirm.ConfirmUtil",
            //處理登入
            "com.example.myapplication.handler.login.RequestLogin":"com.example.myapplication.handler.login.LoginRequestHandler",
            //處理權限
           "com.example.myapplication.handler.permission.RequestPermission":"com.example.myapplication.handler.permission.PermissionRequestHandler",
            //test
           "com.example.myapplication.handler.test.Test":"com.example.myapplication.handler.test.TestHandler"]
}
           

參數說明

include 傳入你想處理的類所在的包名本質上是一個set,可以傳入多個
handlers 傳入一個map,key是你定義的注解,value是注解的處理器。如果一個方法被該注解注釋,就修改位元組碼,在改方法被執行的時候。将該方法傳遞給處理器。由處理控制執行

示例

注解

注解中定義值,這些值在方法被調用的時候會被格式化成JSON資料傳回給處理器。

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface Confirm {
    String value();
}

           

處理器必須有兩個靜态方法。onMethodIntercepted 和 onMethodInterceptedError

方法參數

annotationJson 被格式化成JSON的注解中的值
caller 方法的擁有者,方法聲明在那個類中,就會傳遞該類的this指針
method 被注解注釋的方法
objects 方法執行的所有參數

處理器

public class ConfirmUtil {

  static class ConfirmValue{
      String value;
      boolean showToast;

      public String getValue() {
          return value;
      }

      public void setValue(String value) {
          this.value = value;
      }

      public boolean isShowToast() {
          return showToast;
      }

      public void setShowToast(boolean showToast) {
          this.showToast = showToast;
      }
  }

    public static void onMethodIntercepted(String annotationJson, Object caller, Method method, Object... objects) {

        Context context = (Context) caller;
        ConfirmValue cv=new Gson().fromJson(annotationJson,ConfirmValue.class);
        new AlertDialog.Builder(context)
                .setTitle(cv.value)
                .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        try {
                            method.setAccessible(true);
                            method.invoke(caller,objects);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        dialog.dismiss();
                    }
                }).setNegativeButton("取消", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
            }
        }).create().show();
    }


    public static void onMethodInterceptedError(Object caller,Exception e){
        e.printStackTrace();
        Toast.makeText((Context) caller,"處理注解失敗:"+e.getMessage(),Toast.LENGTH_SHORT).show();
    }




}

           

注意事項

  1. 目前注解不能修飾靜态方法 (後續考慮支援)
  2. 目前方法參數不支援基本類型 (後續會考慮支援)
  3. 函數不能有傳回值,需要傳回的可以通過傳遞接口利用回調實作 (使用場景決定的)

如果是基本類型,需要使用對應的包裝類型。比如

//不支援
    public void add(int a,int b){
      
    }

    //支援
    public void add(Integer a,Integer b){

    }
           

調試

因為涉及到位元組碼,如果感覺生成的方法不對可以build以後再這個位置檢視生成的代碼。

使用ASM實作方法攔截架構,再也不用寫重複代碼了。MehodInterceptor注意事項調試原理方法的複制
使用ASM實作方法攔截架構,再也不用寫重複代碼了。MehodInterceptor注意事項調試原理方法的複制

原理

簡單的說一下,如果一個方法被如下注釋

@RequestLogin
    public void comment(Context context) {
        Toast.makeText(context, "評論成功", Toast.LENGTH_SHORT).show();
    }
           

通過ASM 會把正在的方法 comment 複制成一個其他名字的方法。

private void _confirm_index46_commit(Context context) {
        Toast.makeText(context, "簽發成功", 0).show();
    }
           

而原來的方法會被改造成這樣

public void comment(Context context) {
        try {
            Method var9 = null;
            String var3 = "_requestlogin_index48_comment";
            String var4 = "{}";
            Method[] var5 = this.getClass().getDeclaredMethods();

            for(int var6 = 0; var6 < var5.length; ++var6) {
                if (var5[var6].getName().equals(var3)) {
                    var9 = var5[var6];
                    break;
                }
            }

            if (var9 == null) {
                throw new RuntimeException("don't find method  [" + var3 + "] in class [" + this.getClass().getName() + "]");
            }

            LoginRequestHandler.onMethodIntercepted(var4, this, var9, new Object[]{context});
        } catch (Exception var8) {
            Exception var2 = var8;

            try {
                LoginRequestHandler.onMethodInterceptedError(this, var2);
            } catch (Exception var7) {
                var7.printStackTrace();
            }
        }

    }
           

當然以上的邏輯是可以疊加的,也就是注解可以疊加使用。

@Confirm("确定執行分享操作")
    @RequestLogin
    @RequestPermission( {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.ACCESS_FINE_LOCATION})
    public void share(Context context) {
        Toast.makeText(context, "分享成功", Toast.LENGTH_SHORT).show();
    }
           

最後真正的方法是這樣

private void _requestpermission_index54__requestlogin_index53__confirm_index52_share(Context context) {
        Toast.makeText(context, "分享成功", 0).show();
    }
           

位元組碼

通過定義一個gradle插件,在Android 的插件中注冊了一個Transform ,Transform的作用就是在class 到dex之間進行處理。

使用ASM實作方法攔截架構,再也不用寫重複代碼了。MehodInterceptor注意事項調試原理方法的複制

而修改class使用的是ASM架構。

使用一下以下插件,可以生成ASM代碼。

使用ASM實作方法攔截架構,再也不用寫重複代碼了。MehodInterceptor注意事項調試原理方法的複制

插件效果

使用ASM實作方法攔截架構,再也不用寫重複代碼了。MehodInterceptor注意事項調試原理方法的複制

最後就是慢工出細活。通過Android Studio的對比工具來對比不同方法的ASM代碼的差異,實作功能。

使用ASM實作方法攔截架構,再也不用寫重複代碼了。MehodInterceptor注意事項調試原理方法的複制
使用ASM實作方法攔截架構,再也不用寫重複代碼了。MehodInterceptor注意事項調試原理方法的複制

方法的複制

ASM 提供了兩套API架構,一套把class解析成一顆Node樹,一套把class解析成各種事件。不明白的可以想想xml的解析。

在基于事件的API中我們隻需要把遇到的指令,拷貝到另一個方法中就行了。

使用ASM實作方法攔截架構,再也不用寫重複代碼了。MehodInterceptor注意事項調試原理方法的複制

剩下的就是慢工出細活了。