--摘自《android插件化開發指南》android
1.ActivityThread最終是經過Instrumentation啓動一個Activity的。而ActivityThread啓動Service並不藉助於Instrumentation,而是直接把Service反射出來就啓動了。Instrumentation只給Activity提供服務數組
2.通常預先在宿主app中建立10個StubService佔位就夠了微信
***startService的解決方案***app
首先把插件和宿主的dex合併ide
/** * 因爲應用程序使用的ClassLoader爲PathClassLoader * 最終繼承自 BaseDexClassLoader * 查看源碼得知,這個BaseDexClassLoader加載代碼根據一個叫作 * dexElements的數組進行, 所以咱們把包含代碼的dex文件插入這個數組 * 系統的classLoader就能幫助咱們找到這個類 * * 這個類用來進行對於BaseDexClassLoader的Hook * 類名太長, 不要吐槽. * @author weishu * @date 16/3/28 */
public final class BaseDexClassLoaderHookHelper { public static void patchClassLoader(ClassLoader cl, File apkFile, File optDexFile) throws IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException, NoSuchFieldException { // 獲取 BaseDexClassLoader : pathList
Object pathListObj = RefInvoke.getFieldObject(DexClassLoader.class.getSuperclass(), cl, "pathList"); // 獲取 PathList: Element[] dexElements
Object[] dexElements = (Object[]) RefInvoke.getFieldObject(pathListObj, "dexElements"); // Element 類型
Class<?> elementClass = dexElements.getClass().getComponentType(); // 建立一個數組, 用來替換原始的數組
Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1); // 構造插件Element(File file, boolean isDirectory, File zip, DexFile dexFile) 這個構造函數
Class[] p1 = {File.class, boolean.class, File.class, DexFile.class}; Object[] v1 = {apkFile, false, apkFile, DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0)}; Object o = RefInvoke.createObject(elementClass, p1, v1); Object[] toAddElementArray = new Object[] { o }; // 把原始的elements複製進去
System.arraycopy(dexElements, 0, newElements, 0, dexElements.length); // 插件的那個element複製進去
System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length); // 替換
RefInvoke.setFieldObject(pathListObj, "dexElements", newElements); } }
其次採用「欺上瞞下」的方法函數
public class AMSHookHelper { public static final String EXTRA_TARGET_INTENT = "extra_target_intent"; public static void hookAMN() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException { //獲取AMN的gDefault單例gDefault,gDefault是final靜態的
Object gDefault = RefInvoke.getStaticFieldObject("android.app.ActivityManagerNative", "gDefault"); // gDefault是一個 android.util.Singleton<T>對象; 咱們取出這個單例裏面的mInstance字段
Object mInstance = RefInvoke.getFieldObject("android.util.Singleton", gDefault, "mInstance"); // 建立一個這個對象的代理對象MockClass1, 而後替換這個字段, 讓咱們的代理對象幫忙幹活
Class<?> classB2Interface = Class.forName("android.app.IActivityManager"); Object proxy = Proxy.newProxyInstance( Thread.currentThread().getContextClassLoader(), new Class<?>[] { classB2Interface }, new MockClass1(mInstance)); //把gDefault的mInstance字段,修改成proxy
Class class1 = gDefault.getClass(); RefInvoke.setFieldObject("android.util.Singleton", gDefault, "mInstance", proxy); } public static void hookActivityThread() throws Exception { // 先獲取到當前的ActivityThread對象
Object currentActivityThread = RefInvoke.getStaticFieldObject("android.app.ActivityThread", "sCurrentActivityThread"); // 因爲ActivityThread一個進程只有一個,咱們獲取這個對象的mH
Handler mH = (Handler) RefInvoke.getFieldObject(currentActivityThread, "mH"); //把Handler的mCallback字段,替換爲new MockClass2(mH)
RefInvoke.setFieldObject(Handler.class, mH, "mCallback", new MockClass2(mH)); } }
其中,HookService,讓AMS啓動StubService的實如今類MockClass1上編碼
class MockClass1 implements InvocationHandler { private static final String TAG = "MockClass1"; // 替身StubService的包名
private static final String stubPackage = "jianqiang.com.activityhook1"; Object mBase; public MockClass1(Object base) { mBase = base; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Log.e("bao", method.getName()); if ("startService".equals(method.getName())) { // 只攔截這個方法 // 替換參數, 任你所爲;甚至替換原始StubService啓動別的Service偷樑換柱 // 找到參數裏面的第一個Intent 對象
int index = 0; for (int i = 0; i < args.length; i++) { if (args[i] instanceof Intent) { index = i; break; } } //get StubService form UPFApplication.pluginServices
Intent rawIntent = (Intent) args[index]; String rawServiceName = rawIntent.getComponent().getClassName(); String stubServiceName = UPFApplication.pluginServices.get(rawServiceName); // replace Plugin Service of StubService
ComponentName componentName = new ComponentName(stubPackage, stubServiceName); Intent newIntent = new Intent(); newIntent.setComponent(componentName); // Replace Intent, cheat AMS
args[index] = newIntent; Log.d(TAG, "hook success"); return method.invoke(mBase, args); } else if ("stopService".equals(method.getName())) { // 只攔截這個方法 // 替換參數, 任你所爲;甚至替換原始StubService啓動別的Service偷樑換柱 // 找到參數裏面的第一個Intent 對象
int index = 0; for (int i = 0; i < args.length; i++) { if (args[i] instanceof Intent) { index = i; break; } } //get StubService form UPFApplication.pluginServices
Intent rawIntent = (Intent) args[index]; String rawServiceName = rawIntent.getComponent().getClassName(); String stubServiceName = UPFApplication.pluginServices.get(rawServiceName); // replace Plugin Service of StubService
ComponentName componentName = new ComponentName(stubPackage, stubServiceName); Intent newIntent = new Intent(); newIntent.setComponent(componentName); // Replace Intent, cheat AMS
args[index] = newIntent; Log.d(TAG, "hook success"); return method.invoke(mBase, args); } return method.invoke(mBase, args); } }
第2,AMS被欺騙後,它本來會通知APP啓動StubService,而咱們要Hook掉ActivityThread的mH對象的mCallback對象,仍然截獲它的handleMessage方法(handleCreateService方法),具體實如今MockClass2中spa
class MockClass2 implements Handler.Callback { Handler mBase; public MockClass2(Handler base) { mBase = base; } @Override public boolean handleMessage(Message msg) { Log.d("baobao4321", String.valueOf(msg.what)); switch (msg.what) { // ActivityThread裏面 "CREATE_SERVICE" 這個字段的值是114 // 原本使用反射的方式獲取最好, 這裏爲了簡便直接使用硬編碼
case 114: handleCreateService(msg); break; } mBase.handleMessage(msg); return true; } private void handleCreateService(Message msg) { // 這裏簡單起見,直接取出插件Servie
Object obj = msg.obj; ServiceInfo serviceInfo = (ServiceInfo) RefInvoke.getFieldObject(obj, "info"); String realServiceName = null; for (String key : UPFApplication.pluginServices.keySet()) { String value = UPFApplication.pluginServices.get(key); if(value.equals(serviceInfo.name)) { realServiceName = key; break; } } serviceInfo.name = realServiceName; } }
在宿主中調用插件
Intent intent = new Intent(); intent.setComponent( new ComponentName("jianqiang.com.testservice1", "jianqiang.com.testservice1.MyService1")); startService(intent); Intent intent = new Intent(); intent.setComponent( new ComponentName("jianqiang.com.testservice1", "jianqiang.com.testservice1.MyService1")); stopService(intent);
***bindService的解決方案***代理
只要在實現類MockClass1中增長
else if ("bindService".equals(method.getName())) { // 找到參數裏面的第一個Intent 對象
int index = 0; for (int i = 0; i < args.length; i++) { if (args[i] instanceof Intent) { index = i; break; } } Intent rawIntent = (Intent) args[index]; String rawServiceName = rawIntent.getComponent().getClassName(); String stubServiceName = UPFApplication.pluginServices.get(rawServiceName); // replace Plugin Service of StubService
ComponentName componentName = new ComponentName(stubPackage, stubServiceName); Intent newIntent = new Intent(); newIntent.setComponent(componentName); // Replace Intent, cheat AMS
args[index] = newIntent; Log.d(TAG, "hook success"); return method.invoke(mBase, args); }
宿主中調用
Intent intent = new Intent(); intent.setComponent( new ComponentName("jianqiang.com.testservice1", "jianqiang.com.testservice1.MyService2")); bindService(intent, conn, Service.BIND_AUTO_CREATE); unbindService(conn);
問1:爲何不在unbind的時候欺騙AMS?
由於unbind語法是unbindService(conn),AMS會根據conn來找到對應的Service,因此咱們不須要把MyService2替換爲StubService2
問2:爲何在MockClass2中不須要吧StubService2切換回MyService2?
由於bindService是先走handleCreateService再走handleBindService方法。在handleCreateService方法中已經將StubService2切換回MyService2了,因此後面不須要切換了。
歡迎關注個人微信公衆號:安卓圈