RePlugin源码分析总结

简单介绍(官方wiki)

RePlugin是一套完整的、稳定的、适合全面使用的,占坑类插件化方案,由360手机卫士的RePlugin Team研发,也是业内首个提出”全面插件化“(全面特性、全面兼容、全面使用)的方案。
其主要优势有:

  • 极其灵活:主程序无需升级(无需在Manifest中预埋组件),即可支持新增的四大组件,甚至全新的插件
  • 非常稳定:Hook点仅有一处(ClassLoader),无任何Binder Hook!如此可做到其崩溃率仅为“万分之一”,并完美兼容市面上近乎所有的Android ROM
  • 特性丰富:支持近乎所有在“单品”开发时的特性。包括静态Receiver、Task-Affinity坑位、自定义Theme、进程坑位、AppCompat、DataBinding等
  • 易于集成:无论插件还是主程序,只需“数行”就能完成接入
  • 管理成熟:拥有成熟稳定的“插件管理方案”,支持插件安装、升级、卸载、版本管理,甚至包括进程通讯、协议版本、安全校验等
  • 数亿支撑:有360手机卫士庞大的数亿用户做支撑,三年多的残酷验证,确保App用到的方案是最稳定、最适合使用的

项目结构

整个项目分两个系统,宿主系统host,插件系统plugin。其中每个系统包含两个module,分别为插件module和java module。

Host:

  • replugin-host-gradle:
    根据用户的配置文件,生成HostBuildConfig类,方便插件框架读取并自定义其属性,如:进程数、各类型占位坑的数量、是否使用AppCompat库、Host版本、pulgins-builtin.json文件名、内置插件文件名等。
  • replugin-host-library:
    RePlugin的核心工程,负责初始化、加载、启动、管理插件等。

Plugin:

  • replugin-plugin-gradle:
    插件的编译期中:配置插件打包相关信息;动态替换插件工程中的继承基类,如下,修改Activity的继承、Provider的重定向等。
  • replugin-plugin-library:
    提供通过“Java反射”来调用主程序中RePlugin Host Library的相关接口,并提供“双向通信”的能力,以及各种基类Activity等。

流程分析

Host启动流程:
  • 进程启动
    先启动UI进程,同时拉起常驻进程。
  • 加载坑位
    各进程加载坑位信息(因为加载插件的发起是在当前进程,进入常驻进程安装插件前要在当前进程分配坑位)
  • 各进程初始化
    PluginProcessPer 代表插件进程端,与Server端进行通讯;
    PluginCommImpl 负责宿主与插件,插件与插件间的通讯;
    PluginLibraryInternalPoxy 插件通过反射调用宿主的内部逻辑;
    • 初始化Server 端(常驻进程初始化)
      PmHostSvc(类似于AMS,为插件进程提供Service支持);
      PluginServiceServer 管理服务相关的操作,比如启动,绑定;
      PluginManagerServer 管理插件交互,安装,卸载等;
      ----以上都是常驻进程服务端的Stub。
      PluginProcessMain 常驻进程缓存本身的Binder,插件进程与常驻进程沟通获取Binder,唤起常驻进程等操作;
      PluginManagerProxy 各进程中有一份,用于缓存PluginManagerServer这个Stub;
      尝试清理正在运行且进程内没有组件信息的坑位进程;
      加载(安装)内置插件;
      加载(安装)指定路径 “p_n/p.l” 下的文件,插件进程远程调用,因为真正的load是在Server端,并添加到插件数组中;
  • 初始化Client端(包括UI进程,因为除了常驻进程其余全部是Client端)
    尝试与常驻进程连接,或拉起常驻进程,并获取Binder,缓存到当前进程的PluginManagerPoxy中;
    并将当前进程信息同步到常驻进程;
    将常驻进程之前加载好的插件数组,加载到当前进程;
  • 配置RePluginClassLoader
    在ContextImpl中获取mPackageInfo字段LoadedApk,LoadedApk中获取mPackageInfo.mClassLoader。该ClassLoader就是PathClassLoader。
    将PatchClassLoader的ParentLoader,作为父类加载器,创建RepluginClassLoader,创建好以后通过反射替换掉LoadedApk中的PathClassLoader。
    将PathClassLoader里的信息,全部通过反射copy到RePluginClassLoader,让系统唔认为这就是PathClassLoader,然后重新loadClass方法,优先使用自定义的加载类的方式,加载不到在使用原来的PathClassLoader。
  • 如果当前进程处于插件进程,加载默认插件
    手动安装一个插件,主动调用install方法
  • 根据传入的插件路径,远程调用PluginManagerServer中的install方法;
  • 解析插件,并进行签名验证;
  • 将插件路径文件copy到PluginInfo指定路径,从插件中释放so库到PluginInfo指定路径;
  • 将解析好的PluginInfo信息写入到 “p_n/p.l” 路径。(下次程序启动就不需要再次安装解析,直接读取);
  • 将当前的PlugInfo同步到各个进程,并更新每个进程的插件内存表(广播的方式);
    启动一个插件(比如我们想要start一个插件Activity,要先启动插件)
  • 先获取该Activity对应的坑位信息,并封装到CompoentName
    创建加载器Loader,通过loadDex启动;
    读取PackageInfo中的组件信息,比如四大组件,并将这些信息,与插件关联。
    调整插件中的四大组件的进程名称,将自定义进程名称替换成坑位进程名称。
    注册并处理静态广播。
    调整taskAffinity。
    创建当前插件对应的Resources对象。
    构建PluginDexClassLoader,加载多dex。缓存宿主的ClassLoader(如果DexClassLoader加载不到尝试使用宿主的ClassLoader加载)。
    构建PluginContext。
    通过DexClassLoader加载插件,并反射调用插件中的代码Entry中的方法,将宿主中的ClassLoader传过去。
    进入到插件以后,通过宿主中的RepluginClassLoader做各种反调宿主中的代码(类似于一种注册)。
    将当前插件,作为正在运行的插件,同步到常驻进程正在运行的插件列表。
    删除odex文件(最后暴露出的这个odex路径无意义,删除)。
    至此,插件启动成功,如果插件中存在Application,尝试从PackageInfo中拿到ApplicationInfo,然后拿到ApplicationName,通过DexClassLoader实例化插件Application,手动调用attachBaseContext和create。onTrimMemory,onLowMemory,configurationChanged等跟随宿主Application方法。
  • 保存插件中真实ActivityInfo中的主题;
  • 根据坑位进程名,获取进程标示;
  • 根据标示,尝试启动进程(如果是自定义进程,需要借助Provider拉起,否则自动分配到UI进程);
  • 拿到被分配的进程(PluginProcessPer,之前在Client进程启动后,同步到常驻进程的进程信息中获取到的);
  • 进程启动后,分配坑位Activity(此分配过程便是在坑位所在进程的PluginProcessPer中执行),将坑位Activity与真实Activity关联到state中;
  • 通过PluginDexClassLoader加载真正的Activity(用于判断插件中是否存在该Activity);
  • 然后开始启动坑位Activity,后续交给AMS处理;
  • AMS在处理坑位Activity的时候,某一时刻会尝试加载该坑位Activity,此时我们在RePluginClassLoader中进行拦截,根据坑位找到真正的Activity。
启动后的PluginActivity
  • 该Activity中会重写各种Activity方法,比如attachBaseContext(Context context)
    这个方法中的context参数还是宿主的context,通过反射回调宿主中的createActivityContext方法,创建PluginContext并返回。
启动一个BroadcastReceiver
  • 动态广播的方式
    将插件中的广播通过反射调用宿主的方式,委托给宿主的LocalBroadcastReceiver。
  • 静态广播的方式
    通过插件名与IntentFilter的映射关系(HashMap<插件内receiver组件名称,List<组件的 IntentFilter>>)去常驻进程注册。
    将当前插件下,所有的组件对应的IntentFilter,都注册到PluginReceiverProxy 这个代理中,后续根据里面的Action接收广播,在该代理的onReceive方法中,根据插件判定,如果是常驻进程,在PmHostSvc中回调onReceive方法,否则在PluginProcessPer中回调onReceive方法。
    然后PluginDexClassLoader加载插件中的Recevier,反射实例化对象,手动调用onReceive方法。
启动一个Service
  • 因为服务都是Context启动的,所以直接在PluginContext中寻找startService入口
    懒得写了,直接看代码吧。。。
启动一个ContentProvider
  • 通过之前Task,给使用ContentProvider的地方使用javassit修改成了PluginProviderClient中对应的方法,里面的方法会反射调用宿主中的PluginProviderClient中的方法(比如query方法),然后根据插件信息,配置新的uri,然后启动对应uri的坑位provider(启动方式,是通过getContentResolver的query方法,拉起provider),在坑位Provider中通过反射创建插件中的Provider,并手动调用query方法。

参考:https://github.com/Qihoo360/RePlugin/wiki/高级话题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值