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插件化解决方案的更多相关文章
- BroadcastReceiver插件化解决方案
--摘自<android插件化开发指南> 1.静态广播和动态广播仅区别于注册方式的不同.静态广播的注册信息保存在PMS中,动态广播的注册信息保存在AMS中 2.发送广播,也就是Contex ...
- ContentProvider插件化解决方案
--摘自<android插件化开发指南> 1.当要传输的数据量大小不超过1M的时候,使用Binder:数据量超过1M时,Binder就搞不定了,需要ContentProvider 2.Co ...
- Activity插件化解决方案
--摘自<android插件化开发指南> 1.宿主App加载插件中的类 2.最简单的插件化方案就是在宿主的androidmanifest.xml中申明插件中的四大组件 把插件dex合并到宿 ...
- [置顶]
滴滴插件化VirtualAPK框架原理解析(二)之Service 管理
在前一篇博客滴滴插件化框架VirtualAPK原理解析(一)之插件Activity管理 中VirtualAPK是如何对Activity进行管理的,本篇博客,我们继续来学习这个框架,这次我们学习的是如何 ...
- Android插件化的兼容性(上):Android O的适配
首先声明,<Android插件化开发指南>这本书所介绍的Android底层是基于Android6.0(API level 23)的,而本书介绍的各种插件化解决方案,以及配套的70多个例子, ...
- Android插件化的兼容性(中):Android P的适配
Android系统的每次版本升级,都会对原有代码进行重构,这就为插件化带来了麻烦. Android P对插件化的影响,主要体现在两方面,一是它重构了H类中Activity相关的逻辑,另一个是它重构了I ...
- 包建强的培训课程(10):Android插件化从入门到精通
@import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/c ...
- Android 插件化开发(四):插件化实现方案
在经过上面铺垫后,我们可以尝试整体实现一下插件化了.这里我们先介绍一下最简单的实现插件化的方案. 一.最简单的插件化实现方案 最简单的插件化实现方案,对四大组件都是适用的,技术面涉及如下: 1). 合 ...
- 《Android插件化开发指南》面世
本书在京东购买地址:https://item.jd.com/31178047689.html 本书Q群:389329264 (一)这是一本什么书 如果只把本书当作纯粹介绍Android插件化技术的书籍 ...
随机推荐
- 快速开发android,离不开这10个优秀的开源项目
作为一名菜鸡Android,时常瞻仰大佬们的开源项目是非常必要的.这里我为大家收集整理了10个优秀的开源项目,方便我们日常开发中学习! 作者:ListenToCode博客:https://www.ji ...
- SQL*Plus工具
或者
- Confluence 6 配置服务器基础地址备注
使用不同 URL.如果你配置了不同的基础 URL 地址或者你站点的访问者使用了不同的 URL 地址来访问你的 Confluence 地址,你有很大概率可能会受到错误信息. 修改上下文地址.如果你修改了 ...
- Confluence 6 连接到外部用户目录服务器的问题分析
在有关外部目录服务器配置页面中有一个测试配置(Test Settings)按钮.这个功能将会帮助你分析你的用户管理在 Active Directory 和其他 LDAP 服务器中出现的问题. 希望对你 ...
- nginx实践(四)之静态资源web服务(防盗链)
防盗链目的 防止资源被盗用 http_refer 主要是判断refer信息,判断请求来源是不是合法身份 语法 实例 参数说明: none表示允许没有代理的头信息过来,blocked表示refer信息不 ...
- nodejs之koa-router与koa-body搭配使用
简介 koa需要搭配中间件来做接口更方便,使用Koa-body & Koa-router 使用 koa2 创建接口,处理post请求 const koa=require("koa&q ...
- SpringCloud简介
1.什么是微服务? 微服务就是不同的模块部署在不同的服务器上面,通过接口去访问就是微服务 作用:利用分布式解决网站高并发带来的问题 2.什么是集群? 多台服务器部署相同应用构成一个集群 作用:通过负载 ...
- day12 函数的嵌套调用 闭包函数,函数对象
函数嵌套: 函数嵌套: 嵌套指的是,一个物体包含另一个物体,函数嵌套就是一个函数包含另一个函数 按照函数的两个阶段 嵌套调用 指的是在函数的执行过程中调用了另一个函数,其好处可以简化外层大函数的代码, ...
- D3.js force力导向图用指定的字段确定link的source和target,默认是索引
json.links.forEach(function (e) { var sourceNode = json.nodes.filter(function (n) { return n.name == ...
- Eclipse中部署Android开发环境插件安装问题方案
1.添加第一个插件ADT之后出现eclipse原有的SDK管理问题.需要重新安装SDK 2.一种方式按照系统提示直接联网自动搜索安装,另一种就是下载好之后import. 1.用接口声明的变量称为接口变 ...