前言
9.0 源码相对于之前的版本都有很大的调整,在插件化技术上需要做单独的Hook。不过在插件化方案上还是没有太大的变动。
1.方案
本文采用的还是插件化技术中较为主流的“欺骗AMS”的策略。即为在向AMS发送请求时,将组件信息替换为宿主注册过的组件,
在通过AMS之后,将宿主组件的信息替换为目标插件组件信息。
2. Dex 和 Resource 的加载
想要完成对插件的完全调用,要解决两个问题。第一就是能够正常加载插件中的类,第二个就是能正常加载插件中的资源。
2.1 Dex
在宿主app中,都会调用到BaseDexClassLoader中的DexPathList来加载相应的类,DexPathList中的dexElements
是一个dex信息数组,在查找过程中会遍历该数组,直到找到目标类为止,所以我们可以将插件dex强行加入该数组,这样
在创建组件过程中,就能正常的找到插件组件类。
具体代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
/**
* 添加插件dex到host
*
* @param hostClassLoader 宿主classloader
* @param apkFile 插件apkfile
*/
public static void addDexToHost(ClassLoader hostClassLoader, File apkFile, File optDexFile) {
try {
//取出pathList
Object pathList = ReflectUtils.getFieldObjectWithClassName(DexClassLoader.class.getSuperclass().getName(),hostClassLoader, "pathList");
//取出dexElements
Object[] dexElments = (Object[]) ReflectUtils.getFieldObject(pathList, "dexElements");
//构造新数组
Class<?> componentType = dexElments.getClass().getComponentType();
Object[] newArray = (Object[]) Array.newInstance(componentType, dexElments.length + 1);
//构造新成员
Class[] paramsType = new Class[]{DexFile.class, File.class};
Object[] params = new Object[]{DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0), apkFile};
Object objectWithParams = ReflectUtils.createObjectWithParams(componentType.getName(), paramsType, params);
Object[] tonewArray = new Object[]{objectWithParams};
//填充新数组
System.arraycopy(dexElments, 0, newArray, 0, dexElments.length);
System.arraycopy(tonewArray, 0, newArray, dexElments.length , tonewArray.length);
//设置dexElements
ReflectUtils.setFieldValue(pathList, "dexElements", newArray);
} catch (Exception e) {
e.printStackTrace();
}
}
|
2.2 Resource
对于插件资源的控制,需要用到AssetManager。AssetManager能将一个apk中的资源解析,提供给应用使用,所以我们需要给插件新建一个
AssetManager,并且插件的组件都要使用这个AssetManager。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
PluginPackage pluginPackage ;
//新建一个AssetManager
AssetManager assetManager = AssetManager.class.newInstance();
//新建一个Resources
Resources resources = new Resources(assetManager,context.getResources().getDisplayMetrics(),context.getResources().getConfiguration());
//解析插件资源
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, file.getAbsolutePath());
//封装
pluginPackage = new PluginPackage();
pluginPackage.setManager(assetManager);
pluginPackage.setResources(resources);
pluginPackage.setTheme(resources.newTheme());
pluginPackage.getTheme().setTo(context.getTheme());
String replace = file.getName().replace(Constants.PLUGIN_SUFFIX, "");
pluginPackage.setName(replace);
pluginPackageHashMap.put(replace,pluginPackage);
|
在这里需要注意的是,添加插件资源的操作是在宿主app中,而使用插件资源的操作是在插件app中,所以,宿主app和插件app都需要依赖一个
公共工程,这个工程就是专门管理插件资源的。
3. 实施
3.1 架构规划
针对以上的分析,我们在项目实践时,首先需要规划好项目结构,如上图,我们需要建立一个公共库common,这个库中包含了插件的资源管理器
和需要执行Hook操作的工具类,不管是宿主工程还是插件工程都需要依赖此库。
Hooker中除了需要添加插件Dex的操作之外,还需要完成“欺骗策略”的Hook操作。熟悉四大组件的启动过程之后,我们就知道,想要完整的欺骗
AMS,需要完成两步Hook操作。
第一步在发送启动请求时,需要将请求的插件组件的类名替换成宿主app中申请的组件类名,这一步,进过多步实验,直接Hook AMS是合理的。
代码中的AMSHooker是我们执行具体替换插件类名操作的类,这里属于动态代理的一种方案。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
/**
* 欺骗策略第一环节,拦截Intent,替换之后发送给AMS
*/
public static void hookAMS() {
try {
Object iActivityManagerSingleton = ReflectUtils.getStaticFieldObject("android.app.ActivityManager", "IActivityManagerSingleton");
Object mInstance = ReflectUtils.getFieldObjectWithClassName("android.util.Singleton",iActivityManagerSingleton, "mInstance");
Class<?> aClass = Class.forName("android.app.IActivityManager");
Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{aClass}, new AMSHooker(mInstance));
ReflectUtils.setFieldValueWithClassName("android.util.Singleton",iActivityManagerSingleton,"mInstance",proxyInstance);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
|
第二步,就是在通过了AMS验证之后,需要将目标插件组件的类名替换回来,好让ActivityThread完成反射操作。HandlerHooker也是我们处理
替换操作的代理类。
1
2
3
4
5
6
7
8
|
/**
* 欺骗策略第二环节,将插件Intent还原
*/
public static void hookActivityThread(){
Object sCurrentActivityThread = ReflectUtils.getStaticFieldObject("android.app.ActivityThread", "sCurrentActivityThread");
Handler mH = (Handler) ReflectUtils.getFieldObject(sCurrentActivityThread, "mH");
ReflectUtils.setFieldValueWithClassName(Handler.class.getName(),mH,"mCallback",new HandlerHooker(mH));
}
|
3.2 Activity的启动
在两个代理类中,关于Activity的处理如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
//AMSHooker.java
public class AMSHooker implements InvocationHandler {
...
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startActivity".equals(method.getName())){
int index = -1;
Intent raw = null;
//找出Intent的参数
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent){
index = i;
raw = (Intent) args[i];
break;
}
}
//将Intent中的Activity组件类名换成 宿主中的占位组件 StubActivity
Intent newIntent = new Intent();
newIntent.setComponent(new ComponentName("com.zxy.plugin","com.zxy.plugin.StubActivity"));
newIntent.putExtra(TARGET_AC,raw);
if (index>-1){
args[index] = newIntent;
}
}
return method.invoke(mBase,args);
}
}
|
在返回后,处理如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
public class HandlerHooker implements Handler.Callback {
private Handler mHandler;
public HandlerHooker(Handler o) {
mHandler = o;
}
@Override
public boolean handleMessage(Message msg) {
//在9.0的源码中,H 中159表示Activity的操作
if (msg.what == 159) {
Object obj = msg.obj;
//每个操作都是封装成ClientTransaction
if ("android.app.servertransaction.ClientTransaction".equals(obj.getClass().getName())) {
//一个ClientTransaction中包含一个关于操作activiy的集合
List<Object> mActivityCallbacks = (List<Object>) ReflectUtils.getFieldObject(obj, "mActivityCallbacks");
for (int i = 0; i < mActivityCallbacks.size(); i++) {
Object o = mActivityCallbacks.get(i);
//如果包含了Launch操作,就可以在这里执行替换操作
if ("android.app.servertransaction.LaunchActivityItem".equals(o.getClass().getName())) {
Intent mIntent = (Intent) ReflectUtils.getFieldObject(o, "mIntent");
Intent parcelableExtra = mIntent.getParcelableExtra(AMSHooker.TARGET_AC);
mIntent.setComponent(parcelableExtra.getComponent());
}
}
}
}
mHandler.handleMessage(msg);
return true;
}
}
|
关于插件Activity的资源引用,在上文我们也说到了,需要使用插件自己的AssetManager,所以这里我们会抽象一个插件Activity基类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public abstract class PluginBaseActivity extends Activity {
//每个插件的资源封装
private PluginResourceManager.PluginPackage aPackage;
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
//在插件Actiivity启动时就使用资源管理器将相应的插件资源获取
aPackage = PluginResourceManager.getInstance().getPackage(getPluginKey().replace(".","_"));
}
//插件标示,每个插件都有不一样的标示
protected abstract String getPluginKey();
//将以下三个方法返回值都替换为插件中的包装,这样插件Activity就能正常使用插件资源了
@Override
public AssetManager getAssets() {
return aPackage.getManager();
}
@Override
public Resources.Theme getTheme() {
return aPackage.getTheme();
}
@Override
public Resources getResources() {
return aPackage.getResources();
}
}
|
代码中的PluginResourceManager是一个单例,在宿主app启东时会完成初始化,将所有的插件app的Resource都放入宿主app的
内存中,以供后来的插件app调用。
3.3 Service的启动
启动之前的操作如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
public class AMSHooker implements InvocationHandler {
...
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startService".equals(method.getName())){
int index = -1;
Intent raw = null;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent){
index = i;
raw = (Intent) args[i];
break;
}
}
Intent newIntent = new Intent();
String className = raw.getComponent().getClassName();
//Service由于其特殊性,插件app中的Service与宿主的占位Service需要一一对应。所以这里使用了字典的方式来管理
String stubService = PluginServiceManager.serviceManagerSingleton.getmInstance().getStubService(className);
if (!TextUtils.isEmpty(stubService)){
newIntent.setComponent(new ComponentName("com.zxy.plugin",stubService));
newIntent.putExtra(TARGET_SE,raw);
if (index>-1){
args[index] = newIntent;
}
}
}
return method.invoke(mBase,args);
}
}
|
返回之后的处理如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
public class HandlerHooker implements Handler.Callback {
...
@Override
public boolean handleMessage(Message msg) {
//114属于创建指令
if (msg.what == 114) {
Object obj = msg.obj;
if ("android.app.ActivityThread$CreateServiceData".equals(obj.getClass().getName())) {
ServiceInfo info = (ServiceInfo) ReflectUtils.getFieldObjectWithClassName("android.app.ActivityThread$CreateServiceData", obj, "info");
//替换
info.name = getTargetService(info.name);
}
}
//115属于启动指令
if (msg.what == 115) {
Object obj = msg.obj;
if ("android.app.ActivityThread$ServiceArgsData".equals(obj.getClass().getName())) {
Intent intent = (Intent) ReflectUtils.getFieldObjectWithClassName("android.app.ActivityThread$ServiceArgsData", obj, "args");
Intent target = intent.getParcelableExtra(AMSHooker.TARGET_SE);
//替换
intent.setComponent(target.getComponent());
}
}
mHandler.handleMessage(msg);
return true;
}
}
|
为了统一资源的使用,service也需要加载插件的资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
public abstract class BasePluginService extends Service {
private PluginResourceManager.PluginPackage aPackage;
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
aPackage = PluginResourceManager.getInstance().getPackage(getPluginKey().replace(".","_"));
}
protected abstract String getPluginKey();
@Override
public AssetManager getAssets() {
return aPackage.getManager();
}
@Override
public Resources.Theme getTheme() {
return aPackage.getTheme();
}
@Override
public Resources getResources() {
return aPackage.getResources();
}
}
|
总结
对于一个完整的插件化框架,光有上述工作还是不够的,比如Activity的启动模式的解决,Broadcastrecevier和ContentProvider
的插件化实施,还有对于各个sdk版本的兼容。再接再厉,后续会继续完善。