系列文章
HOOK技术一-HOOK技术初探
HOOK技术二-未注册Activity的启动
HOOK技术三-插件Activity启动前提分析
HOOK技术四-插件中Activity启动实战
HOOK技术五-使用LoadedApk式插件化的理论分析
HOOK技术六-LoadedApk式插件化代码实现
HOOK技术七-版本适配及总结
说明
上篇文章中,我们已经分析了, 如果要启动插件中的Activity, 就需要将插件的element与宿主的element融合成一个整的element,然后设置给BaseDexClassLoader。这里还有有一个问题,虽然说Activity的class可以通过这样的方式加载,但是资源文件却不行,资源文件的加载是靠AssetManager和Resource加载的,所以插件中使用AssetManager和Resource时,要避免使用宿主的,否则会加载宿主的资源文件,造成类加载正确但是资源文件加载错误的情况。
整合Element
/**
* 把插件的dexElements 和 宿主中的 dexElements 融为一体
*/
private void pluginToAppAction() throws Exception {
// 第一步:找到宿主 dexElements 得到此对象 PathClassLoader代表是宿主
PathClassLoader pathClassLoader = (PathClassLoader) this.getClassLoader(); // 本质就是PathClassLoader
Class mBaseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
// private final DexPathList pathList;
Field pathListField = mBaseDexClassLoaderClass.getDeclaredField("pathList");
pathListField.setAccessible(true);
Object mDexPathList = pathListField.get(pathClassLoader);
Field dexElementsField = mDexPathList.getClass().getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
// 本质就是 Element[] dexElements
Object dexElements = dexElementsField.get(mDexPathList);
/*** ---------------------- ***/
// 第二步:找到插件 dexElements 得到此对象,代表插件 DexClassLoader--代表插件
File file = new File(Environment.getExternalStorageDirectory() + File.separator + "p.apk");
if (!file.exists()) {
throw new FileNotFoundException("没有找到插件包!!");
}
String pluginPath = file.getAbsolutePath();
File fileDir = this.getDir("pluginDir", Context.MODE_PRIVATE); // data/data/包名/pluginDir/
DexClassLoader dexClassLoader = new
DexClassLoader(pluginPath, fileDir.getAbsolutePath(), null, getClassLoader());
Class mBaseDexClassLoaderClassPlugin = Class.forName("dalvik.system.BaseDexClassLoader");
// private final DexPathList pathList;
Field pathListFieldPlugin = mBaseDexClassLoaderClassPlugin.getDeclaredField("pathList");
pathListFieldPlugin.setAccessible(true);
Object mDexPathListPlugin = pathListFieldPlugin.get(dexClassLoader);
Field dexElementsFieldPlugin = mDexPathListPlugin.getClass().getDeclaredField("dexElements");
dexElementsFieldPlugin.setAccessible(true);
// 本质就是 Element[] dexElements
Object dexElementsPlugin = dexElementsFieldPlugin.get(mDexPathListPlugin);
// 第三步:创建出 新的 dexElements []
int mainDexLeng = Array.getLength(dexElements);
int pluginDexLeng = Array.getLength(dexElementsPlugin);
int sumDexLeng = mainDexLeng + pluginDexLeng;
// 参数一:int[] String[] ... 我们需要Element[]
// 参数二:数组对象的长度
// 本质就是 Element[] newDexElements
Object newDexElements = Array.newInstance(dexElements.getClass().getComponentType(),sumDexLeng); // 创建数组对象
// 第四步:宿主dexElements + 插件dexElements =----> 融合 新的 newDexElements
for (int i = 0; i < sumDexLeng; i++) {
// 先融合宿主
if (i < mainDexLeng) {
// 参数一:新要融合的容器 -- newDexElements
Array.set(newDexElements, i, Array.get(dexElements, i));
} else { // 再融合插件的
Array.set(newDexElements, i, Array.get(dexElementsPlugin, i - mainDexLeng));
}
}
// 第五步:把新的 newDexElements,设置到宿主中去
// 宿主
dexElementsField.set(mDexPathList, newDexElements);
// 处理加载插件中的布局
doPluginLayoutLoad();
}
为插件创建AssetManager和Resource
private Resources resources;
private AssetManager assetManager;
/**
* 处理加载插件中的布局
* Resources
*/
private void doPluginLayoutLoad() throws Exception {
assetManager = AssetManager.class.newInstance();
// 把插件的路径 给 AssetManager
File file = new File(Environment.getExternalStorageDirectory() + File.separator + "p.apk");
if (!file.exists()) {
throw new FileNotFoundException("没有找到插件包!!");
}
// 执行此 public final int addAssetPath(String path) 方法,才能把插件的路径添加进去
Method method = assetManager.getClass().getDeclaredMethod("addAssetPath", String.class); // 类类型
method.setAccessible(true);
method.invoke(assetManager, file.getAbsolutePath());
Resources r = getResources(); // 拿到的是宿主的 配置信息
// 实例化此方法 final StringBlock[] ensureStringBlocks()
Method ensureStringBlocksMethod = assetManager.getClass().getDeclaredMethod("ensureStringBlocks");
ensureStringBlocksMethod.setAccessible(true);
ensureStringBlocksMethod.invoke(assetManager); // 执行了ensureStringBlocks string.xml color.xml anim.xml 被初始化
// 特殊:专门加载插件资源
resources = new Resources(assetManager, r.getDisplayMetrics(), r.getConfiguration());
}
@Override
public Resources getResources() {
return resources == null ? super.getResources() : resources;
}
@Override
public AssetManager getAssets() {
return assetManager == null ? super.getAssets() : assetManager;
}
插件中的所有Activity使用AssetManager或者Resource时,都要从上面这个代码里面取,否则加载的资源文件会有问题的。
在插件中新建BaseActivity
public class BaseActivity extends Activity {
@Override
public Resources getResources() {
if (getApplication() != null && getApplication().getResources() != null) {
return getApplication().getResources();
}
return super.getResources();
}
@Override
public AssetManager getAssets() {
if (getApplication() != null && getApplication().getAssets() != null) {
return getApplication().getAssets();
}
return super.getAssets();
}
}
插件的所有Activity继承BaseActivity,如下所示
public class TestActivity extends BaseActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
Toast.makeText(this, "我是插件里的Activity", Toast.LENGTH_SHORT).show();
}
}
启动测试
在宿主中增加跳转事件
public void startTestActivity(View view) {
// 启动插件中的Activity
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.sanguine.placeholder.plugin", "com.sanguine.placeholder.plugin.TestActivity"));
startActivity(intent);
}
最终