0%

特别篇:自己的总结

识别插件的类

方案:将所有的插件 dex 合并到 宿主中,具体步骤为:

  1. 根据宿主的 ClassLoader,获取宿主的 dexElements 字段

    具体步骤为: 首先反射出 BaseDexClassLoader 的 pathList 字段,它是 DexPathList 类型的;第二步,反射出 DexPathList 的 dexElements 字段,他是个数组

  2. 根据插件的 dexFile ,反射出一个 Element 类型对象,这就是插件 dex

  3. 把插件 dex 和 宿主 dexElements 合并成一个新的 dex 数组,替换宿主之前的 dexElements 字段

欺骗系统

启动时,其实是启动 PlaceHoldActivity ,将真正要启动的 TargetActivity 放在 Intent 的数据里面,主要步骤:

1、Hook 到 ActivityManagerNative (它里面的gDefault,这个字段是个单例),把 TargetActivity 替换为 PlaceHoldActivity

2、Hook 到 H(Handler) 类的 mCallback 字段,将 PlaceHoldActivity 换成真正的 TargetActivity。或者,如果不想使用这种 Hook,可以在创建Activity (execStartActivity)对象的时候再替换回来

资源使用

原理

  1. 通过反射,构建一个 AssetManager 对象 mAssetManager,之后,反射调用 mAssetManager 的 addAssetPath 方法,将宿主和插件的 dexPath 全部添加进去,这样,这个新的 mAssetManager 拥有宿主以及插件中的所有的资源了。
  2. 根据上述的 mAssetManager 构建处一个 Resources 对象 mResources
  3. 使用 newTheme 创建一个 Theme 对象 mTheme
  4. 在 Activity 中重写 getAssets()、getResources()、getTheme() 方法,分别返回上述的mAssetManager、mResources、mTheme
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
33
34
35
36
37
38
private void loadResources(){
try{
AssetManager assetManager= AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath",String.class);
addAssetPath.invoke(assetManager, dexPath);
mAssetManager = assetManager;
}catch (Exception e) {
e.printStackTrace();
}
Resources superResources = super.getResources();
mResources = new Resources(mAssetManager, superResources.getDisplayMetrics(), superResources.getConfiguration());
mTheme = mResources.newTheme();
mTheme.setTo(super.getTheme());
}

@Override
public AssetManager getAssets() {
if (mAssetManager == null) {
return super.getAssets();
}
return mAssetManager;
}

@Override
public Resources getResources() {
if (mResources == null) {
return super.getResources();
}
return mResources;
}

@Override
public Resources.Theme getTheme() {
if (mTheme == null) {
return super.getTheme();
}
return mTheme;
}

注意一点,由于将所有资源集合在一块,所以资源id可能冲突,解决方案:修改AAPT命令,将宿主和插件之间的id前缀都区分开,比如宿主的资源id以 0x71 开头,插件1以 0x72,插件2以 0x73 等,避免冲突。

还有,如果插件需要使用宿主的资源,那么,可以在宿主中将id写死,具体方式为自定义一个public.xml ,在里面写死。然后插件中以 provided 的方式将宿主的aar引入(这样,打包的时候就不会将这个aar打进去),引用资源的时候,直接使用这个写死的id就行。

1
2
3
4
5
<!--public.xml-->
<?xml version="1.0" encoding="utf-8">
<resources>
<public type="string" name="welcom" id="0x71050024"/>
</resources>

启动模式(LaunchMode)

普通使用的都是Standard 模式,如果要解决其他 3 种 LaunchMode 问题,使用的是占位Activity的思想,即事先为这3种 LaunchMode 创建很多 PlaceHoldActivity。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
SingleInstancePlaceHoldActivity1
SingleInstancePlaceHoldActivity2
SingleInstancePlaceHoldActivity3


SingleTaskPlaceHoldActivity1
SingleTaskPlaceHoldActivity2
SingleTaskPlaceHoldActivity3


SingleTopPlaceHoldActivity1
SingleTopPlaceHoldActivity2
SingleTopPlaceHoldActivity3

接下来,从服务器下载一个 Json ,指定插件Activity 要使用哪个 PlaceHoldActivity ,这里所说的 Activity 只包括 LauncherMode 为 SingleTop、SingleTask、SingleInstance 的 Activity。我们无法指定插件 Activity 的 LaunchMode ,但是通过这种指定占位,当 ActivityA 和 SingleTaskPlaceHoldActivity1 建立对应关系之后,ActivityA 的 LaunchMode 就是 SingleTask 了!

但是这里还有个小bug,无论是SingleTop 还是 SingleTask ,再回到这个Activity时,并不会触发它的 onCreate ,而是触发 它的 onNewIntent 方法!为此,我们需要在Hook的 Handler.Callback 中拦截 onNewIntent 方法,把占位的 PlaceHoldActivity 替换回插件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
class MockClass2 implements Handler.Callback {
@Override
public boolean handleMessage(Message msg) {
switch(msg.what) {
case 112:
handleNewIntent(msg);
break;
}
}

private void handleNewIntent(Message msg) {
Object obj = msg.obj;
ArrayList intents = (ArrayList)RefInvoke.getFieldObject(obj, "intents");
for(Object object: intents) {
Intent raw = (Intent)object;
Intent target = raw.getParcelableExtra(AMSHookHelper.EXTRA_TARGET_INTENT);
if(target != null) {
raw.setComponent(target.getComponent());
if(target.getExtras() != null) {
raw.putExtras(target.getExtras());
}
break;
}
}
}
}
谢谢你的鼓励