Android 極簡反射教程

原文鏈接:http://www.woaitqs.cc/2016/07/14/android-reflection/

光的反射

Java 反射簡介

Java 程序的運行需要相應的環(huán)境(Java Runtime Environment), 而這其中最有名的就是 JVM,JVM 提供了動態(tài)運行字節(jié)碼的能力,除了 JVM 幫我們做鏈接、加載字節(jié)碼相關的事情外,也通過反射提供給我們動態(tài)修改的能力。反射使得我們能夠在運行時,不知道類名、方法名的情況下查看其中的接口、方法和字段等信息,另一方面,也可以動態(tài)調用方法、新建對象,甚至篡改字段值。

那介紹了反射是干嘛之后,反射能在實際的工作中發(fā)揮什么作用嗎?Android 系統(tǒng)在設計的時候,出于安全和架構的考慮,利用了 Java 權限相關的東西(private,package等等,以及 @hide 這個注解)使得我們無法訪問某些字段,或者方法。但在實際開發(fā)過程中,這些隱藏的字段或者方法卻能提供給我們非常想要的特性。在這種矛盾的情況下,反射就能滿足我們的需求,像是打開隱藏關卡的一把鑰匙。

總結起來就是,反射提供了一種與 Class 文件進行動態(tài)交互的機制。例如在下面的入口函數中,就可以看到 HashMapClass 里所有的方法。

public class HashMapClass extends HashMap {

  public static void main(String[] args) {
    Method[] methods = HashMapClass.class.getMethods();

    for (Method method : methods) {
      System.out.println("method name is " + method.getName());
    }
  }

}

Class 類簡介

在進行接下來的反射教程中,首先應該了解 Class Object。Java 中所有的類型,包括 int、float 等基本類型,都有與之相關的 Class 對象。如果知道對應的 Class name,可以通過 Class.forName() 來構造相應的 Class 對象,如果沒有對應的 class,或者沒有加載進來,那么會拋出 ClassNotFoundException 對象。

Class 封裝了一個類所包含的信息,主要的接口如下:

    try {
      Class mClass = Class.forName("java.lang.Object");

      // 不包含包名前綴的名字
      String simpleName = mClass.getSimpleName();

      // 類型修飾符, private, protect, static etc.
      int modifiers = mClass.getModifiers();
      // Modifier 提供的一些用于判讀類型的靜態(tài)方法.
      Modifier.isPrivate(modifiers);

      // 父類的信息
      Class superclass = mClass.getSuperclass();

      // 構造函數
      Constructor[] constructors = mClass.getConstructors();

      // 字段類型
      Field[] fields = mClass.getFields();
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }

常用反射方法

下面列舉一些反射常見的應用場景,主要從 Student 這個類進行入手。

public class Student {

  private final String name;
private int grade = 1;

  public Student(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }

  private int getGrade() {
    return grade;
  }
  
  private void goToSchool() {
    System.out.println(name + " go to school!");
  }
}

1)反射構建 Student 對象

try {
  Class studentClass = Student.class;
  
  // 參數類型為一個 String 的構造函數
  Constructor constructor = studentClass.getConstructor(new Class[]{String.class});
  
  // 實例化 student 對象
  Student student = (Student)constructor.newInstance("Li Lei");
  System.out.print(student.getName());
} catch (ReflectiveOperationException e) {
  e.printStackTrace();
}

2)反射修改私有變量

try {
  Student student = new Student("Han MeiMei");
  System.out.println("origin grade is " + student.getGrade());

  Class studentClass = Student.class;
  // 獲取聲明的 grade 字段,這里要注意 getField 和 getDeclaredField 的區(qū)別
  Field gradeField = studentClass.getDeclaredField("grade");
  
  // 如果是 private 或者 package 權限的,一定要賦予其訪問權限
  gradeField.setAccessible(true);
  
  // 修改 student 對象中的 Grade 字段值
  gradeField.set(student, 2);
  System.out.println("after reflection grade is " + student.getGrade());

} catch (ReflectiveOperationException e) {
  e.printStackTrace();
}

3)反射調用私有方法

try {
  Student student = new Student("Han MeiMei");
  
  // 獲取私有方法,同樣注意 getMethod 和 getDeclaredMethod 的區(qū)別
  Method goMethod = Student.class.getDeclaredMethod("goToSchool", null);
  // 賦予訪問權限
  goMethod.setAccessible(true);

  // 調用 goToSchool 方法。
  goMethod.invoke(student, null);

} catch (ReflectiveOperationException e) {
  e.printStackTrace();
}

Android 反射應用示例

學以致用,現(xiàn)在我們來通過實際的例子,來看看如何利用 Java 的反射特性來完成一些牛逼的功能。設想我們想通過插件的方式,來啟動一個未注冊的 Activity,這就會涉及到很多問題,其中之一就是如何賦予這些插件 Activity 生命周期。這個例子就是通過反射的方式,來手動地進行 Activity 生命周期的通知。

1)了解并熟悉代碼細節(jié)

要實現(xiàn)上述的功能,第一步就是要知道 Activity 的生命周期是如何運作的,要對代碼細節(jié)有所了解。因為反射所操作的對象是具體的 Class 對象,如果不清楚源碼細節(jié),反射將無從說起。

篇幅所限,具體的原理又較為復雜,這里列出鏈接 Android 插件化原理解析——Activity生命周期管理, 有興趣的同學可以自行查看,在這只進行大體上的說明。

Activity 生命周期與 ActivityThread 息息相關,我們來進行各個突破,先看看 Activity 是怎么啟動的。當需要啟動 Activity 時,ActivityManagerService 會通過 Binder 機制向 ActivityThread 發(fā)送消息,經過鏈式地調用后,會執(zhí)行到scheduleLaunchActivity 這個方法,我們看看其內部的實現(xiàn)。

// we use token to identify this activity without having to send the
// activity itself back to the activity manager. (matters more with ipc)
@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
        ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
        CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
        int procState, Bundle state, PersistableBundle persistentState,
        List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
        boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {

    updateProcessState(procState, false);

    ActivityClientRecord r = new ActivityClientRecord();

    r.token = token;
    r.ident = ident;
    r.intent = intent;
    r.referrer = referrer;
    r.voiceInteractor = voiceInteractor;
    r.activityInfo = info;
    r.compatInfo = compatInfo;
    r.state = state;
    r.persistentState = persistentState;

    r.pendingResults = pendingResults;
    r.pendingIntents = pendingNewIntents;

    r.startsNotResumed = notResumed;
    r.isForward = isForward;

    r.profilerInfo = profilerInfo;

    r.overrideConfig = overrideConfig;
    updatePendingConfiguration(curConfig);

    sendMessage(H.LAUNCH_ACTIVITY, r);
}

注意最后的 sendMessage 方法,說明內部是采用的 handler 機制來進行通信的。在這篇文章中 Android 應用進程啟動流程 提及到當應用進程啟動后,會調用 ActivityThread 的 main 方法,并在這個方法中進行相應的消息循環(huán)初始化,其后在主線程上的消息傳遞都是通過 ActivityThread 中的 H 這個內部來進行初始化,這里的 H 就是可能的突破口之一。

private class H extends Handler {
    public static final int LAUNCH_ACTIVITY         = 100;
    public static final int PAUSE_ACTIVITY          = 101;
    public static final int PAUSE_ACTIVITY_FINISHING= 102;
    public static final int STOP_ACTIVITY_SHOW      = 103;
    public static final int STOP_ACTIVITY_HIDE      = 104;
    public static final int SHOW_WINDOW             = 105;
    public static final int HIDE_WINDOW             = 106;
    public static final int RESUME_ACTIVITY         = 107;
    public static final int SEND_RESULT             = 108;
    public static final int DESTROY_ACTIVITY        = 109;
    public static final int BIND_APPLICATION        = 110;
    public static final int EXIT_APPLICATION        = 111;
    public static final int NEW_INTENT              = 112;
    public static final int RECEIVER                = 113;
    public static final int CREATE_SERVICE          = 114;
    public static final int SERVICE_ARGS            = 115;
    public static final int STOP_SERVICE            = 116;
    
    //......
    
    public void handleMessage(Message msg){
        // ...
    }
}

既然發(fā)現(xiàn)通信是通過 H 這個 Handler 來完成的,那么再看看 Handler 的實現(xiàn)原理,這里也有一篇文章供參考, Android Handler機制全解析 。Handler 在內部維護著一個 callback 對象,當有消息發(fā)生時,會通過這個 callback 往外發(fā)送消息。

/**
 * Handle system messages here.
 */
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

如果能夠替換 callback 變量,這樣消息就可以傳遞到替換后的 callback 里了。這樣是否能達到我們的目的?

2) 驗證是否滿足反射條件

如果我們反射的對象,擁有多個實例,那么我們就需要在不同的地方進行處理,顯然這樣會額外增加實現(xiàn)的復雜度,因而反射盡量在 單例或者靜態(tài) 實例上完成,代碼的復雜度能提升不少。

在前面提到了通過替換 callback 的方式,這樣是否可行,我們來驗證下。首先 H 是放置在 ActivityThread 這個類里面的,而 ActivityThread 運行的線程就是主線程,我們知道每一個應用都擁有一個主線程,因而這里的 ActivityThread 只存在一份,進而也可以保證 H 的唯一性。

另一方面,ActivityThread 在內部也維護了 currentActivityThread 這個變量,雖然由于 API 的訪問限制,不能直接訪問,但也同樣可以由反射拿到。

至此,可以證明這種方式理論上是可以成功的。

3)實施代碼細節(jié),并在合適的地方進行代碼注入

首先,我們自定義出自定義的 callback 對象,這個 callback 作為 H 中 callback 的代理,這里需要注意的是 msg 的定義要和底層保持一致,代碼如下:

public class LaunchCallback implements Handler.Callback {

  public static final int LAUNCH_ACTIVITY = 100;

  private Handler.Callback originCallback;

  public LaunchCallback(Handler.Callback originCallback) {
    this.originCallback = originCallback;
  }

  @Override
  public boolean handleMessage(Message msg) {

    if (msg.what == LAUNCH_ACTIVITY) {
      Toast.makeText(
          VApp.getApp().getApplicationContext(),
          "activity is going to launch! ", Toast.LENGTH_SHORT).show();
    }

    return originCallback.handleMessage(msg);
  }

}

通過前文提及的反射方法,將運行的 callback 替換為自定義的 callback,代碼如下:

public class InjectTool {

  public static void dexInject() {
    try {

      // 通過反射調用 ActivityThread 的靜態(tài)方法, 獲取 currentActivityThread
      Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
      Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
      currentActivityThreadMethod.setAccessible(true);
      Object currentActivityThread = currentActivityThreadMethod.invoke(null);

      // 獲取 currentActivityThread 這個示例中的 mH
      Field handlerField = activityThreadClass.getDeclaredField("mH");
      handlerField.setAccessible(true);
      Handler handler = (Handler) handlerField.get(currentActivityThread);

      // 修改 mH 中的 callback 字段
      Field callbackField = Handler.class.getDeclaredField("mCallback");
      callbackField.setAccessible(true);
      Handler.Callback callback = (Handler.Callback) callbackField.get(handler);

      callbackField.set(handler, new LaunchCallback(callback));
    } catch (IllegalArgumentException | NoSuchMethodException | IllegalAccessException
        | InvocationTargetException | ClassNotFoundException | NoSuchFieldException e) {
      e.printStackTrace();
    }
  }

}

在 Application 中進行注入,完成修改的落地

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    InjectTool.dexInject();
}

實際運行的效果,如圖所示:

activity launch

activity 其他的生命周期也可以同樣處理,這里就不再贅述了。

4)反射使用總結

在通過反射實現(xiàn)相關功能的時候,第一件事情就是認真地閱讀源碼,理清其中的脈絡,其后找尋其中的突破點,這些點一般為 static 方法或者單例對象,最后才是代碼落地。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容