由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,这是发展方向,个人见解多关注下这类的框架

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,898评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,401评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,058评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,539评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,382评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,319评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,706评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,370评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,664评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,715评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,476评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,326评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,730评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,003评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,275评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,683评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,877评论 2 335

推荐阅读更多精彩内容