android 虚拟化 用DexClassLoader先

最近准备详细研究一下android 插件化技术,所以自己通过自己的理解开始写一个插件框架出来,目前只是写简单的demo,摸清楚基本原理。后面会写详细教程,先把核心内容写出来,然后再把一些别的细节写出来,可能这个周期比较长,也是为了自己可以长期坚持写博客。

插件化最核心的2点:

1:代码调用

2:资源调用


1:代码调用原理:

不管怎么样,java里面对象就是class, 只要我们能new出对象,我们就可以进行调用,如果插件里面只是普通的类,我们可以直接new,然后调用具体的函数。但Activity,Service ,等等都有对应的Context, 说直白点就是,Activity 等创建有很多属性传进来,就跟我们模拟网络请求一样,我们需要带对应的参数才能获取对应的数据,所以我们可以看到开源的插件化都是在构建参数写了大量代码,为了尽可能把参数改成正确。我们要构建合适的参数,我们需要大量的hook,去修改成我们需要的参数,hook点不一样,构建参数数量就不一样。

2:资源调用

资源调用算是参数构建,其实我这样分类不是合适。

所以总结来看:我们就是一直构建合适参数给系统,要系统能够跑起来我们的插件。


1:代码加载

android 里面用DexClassLoader 加载APk,来构建Classloader,然后我们采用合适的方式替换原来的classloader。每个框架好像思路稍微不一样,我写的demo时候直接替换ActivityThread 里面的mPackages 的Classloader(具体细节这里不说),用这种方式很直接,不用考虑很多东西,原理也容易明白。这样子ActivityThread mH 处理创建Activity 就是已经创建了(具体为什么,其实要分析ActivityThread的代码的,因为网上已经有好多人讲解,就不写了)。但这种创Activity 不能含有任何资源,因为我们只是最简单的方式创建Activity,所有的信息都是用宿主的,那么资源全部都是指向宿主,那么插件里面就找不到对应的资源了。


   Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }
    我们只是把宿主的ClassLoader 替换了,变成可以兼容插件的。其他的信息都没有修改,所以资源都是指向宿主。


2:资源处理

网上写的简单的思路替换对应的Activity的Resource,大多数的教程都是入侵式,所以我不喜欢。我们hook ActivityThread 里面的mInstrumentation,因为 mInstrumentation 基本管理Activity 各个时期。

mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);

主要是callActivityOnCreate 这个时期感觉比较好,这里我直接替换资源。

void fakePluginActivityRes(Resources pluginRes, Activity activity){
        try {
            activity.getResources();
            //这里肯定版本不兼容的

            // 替换了 android.view.ContextThemeWrapper resource 这样就不用兼容性代码了
            Log.d(TAG, "fakePluginActivityRes: " + Activity.class.getSuperclass().getName());
            Class activityClass = Class.forName(Activity.class.getSuperclass().getName());
            Field mResourcesField = activityClass.getDeclaredField("mResources");
            mResourcesField.setAccessible(true);
            mResourcesField.set(activity, pluginRes);

            //activity.getResources();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
   我直接替换了掉资源,这里面代码跟网上稍微不一样,这里可以修改Context 资源,但我只是验证思路,就hook比较浅的层,可能会有兼容性问题。这里跟网上的插件重新getResources思路几乎一样,只是这样我不用入侵式代码。重点来了,这里我测试有问题,启动插件会崩溃,分析好久icon 找不到,这个我分析好久才分析出来,因为开始不熟悉,网上都没有说这一点,我后面分析代码与分析错误信息。

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
   
mWindow.setDefaultIcon(mActivityInfo.getIconResource());
   会发现这里崩溃,错误日志表明一个资源找不到,我通过反编译宿主的R,找到值就是宿主的Icon,但我们已经替换资源,如果icon 资源id 不一样的话,就肯定找不到。所以一直会报错。我后面直接把宿主的android:icon直接去掉就可以了。后面为了验证解决问题思路。我反射替换mActivityInfo 里面icon替换成插件对应的icon也可以。

在这里基本思路走通了,但自己写的时候就发现修改越晚,需要构建的参数越多。那么可能出现漏掉的参数也会越多,插件框架稳定性越差,这里为什么360 droidplugin 为什么自己重新构建loadApk,因为可以用直接插件的APK 全部参数来构建loadedApk,那么效果比较好,可能还有更合适的hook点,貌似360 商业的droidplugin hook少了很多,估计是找新的hook更合适hook点了。

写本篇文章完全是为了记录解决问题思路,花了一天事情才解决问题。读者可能看的云里雾里,因为缺少大量上下文。后续写一些列详细文章,希望自己能做到吧。

猜你喜欢

转载自blog.csdn.net/xiaozuoay/article/details/79077620
今日推荐