Service插件化解决方案
Posted anni-qianqian
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Service插件化解决方案相关的知识,希望对你有一定的参考价值。
--摘自《android插件化开发指南》
1.ActivityThread最终是通过Instrumentation启动一个Activity的。而ActivityThread启动Service并不借助于Instrumentation,而是直接把Service反射出来就启动了。Instrumentation只给Activity提供服务
2.一般预先在宿主app中创建10个StubService占位就够了
***startService的解决方案***
首先把插件和宿主的dex合并
/** * 由于应用程序使用的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中
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了,所以后面不需要切换了。
以上是关于Service插件化解决方案的主要内容,如果未能解决你的问题,请参考以下文章
滴滴插件化VirtualAPK框架原理解析之Service 管理
Nginx——Nginx启动报错Job for nginx.service failed because the control process exited with error code(代码片段