由Small插件框架的分析看Android插件框架发展历程

Small的解决的问题

开发时:让你完全透明的像开发普通工程一样完成插件开发
编译时:自动化的帮助你分离各个公共库、业务模块插件(插件仅保留自身的代码跟资源,达到最小化)
运行时:运用最少量的Hook无缝的将各个插件并入宿主,让代码跟资源完全融合,自由调用

Hook Instrumentation对象欺骗系统启动插件activity

首先替换系统instrumentation,替换ActivityThread中的mCallback,mCallback中处理了很多的消息类型,比如LAUNCH_ACTIVITY或者CREATE_SERVICE,通过在这里拦截替换成自己的activity和service来加载插件的activity和service

    @Override
    public void setUp(Context context) {
        super.setUp(context);
        if (sHostInstrumentation == null) {
            try {
                // Inject instrumentation
                final Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
                final Method method = activityThreadClass.getMethod("currentActivityThread");
                Object thread = method.invoke(null, (Object[]) null);
                Field field = activityThreadClass.getDeclaredField("mInstrumentation");
                field.setAccessible(true);
                sHostInstrumentation = (Instrumentation) field.get(thread);
                Instrumentation wrapper = new InstrumentationWrapper();
                field.set(thread, wrapper);

                if (context instanceof Activity) {
                    field = Activity.class.getDeclaredField("mInstrumentation");
                    field.setAccessible(true);
                    field.set(context, wrapper);
                }

                // Inject handler
                field = activityThreadClass.getDeclaredField("mH");
                field.setAccessible(true);
                Handler ah = (Handler) field.get(thread);
                field = Handler.class.getDeclaredField("mCallback");
                field.setAccessible(true);
                field.set(ah, new ActivityThreadHandlerCallback());
            } catch (Exception ignored) {
                ignored.printStackTrace();
                // Usually, cannot reach here
            }
        }
    }

其中InstrumentationWrapper这个类重写了execStartActivity方法


        /** @Override V21+
         * Wrap activity from REAL to STUB */
        public ActivityResult execStartActivity(
                Context who, IBinder contextThread, IBinder token, Activity target,
                Intent intent, int requestCode, android.os.Bundle options) {
            wrapIntent(intent);
            return ReflectAccelerator.execStartActivity(sHostInstrumentation,
                    who, contextThread, token, target, intent, requestCode, options);
        }

        /** @Override V20-
         * Wrap activity from REAL to STUB */
        public ActivityResult execStartActivity(
                Context who, IBinder contextThread, IBinder token, Activity target,
                Intent intent, int requestCode) {
            wrapIntent(intent);
            return ReflectAccelerator.execStartActivity(sHostInstrumentation,
                    who, contextThread, token, target, intent, requestCode);
        }

通过wrapIntent方法伪装成启动的是menifest中注册的桩activity来骗取系统的声明周期管理和合法性校验

        private void wrapIntent(Intent intent) {
            ComponentName component = intent.getComponent();
            String realClazz;
            if (component == null) {
                // Implicit way to start an activity
                component = intent.resolveActivity(Small.getContext().getPackageManager());
                if (component != null) return; // ignore system or host action

                realClazz = resolveActivity(intent);
                if (realClazz == null) return;
            } else {
                realClazz = component.getClassName();
            }

            if (sLoadedActivities == null) return;

            ActivityInfo ai = sLoadedActivities.get(realClazz);
            if (ai == null) return;

            // Carry the real(plugin) class for incoming `newActivity' method.
            intent.addCategory(REDIRECT_FLAG + realClazz);
            String stubClazz = dequeueStubActivity(ai, realClazz);
            intent.setComponent(new ComponentName(Small.getContext(), stubClazz));
        }

接着在替换的mCallback中拦截intant替换成自己的activity


    /**
     * Class for restore activity info from Stub to Real
     */
    private static class ActivityThreadHandlerCallback implements Handler.Callback {

        private static final int LAUNCH_ACTIVITY = 100;

        @Override
        public boolean handleMessage(Message msg) {
            if (msg.what != LAUNCH_ACTIVITY) return false;

            Object/*ActivityClientRecord*/ r = msg.obj;
            Intent intent = ReflectAccelerator.getIntent(r);
            String targetClass = unwrapIntent(intent);
            if (targetClass == null) return false;

            // Replace with the REAL activityInfo
            ActivityInfo targetInfo = sLoadedActivities.get(targetClass);
            ReflectAccelerator.setActivityInfo(r, targetInfo);
            return false;
        }
    }

非常简洁的实现了插件activity的加载,下面看下menifest中的桩

        <!-- Stub Activities -->
        <!-- 1 standard mode -->
        <activity
            android:name="net.wequick.small.A"
            android:launchMode="standard" />
        <activity
            android:name="net.wequick.small.A1"
            android:theme="@android:style/Theme.Translucent" />
        <!-- 4 singleTask mode -->
        <activity
            android:name="net.wequick.small.A10"
            android:launchMode="singleTask" />
        <activity
            android:name="net.wequick.small.A11"
            android:launchMode="singleTask" />
        <activity
            android:name="net.wequick.small.A12"
            android:launchMode="singleTask" />
        <activity
            android:name="net.wequick.small.A13"
            android:launchMode="singleTask" />
        <!-- 4 singleTop mode -->
        <activity
            android:name="net.wequick.small.A20"
            android:launchMode="singleTop" />
        <activity
            android:name="net.wequick.small.A21"
            android:launchMode="singleTop" />
        <activity
            android:name="net.wequick.small.A22"
            android:launchMode="singleTop" />
        <activity
            android:name="net.wequick.small.A23"
            android:launchMode="singleTop" />
        <!-- 4 singleInstance mode -->
        <activity
            android:name="net.wequick.small.A30"
            android:launchMode="singleInstance" />
        <activity
            android:name="net.wequick.small.A31"
            android:launchMode="singleInstance" />
        <activity
            android:name="net.wequick.small.A32"
            android:launchMode="singleInstance" />
        <activity
            android:name="net.wequick.small.A33"
            android:launchMode="singleInstance" />

        <!-- Web Activity -->
        <activity
            android:name="net.wequick.small.webkit.WebActivity"
            android:hardwareAccelerated="true"
            android:screenOrientation="portrait"
            android:windowSoftInputMode="stateHidden|adjustPan" />
    </application>

这种写法的好处是系统托管activity的launchMode,弊端是activity的数量收到限制,因为一个插件activity就对应一个桩,当桩的数量不够时不好处理,更好的方法是只注册一个activity,自己管理launchMode,记录下每一个启动的activity的launchMode,按照launchMode的规则自己对应真实的插件activity

资源分区,解决宿主和插件的资源id冲突

详解:https://github.com/wequick/Small/wiki/Android-dynamic-load-resources

工程结构

Small对工程结构做了较大的调整

XPR4QB78D(0VA~5TO8N`0KN.png

app:host工程
app.:app插件工程;
lib.
:library插件工程;
web.*:web插件工程;
其他:其他assert 工程;

所谓插件就是指的这些module,每一个module在编译插件的时候都会生成一个.so文件,签名后被自动放在host里面,host实际是一个空壳,调用各个插件,下图解读开发、编译、运行三个阶段

small1.png

插件的发展历程

Android Dynamic Loader
dynamic-load-apk
CJFrameForAndroid
android-pluginmgr
Direct-Load-apk
DroidPlugin
DynamicAPK
ACDD
Small
Android-Plugin-Framework
OSGIService

归类总结

我把插件框架的应用场景分为三类
1)插件之间完全隔离,目的是把所有别的应用变成自己的插件,典型的是360的DroidPlugin,实现的最彻底,插件之间完全不认识对方,不能调用对方的代码&资源
2)插件之间通过接口隔离,典型的通过osgi构建一套接口,比如
ACDDOSGIService
3)插件和宿主完全融合,代码资源能够互调,典型的Small
这是两个不同的方向,一个旨在彻底的分离插件,一个旨在分离同一个app。

发展方向

从桌面应用的发展方向来看,最终大家都会像web靠拢,通过简单的配置几个标签就能实现应用,android&ios都只是在朝着这个方向努力,目前的reactnative,阿里的weex,这是发展方向,个人见解多关注下这类的框架

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容