Android中插件化简析

插件化简介

Android中热修复主要用来修复bug,插件化则主要用来增加功能,将一些独立的功能打包为单独的dex作为插件。在需要的时候再动态加载。

Android中的插件化以Hook方式为主流。

Activity插件化

Activity的插件化必然涉及Activity的启动过程。从Android中根Activity的启动过程可知,Activity的启动分为3个阶段

Launcher或Activity请求AMS过程(第一次启动应用程序或应用程序打开新的Activity)

AMS到ApplicationThread的调用过程

ActivityThread启动Activity过程

因为Activity启动时必须先在Manifest中注册。而插件中的Activity是无法提前注册的。再根据Activity的启动过程,那么Activity的插件化可以分为以下3步:

占坑:使用一个占坑Activity-LocalActivity在Manifest中注册,避免插件Activity没有注册导致崩溃的情况

绕过AMS验证:启动插件Activity-PlugActivity时,将PlugActivity替换为LocalActivity,进行AMS的验证,生命周期及栈管理。

还原Activity:AMS调用ActivityThread启动Activity时,将LocalActivity再替换为PlugActivity

那么再看每一步的具体实现:

  • 创建占坑Activity-LocalActivity

    //一个空实现的Activity
    public class LocalActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_local);
        }
    }
    //在manifest中注册
    <activity android:name=".LocalActivity"></activity>
    
  • 绕过AMS验证:

    要绕过AMS验证,必须让在Manifest中注册的LocalActivity代替要启动的PlugActivity去验证,那么就要在启动时将PlugActivity替换为LocalActivity。

    Android中Hook简析中分析过,Activity启动时调用了Instrumentation的execStartActivity(),那么可以通过Hook的方式在这里进行替换,先看一下之前Hook的代码:

    public class InstrumentationProxy extends Instrumentation {
        Instrumentation instrumentation;
        private Method method;
          //持有真正启动Activity的Instrumentation引用
        public InstrumentationProxy(Instrumentation instrumentation) {
            this.instrumentation = instrumentation;
        }
          //重写启动Activity的execStartActivity()
        public ActivityResult execStartActivity(
                Context who, IBinder contextThread, IBinder token, Activity target,
                Intent intent, int requestCode, Bundle options) {
                   //将要启动的Activity改为LocalActivity
                  intent.setClassName(who,"com.madnessxiong.client.LocalActivity");
              //通过反射获取Instrumentation的class对象
            Class<? extends Instrumentation> aClass = Instrumentation.class;
            try{
              //通过class对象获取真正启动Activity的execStartActivities()
               method = aClass.getMethod("execStartActivity",                Context.class,IBinder.class,IBinder.class,Activity.class,Intent.class,int.class,Bundle.class);
             //通过反射的方式执行真正启动Activity的execStartActivities()
            ActivityResult invoke = (ActivityResult) method.invoke(instrumentation, who, contextThread, token, target, intent, options);
                return invoke;
            }catch (Exception e){
            }
            return null;
        }
    
    
    }
    

    在这里修改了intent,将要启动的Activity改为了LocalActivity。完成了替换工作。这时启动Activity的就是LocalActivity。由于LocalActivity在Manifest中注册过,所以可以通过AMS的校验。

  • 还原Activity

    AMS最终会调用到ActivityThread.performLaunchActivity():

           private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
                      //在newActivity()中用类加载器创建activity的实例,
                   activity = mInstrumentation.newActivity(
                           cl, component.getClassName(), r.intent);
               return activity;
           }
    

    然后通过mInstrumentation.newActivity()创建一个Activity,那么这里可以作为一个hook点。代理Instrumentation的newActivity()。

    根据上面的分析,那么通过Hook的方式将ActivityThread的mInstrumentation替换为InstrumentationProxy:

        public void HookActivityThread(){
            try {
                  //获取ContextImpl.class
                Class<?> contextImplClass = Class.forName("android.app.ContextImpl");
                  //获取mMainThread对应的成员变量
                Field mMainThreadFiled = contextImplClass.getDeclaredField("mMainThread");
                mMainThreadFiled.setAccessible(true);
                  //成员变量activityThread
                Object activityThread = mMainThreadFiled.get(getBaseContext());
                          //获取ActivityThread.class
                Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
                  //获取Instrumentation属性
                Field field = activityThreadClass.getDeclaredField("mInstrumentation");
                field.setAccessible(true);
    
                Instrumentation mInstrumentation = (Instrumentation)field.get(activityThread);
                InstrumentationProxy instrumentationProxy = new InstrumentationProxy(mInstrumentation);
                field.set(activityThread,instrumentationProxy);
            }catch (Exception e){
              
            }
    
        }
    

    由于ActivityThread时@hide无法直接获取它的对象,而在进程启动时已经将一个mMainThread存入了ContextImpl中。所以这里先通过反射的方式在ContextImpl中获取了ActivityThread对象,最后用InstrumentationProxy替换mInstrumentation。那么调用newActivity()时,就会来到InstrumentationProxy.newActivity():

        public Activity newActivity(ClassLoader cl, String className,
                                    Intent intent)
                throws InstantiationException, IllegalAccessException,
                ClassNotFoundException {
            return super.newActivity(cl,"com.madnessxiong.client.PlugActivity",intent);
        }
    

    在InstrumentationProxy.newActivity()中,直接调用父类,也就是Instrumentation自身的newActivity(),将类名改为实际要启动的PlugActivity。就完成了Activity的还原。而整个替换的过程只是对intent中的类名进行了替换,而在AMS中是通过token对Activity进行验证的,所以并不影响它的生命周期。

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

推荐阅读更多精彩内容