5、Hook Handler 代理类
静态代理 ActivityThread 中的 final H mH = new H() 成员 ;
在 AMS 执行完毕后 , 主线程 ActivityThread 中创建 Activity 实例对象之间 , 再将插件包 Activity 替换回去 ;
package kim.hsl.plugin; import android.content.Intent; import android.os.Handler; import android.os.Message; import android.util.Log; import java.lang.reflect.Field; import java.util.List; /** * 静态代理 ActivityThread 中的 final H mH = new H() 成员 */ public class HandlerProxy implements Handler.Callback { public static final int EXECUTE_TRANSACTION = 159; @Override public boolean handleMessage(Message msg) { if (msg.what == EXECUTE_TRANSACTION) { // 反射 android.app.servertransaction.ClientTransaction 类 // 该类中有如下成员变量 // private List<ClientTransactionItem> mActivityCallbacks; // 这个集合中存放的就是 android.app.servertransaction.LaunchActivityItem 类实例 // 不能直接获取 LaunchActivityItem 实例 , 否则会出错 Class<?> clientTransactionClass = null; try { clientTransactionClass = Class.forName("android.app.servertransaction.ClientTransaction"); } catch (ClassNotFoundException e) { e.printStackTrace(); } // 验证当前的 msg.obj 是否是 ClientTransaction 类型 , 如果不是则不进行 Intent 替换 // 通过阅读源码可知 , 在 ActivityThread 的 mH 中, 处理 EXECUTE_TRANSACTION 信号时 // 有 final ClientTransaction transaction = (ClientTransaction) msg.obj; if (!clientTransactionClass.isInstance(msg.obj)) { return true; } // 反射获取 // private List<ClientTransactionItem> mActivityCallbacks; 成员字段 Field mActivityCallbacksField = null; try { mActivityCallbacksField = clientTransactionClass.getDeclaredField("mActivityCallbacks"); } catch (NoSuchFieldException e) { e.printStackTrace(); } // 设置成员字段可见性 mActivityCallbacksField.setAccessible(true); // 反射获取 // private List<ClientTransactionItem> mActivityCallbacks; 成员字段实例 Object mActivityCallbacksObject = null; try { mActivityCallbacksObject = mActivityCallbacksField.get(msg.obj); } catch (IllegalAccessException e) { e.printStackTrace(); } // 将 // private List<ClientTransactionItem> mActivityCallbacks; 成员字段实例 // 强转为 List 类型 , 以用于遍历 List mActivityCallbacksObjectList = (List) mActivityCallbacksObject; for (Object item : mActivityCallbacksObjectList) { Class<?> clazz = item.getClass(); // 只处理 LaunchActivityItem 的情况 if (clazz.getName().equals("android.app.servertransaction.LaunchActivityItem")) { // 获取 LaunchActivityItem 的 private Intent mIntent; 字段 // 该 Intent 中的 Activity 目前是占坑 Activity 即 StubActivity // 需要在实例化之前 , 替换成插件包中的 Activity Field mIntentField = null; try { mIntentField = clazz.getDeclaredField("mIntent"); } catch (NoSuchFieldException e) { e.printStackTrace(); } mIntentField.setAccessible(true); // 获取 LaunchActivityItem 对象的 mIntent 成员 , 即可得到 Activity 跳转的 Intent Intent intent = null; try { intent = (Intent) mIntentField.get(item); } catch (IllegalAccessException e) { e.printStackTrace(); } // 获取 启动 插件包 组件的 Intent Intent pluginIntent = intent.getParcelableExtra("pluginIntent"); if (pluginIntent != null) { // 使用 包含插件包组件信息的 Intent , // 替换之前在 Ams 启动之前设置的 占坑 StubActivity 对应的 Intent try { mIntentField.set(item, pluginIntent); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } } return false; } }
6、Hook Instrumentation 代理类
通过 Hook 方式修改 Activity 中的 Resources 资源 ;
package kim.hsl.plugin; import android.app.Activity; import android.app.Application; import android.app.Instrumentation; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.Resources; import android.os.Bundle; import android.os.IBinder; import java.lang.reflect.Field; public class InstrumentationProxy extends Instrumentation { private static final String TAG = "InstrumentationProxy"; /** * 持有被代理对象 * 有一些操作需要使用原来的 Instrumentation 进行操作 */ private final Instrumentation mBase; /** * 在构造方法中注入被代理对象 * @param mBase */ public InstrumentationProxy(Instrumentation mBase) { this.mBase = mBase; } public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { ActivityResult result = null; // 反射调用 Instrumentation mBase 成员的 execStartActivity 方法 result = Reflector.on("android.app.Instrumentation") .method("execStartActivity", // 反射的方法名 Context.class, // 后续都是方法的参数类型 IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class) .with(mBase) .call(who, // 后续都是传入 execStartActivity 方法的参数 contextThread, token, target, intent, requestCode, options); return result; } /** * 在该方法中 , 可以拿到 Activity , 通过反射修改 Activity 中的 Resources 成员变量 * @param cl * @param className * @param intent * @return * @throws ClassNotFoundException * @throws IllegalAccessException * @throws InstantiationException */ public Activity newActivity(ClassLoader cl, String className, Intent intent) throws ClassNotFoundException, IllegalAccessException, InstantiationException { Activity activity = mBase.newActivity(cl, className, intent); // 替换 Activity 中的 Resources exchangeResourcesOfActivity(activity, intent); return activity; } /** * 在该方法中 , 可以拿到 Activity , 通过反射修改 Activity 中的 Resources 成员变量 * @param clazz * @param context * @param token * @param application * @param intent * @param info * @param title * @param parent * @param id * @param lastNonConfigurationInstance * @return * @throws IllegalAccessException * @throws InstantiationException */ @Override public Activity newActivity(Class<?> clazz, Context context, IBinder token, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, Object lastNonConfigurationInstance) throws IllegalAccessException, InstantiationException { Activity activity = mBase.newActivity(clazz, context, token, application, intent, info, title, parent, id, lastNonConfigurationInstance); // 替换 Activity 中的 Resources exchangeResourcesOfActivity(activity, intent); return activity; } /** * 反射 Activity , 并设置 Activity 中 Resources 成员变量 * @param activity * @param intent */ private void exchangeResourcesOfActivity(Activity activity, Intent intent) { // 这里注意 : 所有的 Activity 创建 , 都会过这个方法 , 这里只将插件包中的 Activity 的资源替换 // 这里要做一个判断 // 不能修改宿主应用的资源 // 只有插件包中的 Activity 才进行相应的修改 // 在调用插件包中的组件时 , 在 Intent 中传入一个 isPlugin 变量 , // 也可以传入插件的标志位 , 区分不同的插件包 // 这里只有一个插件包 , 只设置一个 Boolean 变量即可 if (!intent.getBooleanExtra("isPlugin", false)) return; // 获取插件资源 Resources pluginResources = PluginManager.getInstance(activity).getResources(); // 反射 ContextThemeWrapper 类 , Activity 是 ContextThemeWrapper 的子类 // Resources mResources 成员定义在 ContextThemeWrapper 中 Class<?> contextThemeWrapperClass = null; try { contextThemeWrapperClass = Class.forName("android.view.ContextThemeWrapper"); } catch (ClassNotFoundException e) { e.printStackTrace(); } // 反射获取 ContextThemeWrapper 类的 mResources 字段 Field mResourcesField = null; try { mResourcesField = contextThemeWrapperClass.getDeclaredField("mResources"); } catch (NoSuchFieldException e) { e.printStackTrace(); } // 设置字段可见性 mResourcesField.setAccessible(true); // 将插件资源设置到插件 Activity 中 try { mResourcesField.set(activity, PluginManager.getInstance(activity).getResources()); } catch (IllegalAccessException e) { e.printStackTrace(); } } }
7、占坑 Activity
一个普通的 Activity , 在清单文件中正常注册 , 在 Hook Activity 启动过程中 , 起到占坑作用 ;
package kim.hsl.plugin; import androidx.appcompat.app.AppCompatActivity; import android.app.Activity; import android.os.Bundle; import android.util.Log; /** * 该 Activity 主要用于占位 * 实际上使用插件包中的 Activity 替换该 Activity */ public class StubActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_stub); Log.i("plugin", "启动了占坑 Activity"); } }
四、插件应用
插件应用是普通的应用 , 与正常应用没有区别 , 不用作特别的操作 , 这也是 Hook 插件化框架的优点 , 对代码的侵入性很小 , 开发者可以按照正常的开发逻辑 , 开发插件应用 ;
package com.example.plugin; import androidx.appcompat.app.AppCompatActivity; import android.app.Activity; import android.content.res.Resources; import android.os.Bundle; import android.util.Log; public class MainActivity extends Activity { private static final String TAG = "plugin_MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.i("plugin", "启动了插件 Activity"); } public void log(){ Log.i(TAG, "Plugin MainActivity"); } /* // 这种方式侵入代码 , 造成开发的差异性 , 建议使用 Hook 加载插件资源 @Override public Resources getResources() { if (getApplication() != null && getApplication().getResources() != null) { return getApplication().getResources(); } return super.getResources(); }*/ }