Direct-Load-apk 源代码分析

来源:互联网 发布:广义逆矩阵求法 编辑:程序博客网 时间:2024/04/27 14:11
这款框架结合了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;
    }






2 0
原创粉丝点击