胡琪

为今天工作,为明天投资,为未来孵化一些东西!

【插件化】开篇总述

前段时间因为工作需要开发环境安全SDK,在开发该SDK的过程中有一个子功能就是检测App是否运行在可信环境中,其中一项就是对虚拟环境的检测,也即多开/分身类App的检测。因此调研了下这类App的实现原理,发现其原理本质上就是通过插件化技术达到在多开App的沙盒环境中免安装运行用户App。检测多开App其实很简单,但是多开App背后的原理插件化却没那么简单甚至可以说是非常复杂,不过我认为也是非常值得我们学习的。首先插件化和热修复一样本身属于安卓应用层安全的范畴,另外在学习插件化的过程中我发现如果能够把插件化的原理了解透彻,那么你将会对安卓framework层的运行机制,尤其是和四大组件相关的运行机制有着非常深刻的理解。个人觉得只要你从事的是和安卓相关的工作,不管是应用层App开发,还是安卓ROM开发亦或是安卓安全,插件化技术以及多开技术绝对是值得你去学习和了解的。

开发一个插件化框架需要解决的问题

首先从效果来看多开App的目的是达到在不安装插件Apk到手机系统的情况下,在多开App中运行该插件Apk。另外运行的插件Apk和多开App本身是无任何关联的,比如可以在平行空间这个多开App内运行微信,微信和平行空间这个App在开发时肯定是无任何关联的,也就是说插件Apk即可作为插件被多开App调起,同时本身也是一个独立的Apk。从开发者的角度来看,在开发插件Apk的过程中对宿主完全无依赖,无感知。

从这个效果来看我们可以提炼出三大关键信息:

  • 免安装
  • 运行在宿主App模拟的虚拟环境下
  • 插件和宿主相互独立,插件对宿主无依赖,无感知

这3大特征实际上就对应了要开发一个插件化框架需要解决的几大问题:

  • 免安装意味着插件Apk的Manifest文件不能被安卓系统解析,插件中声明的组件信息对安卓系统而言是无效的。而四大组件的使用必须在Manifest清单文件中声明,所以这里就有一个问题需要解决,如何保证宿主调用插件中的组件时不出现未声明异常,如启动插件中的Activity而不出现have you declared this activity in your androidmanifest.xml异常.
  • 运行在宿主App虚拟环境内意味着插件Apk是无自己的Context的。因为安卓应用程序和普通java代码程序的一个很大的区别就是安卓App在启动过程中会在负责启动App的系统framework层的代码的启动流程中赋值。被宿主App调起而非系统调起,意味着该插件App没走系统的启动流程,因此很多具备安卓特性的类的功能是不完备的,如插件自己的Context。Application。所以需要解决插件Apk Context,Application等问题
  • 1和2合起来(未安装且由宿主调起):意味着插件Apk代码的加载和资源的加载是宿主解决。所以需要解决插件Apk代码和资源的加载问题
  • 插件和宿主相互独立意味着不能通过插件遵守宿主开发规约的形式来实现上述目的,这一条和第2条合起来意味着不能通过代理组件的形式来实现插件Apk中组件的生命周期同步。比如插件通过调用宿主代理Activity的形式来同步Activity生命周期。所以需要解决插件Apk中相关组件的生命周期的管理。

所以从上面可以看到一个成熟的插件化框架主要需要解决以下几大关键问题:

  1. 插件Apk代码的加载和资源的加载。也即插件Apk中类的加载和图片,字符串,布局文件等资源文件的加载。
  2. 安卓中特有类的创建以及与直接安装到系统上的功能的一致性,比如Application,Context,Resource等,这些类不是简单的new一个或通过Classloader加载创建后就能使用的,需要做到插件中的这些类在宿主中调起时和被安装到手机系统调起时其功能是等同的,而不是残缺的或不一致的,比如插件Apk的Activity在获取资源时应该获取到插件Apk自己的,而不是宿主的Resource指向的资源
  3. 插件Apk四大组件的生命周期的管理

要开发一个插件化框架,上述3大问题是最核心的问题,也是插件化的理论基础部分需要解决的核心问题,当然在实际开发实践过程中也会有很多其他小问题需要解决,比如Activty不同启动模式的解决,不同ROM版本的兼容性解决,多个插件环境隔离,多个插件之间的通信等。

但是上述3大问题是最核心的问题。解决上述3大核心问题基本上就能开发一个插件化框架的demo了。(当然要开发一款商用的高稳定性和兼容性的插件化框架还是很麻烦的)。

解决上述3大难题的几种思路

插件Apk代码的加载和资源的加载

1插件Apk代码的加载

主要就是普通类的加载和具备安卓特性的类的创建和加载了。安卓中特有类的相关问题是需要解决的第2大难题。这个后面会讲到。对于无安卓特性的类的加载,本质上就是动态的加载一个dex,如果大家对热修复非常了解的话,这个就很简单了。这里不做过多赘述,大致提下实现方案:

从原理层面来看主要包括以下三大解决方案:

  1. 类MultiDex的插件dex插桩
  2. 直接自定义ClassLoader加载dex
  3. 类Instant Run的ClassLoader hack

不清楚的同学可以参看专栏:热修复与动态加载

从插件化架构层面来看可以概括为单ClassLoader和多ClassLoader 2种方案,1和3属于单ClassLoader方案,如果存在多个插件,直接将多个插件的dex注入到宿主的ClassLoader的类加载和查找路径中。2属于多ClassLoader方案,多ClassLoader的好处之一就是插件环境隔离,另外一个比较大的好处就是利于插件的管理。

2插件Apk资源的加载

安卓中Apk资源的加载都是由Resources这个类来管理和控制的,包括主题Theme。所以关键是如何构造与插件Apk资源相关联的Resources,这个业界已经有常规化的解决方法了,就是通过反射调用AssetManager的addAssetPath将插件Apk路径与AssetManager相关联,然后使用该AssetManager作为参数创建Resources。该Resources就能使用插件Apk中的资源了。

3解决启动四大组件时出现未声明异常

我们知道Activity、Service、Content Provider必须在Manifest文件中声明才可以被创建使用,但是因为宿主和插件开发的无感知特性,一个成熟的插件化框架对于宿主和插件开发者而言是无需了解彼此的,宿主不可能提前知道插件App中定义了哪些四大组件,目前业界的通用做法就是在插件化框架lib的Manifest中预先声明一些四大组件,以Activity为例,然后在启动插件Activity的流程中需要检测该Activity是否声明之前将其intent信息替换为启动某个预先声明的StubActivity。这样就绕过了检测声明,最终在通过Handler.CallBack消息转发的时候再替换回来为插件中的待启动Activity。这里涉及到四大组件的创建和启动流程的很多细节,比较复杂,后面回专门详细介绍,这里仅仅是大的思路框架。

安卓中特有类的创建以及与直接安装到系统上的功能的一致性

具备安卓特性的常见的应用层的类主要包括Application,四大组件,Context/ContextImpl,Resource,ClassLoader。其中对于四大组件而言其核心功能本质上都是委托给Context/ContextImpl来完成的,所以我们只需要保证插件Apk的Application,Context/ContextImpl,Resource,ClassLoader在虚拟环境中免安装运行和安装到系统上点击运行的功能一致就行,也就是说我们得为每个插件维护其对应的这些类的信息,保证在使用这些类的某些功能时使用到的信息是和插件相关的,比如资源文件,app包名以及类代码等。而不能出现原本是插件App的代码在执行,结果返回了宿主的一些信息。比如插件App中有一处获取包名的代码或者设置布局文件的代码,结果返回了宿主的包名或使用了宿主的布局资源等。

插件Apk四大组件的生命周期的管理

这个问题看上去很难,但是实际上只要不是通过代理组件的方式来手动同步生命周期,而是通过欺骗系统去校验Stub组件然后替换回原组件的方式,那么生命周期实际上是不需要我们去同步的。原因涉及到四大组件启动流程的细节,这里大概提下,后面在自己动手实现插件化框架的过程中再写文章详细分析。以Activity为例,实际上对于系统进程的AMS而言是通过token来标识和管理Activity的,而不是通过类名。因此在创建了Activity对象之后,对于AMS而言会将这个对象和token建立关联,之后就用这个token来标识该Activity对象。

总述

通过前面三大难题解决思路的讲解,其实你很容易发现,插件化框架的大的宗旨就是把原本需要在安卓framework层由系统做的事提前到应用层由宿主App做掉。

那么哪些事情需要宿主App替系统做掉呢?实际上主要还是前面提到的几个部分:

  1. 替系统“安装”插件Apk 
  2. 替系统加载插件Apk的类和资源
  3. 替系统维护插件Apk的安卓特有类的功能的“正确性”,如Context/ContextImpl,Application,Resource等

这就引出了后续插件化系列的打算写的其他文章,暂定以下内容:

  1. 从插件化看安卓系统Apk安装的流程和本质
  2. 从插件化看安卓系统Activity的继承结构与启动流程
  3. 从插件化看安卓系统资源加载流程和原理
  4. 自己动手写一个插件化框架demo

因为类的加载和Application启动的细节在热修复相关专题中已经分析过了,所以基本上前3大块内容弄清楚之后就可以自己手撸一个支持免安装仅包含Activity组件的插件Apk的插件化框架了。若想支持其他3大组件也可以依此思路去分析,而且理论上其他三大组件的支持要比Activity简单些。

另外插件化技术领域也开源了很多优秀的框架,个人比较推荐如下2个:

https://github.com/didi/VirtualAPK

https://github.com/Qihoo360/RePlugin

其中RePlugin从技术原理上来讲不属于插件和宿主/插件化lib完全无感知。只不过因为该框架提供了和插件相关的gradle编译脚本在编译时自动替接入者把这些事做了。从使用上达到了开发插件对宿主完全无感知。(用RePlugin团队的话来说就是开发插件像开发单品那样)

写在最后

个人觉得插件化,热修复,Instan Run这3大应用场景是学习安卓framework层运行机制和原理的绝佳切入点,尽管网上有很多关于安卓framework层源码分析的文章,比如老罗的安卓之旅,但是绝大多数文章都是直接分析源码,长篇大论,如果你不是安卓系统/ROM开发工程师,不具备和framework层开发打交道的经验,那么很容易只见树木不见森林,抓不住重点,不知道哪些细节是很重要的,哪些细节是不重要的,导致走马观花,过后即忘。而这3大应用场景尤其是插件化需要对framework层的很多细节比较清楚。这也是为何我建议大家自己动手去写一个插件化框架的原因,当然不需要你去开发一个商用的兼容各个版本ROM的插件化框架,只需要保证能在你的测试机上免安装运行一个相对复杂的apk即可。当你完成这个任务之后,你对安卓系统framework层将会非常了解了。

打赏

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注