这款框架结合了Dynamic-Load-apk 和 PluginMgr 的弱点,使用了新的思路,成功实现了启动普通的apk。
1.Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler());
可以设置所有未捕获异常的Handler
2.一开始直接往 LActivityProxy 也就是代理Activity 中跳
3.总体理念:代理+伪装注入
4.在代理Activity 中,直接初始化插件APK
调用loadAPK 将插件的路径和当前的Context 传递给插件模型类,接着,依次调用
fillPluginRes(plugin);
if (!plugin.isOver()) {
fillPluginInfo(plugin);
}
fillPluginLoader(plugin);
fillPluginApplication(plugin);
填充并初始化插件模型类的资源、信息、ClassLoader、Application
难点有哪些呢?
首先,在fillRes 也就是加载资源函数中:
AssetManager assetManager = AssetManager.class.newInstance();
Reflect assetRef = Reflect.on(assetManager);
assetRef.call("addAssetPath", plugin.getPluginPath());
plugin.setPluginAssetManager(assetManager);
Resources superRes = super.getResources();
Resources pluginRes = new Resources(assetManager,
superRes.getDisplayMetrics(),
superRes.getConfiguration());
plugin.setPluginRes(pluginRes);
Resources.Theme pluginTheme = plugin.getPluginRes().newTheme();
pluginTheme.setTo(super.getTheme());
plugin.setCurrentPluginTheme(pluginTheme);
注意带颜色的字:将当前代理Activity 的主题给了plugin
在fillLoader 也就是加载ClassLoader 过程中,它自定义了一个ClassLoader 继承了DexClassLoader ,在内部利用HashMap 实现了一个缓存。HashMap<String,DexClassLoader> ,String即为APK 的路径
在fillApplication 中有这么一句
Application pluginApp = (Application) loader.loadClass(appName).newInstance();
Reflect.on(pluginApp).call("attach", getApplicationContext());
Application 的attach 是一个隐藏的方法,实际调用了 ConTextWrapper 的 attachBaseContext 方法,实现了插件Application 的this 指针的正确调用.
5.如何做到Activity 的生命周期的呢?
我们知道,通过ClassLoader 直接加载出来的Activity 并没有生命周期,一般,有这么几种解决方法:
(1).替换ActivityThread LoadedApk中的mClassLoader
(2).合并PathClassLoader和DexClassLoader中的dexElements数组
缺点:需要在Host 程序的清单文件中声明插件Activity 的内容
(3).通过代理Activity,在代理Activity 中onXXX() 方法中执行插件的方法
缺点:插件Activity 不能调用this 指针,需要把每个生命周期方法重写一次,并且插件方需要直接或间接反向调用Host方的setContentView 方法去设定视图,非常不方便
(4).Host 方热部署动态生成一个 Activity extends 插件Activity
缺点:在ART 环境下无法正确运行
(5).Host方先通过反射调用插件 Activity 的 attachBaseContext 方法 ,将mBase 设置成代理Activity,保证this 指针能正确使用,再将插件 Activity 中的所有『环境』变量设置为代理Activity 的变量进行伪装,如下:
attachBaseContextpluginRef.set("mBase", proxy);
pluginRef.set("mDecor", proxyRef.get("mDecor"));
pluginRef.set("mTitleColor", proxyRef.get("mTitleColor"));
pluginRef.set("mWindowManager", proxyRef.get("mWindowManager"));
pluginRef.set("mWindow", proxy.getWindow());
pluginRef.set("mManagedDialogs", proxyRef.get("mManagedDialogs"));
pluginRef.set("mCurrentConfig", proxyRef.get("mCurrentConfig"));
pluginRef.set("mSearchManager", proxyRef.get("mSearchManager"));
pluginRef.set("mMenuInflater", proxyRef.get("mMenuInflater"));
pluginRef.set("mConfigChangeFlags", proxyRef.get("
...
伪装之后,再通过 getPluginRef().call("onCreate", saveInstance); 手动调用插件Activity 的onCreate 方法,就能让插件Activity 正确调用其他的生命周期函数了
6.如何进行Activity 的跳转呢?
在对Activity 进行『伪装』的时候,顺手将Activity 中的 mInstrumentation 修改成了自定义的 Instrumentation ,众所周知,Activity的跳转实际就是通过它进行的,自定义 Instrumentation 将目标改成代理Activity 即可:
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
Intent gotoPluginOrHost = new Intent();
ComponentName componentName = intent.getComponent();
String className = componentName.getClassName();
String packageName = componentName.getPackageName();
//Toast.makeText(who, className, Toast.LENGTH_LONG).show();
gotoPluginOrHost.setClass(who, LActivityProxy.class);
gotoPluginOrHost.putExtra(LPluginConfig.KEY_PLUGIN_DEX_PATH, LPluginManager.finalApkPath);
gotoPluginOrHost.putExtra(LPluginConfig.KEY_PLUGIN_ACT_NAME,className);
ActivityResult result =instrumentRef.call("execStartActivity",who,contextThread,token,target,gotoPluginOrHost,requestCode,options).get();
return result;
}