美文网首页
Android中插件化简析

Android中插件化简析

作者: MadnessXiong | 来源:发表于2020-04-18 21:03 被阅读0次

插件化简介

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进行验证的,所以并不影响它的生命周期。

相关文章

网友评论

      本文标题:Android中插件化简析

      本文链接:https://www.haomeiwen.com/subject/clogvhtx.html