RePlugin 记录(一)初始化流程

360在开源了DroidPlugin后又开源了RePlugin(从研发时间上是RePlugin更早),这不禁让人想问两者的区别。从RePlugin的github中的wiki我们可以找到答案,其最大区别主要是稳定性和宿主与插件的交互。

稳定性:

DroidPlugin hook了多处系统api,而系统api版本的差异性,以及手机厂商可能的修改导致需要做非常多的兼容性处理工作。RePlugin只hook了ClassLoader一处,相比之下稳定性要高很多。
DroidPlugin 也因此可以做到不修改插件原有代码情况下直接加载任意apk,RePlugin不能直接把任意apk当插件加载,需要引入Replugin的编译插件(Replugin 里面插件的startActivity,getResource等都是通过让插件使用PluginContext来完成的,而这个过程是通过gradle 自动化将插件的Activity等继承PluginActivity完成的。

宿主与插件的交互:

RePlugin宿主与插件有交互,DroidPlugin是独立。DroidPlugin侧重于加载第三方独立插件,比如微信,并且插件不能访问宿主的代码和资源。RePlugin插件可以访问代码,且通过间接方式场景进行资源交互https://github.com/Qihoo360/RePlugin/wiki/FAQ

本文主要从三个场景切入,通过分析代码(版本:replugin 2.3.0)来了解RePlugin

1、宿主进程初始化

2、插件启动过程

3、跳转插件Activity流程

使用上,宿主通过继承RePluginApplication完成插件框架的注册,因此RePluginApplication作为分析的入口

先通过时序图看下宿主进程初始化的流程


RePlugin 记录(一)初始化流程_第1张图片
newsPublishInfor.png

RePluginApplication.attachBaseContext:

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        //配置初始化,比如插件安装的路径,是否开启插件签名校验等等
        RePluginConfig c = createConfig();
        if (c == null) {
            c = new RePluginConfig();
        }
        //插件框架对外回调接口集,比如创建宿主用的RePluginClassLoader,插件用的PluginDexClassLoader
        RePluginCallbacks cb = createCallbacks();
        if (cb != null) {
            c.setCallbacks(cb);
        }
       
        RePlugin.App.attachBaseContext(this, c);
    }

RePlugin.App.attachBaseContext:

public static void attachBaseContext(Application app, RePluginConfig config) {
            ......
           //配置没有设置的话使用默认配置
            sConfig.initDefaults(app);
          //设置常驻进程的进程名字
            IPC.init(app);
            // 初始化HostConfigHelper(通过反射HostConfig来实现)
            // NOTE 一定要在IPC类初始化之后才使用
            HostConfigHelper.init();
           
            PMF.init(app);
            // 执行插件挂载(绑定关键变量)和加载默认插件
            PMF.callAttach();
        }

PMF.init

public static final void init(Application application) {
        //对这个app根据所在的进程获取用户id,和进程索引。映射appliacation和进程的关系
        PluginManager.init(application);
       //PmBase 是插件管理的核心类,进程的启动,插件的启动等相关,四大组件的查询都在这里。
       (1) sPluginMgr = new PmBase(application);
        (2)sPluginMgr.init();
     
        (3)PatchClassLoaderUtils.patch(application);
    }

(1)PmBase(Context context)

PmBase(Context context) {
       
        if (PluginManager.sPluginProcessIndex == IPluginManager.PROCESS_UI || PluginManager.isPluginProcess()) {
            //通过进程索引标示四大组件的坑位,后面查询的时候使用(对应通过gradle插件在mainfest生成的坑位,
           //可以看到每个进程的坑位是有限的。因为这些坑位是在跨插件调用才会使用的)
            mContainerProviders.add(IPC.getPackageName() + CONTAINER_PROVIDER_PART + suffix);
            mContainerServices.add(IPC.getPackageName() + CONTAINER_SERVICE_PART + suffix);
        }
        //PluginProcessPer可以看作是提供服务的类,client通过binder传给常驻进程,然后常驻进程收到
        // 请求时分发到对应的进程
        mClient = new PluginProcessPer(context, this, PluginManager.sPluginProcessIndex, mContainerActivities);
        //PluginCommImpl主要处理宿主与插件,插件与插件之间的通信(更像是请求接口)。
        mLocal = new PluginCommImpl(context, this);
        //框架内部调用,startActivity等方法
        mInternal = new PluginLibraryInternalProxy(this);
    }

(2)PmBase类 sPluginMgr.init()

void init() {
     //(默认)“常驻进程”作为插件管理进程,省略了ui进程作为插件管理进程的情况
     if (HostConfigHelper.PERSISTENT_ENABLE) {
      // 常驻进程作为Server,其余进程作为Client。
      // 时间线是宿主启动,第一次走到这里,显然当前进程是client。走initForClient,在里面
     // 会去启动常驻进程。并等待常驻进程。然后常驻进程会走一遍上面的流程。然后在这里initForServer
     //常驻进程的application所有流程走完了才会继续client的原有流程下去。
     if (IPC.isPersistentProcess()) {
        // 初始化“Server”所做工作
        ② initForServer();
      } else {
        // 连接到Server
       ① initForClient();
       }
       //初始化本进程下的插件
       ③PluginTable.initPlugins(mPlugins);     
}

① PmBase.initForClient()

private final void initForClient() {
        // 1. 先尝试连接
        PluginProcessMain.connectToHostSvc();

        // 2. 然后从常驻进程获取插件列表
        refreshPluginsFromHostSvc();
    }

PluginProcessMain.connectToHostSvc()

static final void connectToHostSvc() {
        
        //通过context.getContentResolver().query()启动ContentProvider指定了常驻进程。
        //这里使用ContentProvider,是因为启动目标进程并完成application的初始化是同步的,确保application初始化完成
        // 调用getContentResolver()同步等待AMS获取ContentResolver,contentProvider进程没起来
       // AMS会wait等待ContentProvider所在进程ActivityThread的application 初始化完后安装contentProvider,
        // 发布到AMS,调用notify 等待的线程。
 
        //启动常驻进程后做完常驻进程的初始化后在query获得的cursor
        // IBinder binder = BinderCursor.getBinder(cursor);获取常驻进程的binder以便后面通信

        IBinder binder = PluginProviderStub.proxyFetchHostBinder(context);
       
       // .....省略异常处理和死亡后处理的情况
        //把上面的binder 转成IPluginHost 后面通信时使用
        sPluginHostRemote = IPluginHost.Stub.asInterface(binder);
        if (LOG) {
            LogDebug.d(PLUGIN_TAG, "host binder.i = " + PluginProcessMain.sPluginHostRemote);
        }

        // 连接到插件化管理器的服务端
         //获取插件管理器的binder通信客户端
         //PluginManagerServer插件管理器:用来控制插件的安装、卸载、获取等。运行在常驻进程中 
         //IPluginHost:涉及到插件交互、运行机制有关的管理器
         //可以把PluginManagerServer类比为framework的pms而IPluginHost为ams
          PluginManagerProxy.connectToServer(sPluginHostRemote);

          // 将当前进程的"正在运行"列表同步到常驻进程
          // TODO 若常驻进程重启,则应在启动时发送广播,各存活着的进程调用该方法来同步
          PluginManagerProxy.syncRunningPlugins();  
           
        // 注册该进程信息到“插件管理进程”中
       // 把PluginProcessPer传给IPluginHost之后调用客户端进程时通信用
        PMF.sPluginMgr.attach();
    }

PmBase.refreshPluginsFromHostSvc( )

    /**
     * 从HostSvc(插件管理所在进程)获取所有的插件信息
     */
    private void refreshPluginsFromHostSvc() {
       //在initForServer 的Builder.builder()会获取插件信息然后加载进到HostSvc(IPluginHost的服务端)
      // 获取插件信息
        List plugins = PluginProcessMain.getPluginHost().listPlugins();
    

        // 判断是否有需要更新的插件
        // FIXME 执行此操作前,判断下当前插件的运行进程,具体可以限制仅允许该插件运行在一个进程且为自身进程中
        List updatedPlugins = null;
        if (isNeedToUpdate(plugins)) {
        // 下载新下发的插件可以更新。。。通过PluginManagerServer更新插件,类似pms更新应用
           updatedPlugins = PluginManagerProxy.updateAllPlugins();
        }
       // 更新本地插件信息
        if (updatedPlugins != null) {
            refreshPluginMap(updatedPlugins);
        } else {
            refreshPluginMap(plugins);
        }
    }

② initForServer() 常驻进程初始化

private final void initForServer() {
        //  初始化PmHostSvc和PluginManagerServer和PluginServiceServer 三个主要服务
        // 并将 PmHostSvc绑定到PluginManagerServer
        mHostSvc = new PmHostSvc(mContext, this);
        PluginProcessMain.installHost(mHostSvc);
        PluginProcessMain.schedulePluginProcessLoop(PluginProcessMain.CHECK_STAGE1_DELAY);

        // 旧方案:
        //通过Builder扫描插件,并更新PluginInfo
        // 兼容即将废弃的p-n方案 by Jiongxuan Zhang
        mAll = new Builder.PxAll();
        Builder.builder(mContext, mAll);
        refreshPluginMap(mAll.getPlugins());

        // [Newest!] 使用全新的RePlugin APK方案
        // Added by Jiongxuan Zhang
        List l = PluginManagerProxy.load();
        if (l != null) {
        // 将"纯APK"插件信息并入总的插件信息表中,方便查询
        // 这里有可能会覆盖之前在p-n中加入的信息。本来我们就想这么干,以"纯APK"插件为准
        refreshPluginMap(l);
    }

关于插件的信息
外置插件(未来将只有这一种目录):

  • APK存放路径:主程序路径/app_p_a
  • Dex存放路径:主程序路径/app_p_od
  • Native存放路径:主程序路径/app_p_n
  • 插件数据存放路径:主程序路径/app_plugin_v3_data

内置插件 & 旧P-N插件(未来和等同于外置插件):

  • APK存放路径:主程序路径/app_plugin_v3
  • Dex存放路径:主程序路径/app_plugin_v3_odex
  • Native存放路径:主程序路径/app_plugin_v3_libs
  • 插件数据存放路径:主程序路径/app_plugin_v3_data

文件的组织形式

外置插件:为了方便使用,插件会有一个JSON文件,用来记录所有已安装插件的信息。目前位于“主程序路径/app_p_a/p.l”中。有兴趣的朋友可以自行打开此文件来阅览其中内容。
安装完后会在/data/data/packageName/files/app_p_a/p.l 记下插件的信息


image.png

内置插件:不同于外置插件,内置插件的JSON文件只存放于主程序“assets/plugins-builtin.json”文件下。每次会从那里获取信息。


RePlugin 记录(一)初始化流程_第2张图片
image.png

可以看官网的介绍https://github.com/Qihoo360/RePlugin/wiki/%E6%8F%92%E4%BB%B6%E7%9A%84%E7%AE%A1%E7%90%86

③PluginTable.initPlugins(mPlugins)
刷新插件表,支持以包名和别名为key的插件

static final void initPlugins(Map plugins) {
        synchronized (PLUGINS) {
            for (Plugin plugin : plugins.values()) {
                putPluginInfo(plugin.mInfo);
            }
        }
    }
private static void putPluginInfo(PluginInfo info) {
        // 同时加入PackageName和Alias(如有)
        PLUGINS.put(info.getPackageName(), info);
        if (!TextUtils.isEmpty(info.getAlias())) {
            // 即便Alias和包名相同也可以再Put一次,反正只是覆盖了相同Value而已
            PLUGINS.put(info.getAlias(), info);
        }
    }

回到PMF.init的(3)PatchClassLoaderUtils.patch(application)

public static boolean patch(Application application) {
          // 获取Application的BaseContext (来自ContextWrapper)
            Context oBase = application.getBaseContext();
          
            // 获取mBase.mPackageInfo
            // 1. ApplicationContext - Android 2.1
            // 2. ContextImpl - Android 2.2 and higher
            // 3. AppContextImpl - Android 2.2 and higher
            Object oPackageInfo = ReflectUtils.readField(oBase, "mPackageInfo");
            
            // mPackageInfo的类型主要有两种:
            // 1. android.app.ActivityThread$PackageInfo - Android 2.1 - 2.3
            // 2. android.app.LoadedApk - Android 2.3.3 and higher
        
            // 获取mPackageInfo.mClassLoader
            ClassLoader oClassLoader = (ClassLoader) ReflectUtils.readField(oPackageInfo, "mClassLoader");
          
            // 外界可自定义ClassLoader的实现,但一定要基于RePluginClassLoader类
            ClassLoader cl = RePlugin.getConfig().getCallbacks().createClassLoader(oClassLoader.getParent(), oClassLoader);

            // 将新的ClassLoader写入mPackageInfo.mClassLoader
            ReflectUtils.writeField(oPackageInfo, "mClassLoader", cl);

            // 设置线程上下文中的ClassLoader为RePluginClassLoader
            // 防止在个别Java库用到了Thread.currentThread().getContextClassLoader()时,“用了原来的PathClassLoader”,或为空指针
            Thread.currentThread().setContextClassLoader(cl);
            return true;
    }

对照系统设置ClassLoader绑定到Context比较好理解,以下是andoird p的源码
application:ActivityThread.handleBindApplication

private void handleBindApplication(AppBindData data) {
          ....
           // 这里创建LoadedApk实例并放进缓存,后面创建Activity会取出这个LoadedApk
          data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
          Application app = data.info.makeApplication(data.restrictedBackupMode, null);
          ....
}

LoadedApk.makeApplication

public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
        Application app = null;
        //mainfest我们设置的自定义application
        String appClass = mApplicationInfo.className;
        if (forceDefaultAppClass || (appClass == null)) {
            appClass = "android.app.Application";
        }
          
       //实际调用createOrUpdateClassLoaderLocked创建ClassLoader,把应用下的jar,dex,so路径加到 
       //ClassLoader,后面类加载用。非系统应用默认是获取pathClassLoader
       //后面创建插件的ClassLoader过程可以和这个进行对比
       java.lang.ClassLoader cl = getClassLoader();
        if (!mPackageName.equals("android")) {
           //Thread.currentThread().setContextClassLoader(contextClassLoader);
           initializeJavaContextClassLoader();
        }
        ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
        //反射创建application,并且调用attach 最后调用application.attachBaseContext 把appContext
        //设置到mBase变量
        app = mActivityThread.mInstrumentation.newApplication(
                 cl, appClass, appContext);
        appContext.setOuterContext(app);   
        return app;
    }

代码涉及的类关系如图所示


image.png

另外hook ClassLoader的方法参考术哥对droidPulgin和small的讲解
droidPlugin是通过hook掉LoadedApk来达到hook ClassLoader,因为getPackageInfoNoCheck是public的
所以能够保障其稳定性
small是通过给默认ClassLoader添加补丁的方式
http://weishu.me/2016/04/05/understand-plugin-framework-classloader/

参考:
插件的管理:https://github.com/Qihoo360/RePlugin/wiki/%E6%8F%92%E4%BB%B6%E7%9A%84%E7%AE%A1%E7%90%86
Android 插件化原理解析——插件加载机制:
http://weishu.me/2016/04/05/understand-plugin-framework-classloader/
Replugin 全面解析(1):https://www.jianshu.com/p/5994c2db1557
RePlugin中如何打开插件中的自定义进程Activity:https://mp.weixin.qq.com/s/IpNcyTjML16og4LrxjxFmQ
官方wiki :https://github.com/Qihoo360/RePlugin/wiki

你可能感兴趣的:(RePlugin 记录(一)初始化流程)