为什么出现插件化?
- 技术上
由于代码量的增加,App遇到65535问题。
- 业务上
模块解耦,独立开发,独立上线。
插件化要解决的问题
- 代码加载
ClassLoader机制:可以用此进行类的加载。
组件生命周期管理:对于Android来说,组件是有生命的,所以还需要进行此项任务。
- 资源加载
加载方式:基本大同小异,使用AssetManager的隐藏方法addAssetPath。
管理方式:或者共用一套资源,采用资源分段机制解决冲突;或者独立资源,不同插件管理自己的资源。
为什么选择360 DroidPlugin 进行介绍?
DroidPlugin Hook了系统几乎所有的Sevice,欺骗了大部分的系统API。
DroidPlugin中,插件是有血有肉的系统管理的真正组件。
DroidPlugin通过Hook增强了Framework层的很多系统服务,开发插件就跟开发独立app差不多。
Hook机制
- 参考上上一篇
Binder代理
- 参考上一篇
四大组件的插件化
Activity
前言
- 必须明确的问题:
进程间通信需要Binder对象。
插件化技术只能在本进程中做手脚,由于进程隔离的存在,我们对别的进程无能为力。
一般的Activity都是需要在AndroidManifest.xml中显式声明,否则报异常。
启动没有在AndroidManifest.xml中显式声明,但存在于自身APK中的Activity
-
Activity启动过程
Activity的启动过程可以简要的通过下图总结,分为三步:第一步,先从App进程启动Activity;第二步,通过IPC通信,进入系统进程System_server,完成生命周期,堆栈和权限管理等等;第三步,回到App进程完成Activity对象的创建,并回调其生命周期方法。
策略
既然插件中的Activity无法预先在AndroidManifest.xml中显式声明,那么我们可以先在AndroidManifest.xml中,显示声明一个假的Activity;在第一步启动真Activity的时候,将真的Activity替换为假的Activity;然后用其来骗过系统,让系统为我们提供服务(如生命周期,权限管理等);最后在第三步再将假的Activity替换回插件中真的Activity。
因此,hook点有两个:第一步和第三步。实施
1.AndroidManifest.xml中显示声明假的Activity
2.Hook AMS在本进程的代理对象ActivityManagerNative,针对startActivity方法,将真Activity替换为假Activity
3.Hook Handler的mCallback对象,在自定义的mCallback对象中,将假Activity还原回真Activity注意
1.以上过程完成后,插件Activity已具有正常的生命周期。因为,对于生命周期的回调,AMS与App之间,并没有使用Activity,而是使用token标识。token标识已经能够连接真假Activity。
2.Activity的不同的启动模式也需要支持。
3.由于IntentFilter的不确定性,所以不支持以IntentFilter的方式启动插件化中的Activity。
4.本文只是加载自身APK中的类,但一般插件都存在于其他APK中,这种问题如何解决?接下来就来讲一下。
启动没有在AndroidManifest.xml中显示声明,并且存在于外部插件中的Activity
- 思路
如何将插件的apk或者dex文件告知宿主,让其完成插件类的加载?主要有两种方式:Hook ClassLoader,全盘接管类加载的过程;委托系统,告知系统插件相关信息,让系统帮忙完成加载 - 方式一:全盘接管
DroidPlugin采用这种方式,Hook点较多,但每个插件都有自己对应的ClassLoader,类的隔离性好,可完成代码的热加载。 - 方式二:系统帮忙
MutiDex采用这种方式,Hook点较少,简单,但插件和宿主共用宿主的ClassLoader,类的隔离性差,不可完成代码的热加载,可能需要重启进程。
BroadcastReceiver
前言
- 必须明确的问题:
广播分静态广播和动态广播,两种广播的不同造成插件化方式也不同。静态广播需要预注册,注册信息存在PMS中;动态广播不需要预注册,注册信息存在AMS中;
动态广播
思路
不需要预注册,并且生命周期简单,只存活于onReceiver回调中。所以可自行手动控制生命周期。实施
1.利用ClassLoader机制,加载插件中的Receiver。从而实现注册和发送。
2.使得插件中的Receiver接收onReceiver回调。从而实现接收。
静态广播
思路
与Activity一样,需要预注册,但却不能与Activity的方式一样完成插件化,因为IntentFilter的存在,宿主无法预知插件中具体使用的IntentFilter。
因为动态广播可以动态添加IntentFilter,所以可将静态广播转为动态广播,以此方式实现静态广播的插件化。实施
1.解析插件AndroidManifest.xml,获取receiver标签下面的内容
2.利用ClassLoader机制,加载插件中的Receiver,并把解析出来的每一个静态Receiver都注册为动态的。从而实现注册和发送。
3.使得插件中的Receiver接收onReceiver回调。从而实现接收。注意
因为静态广播转为动态广播,所以会牺牲静态广播的一个优点,即静态广播在进程死亡之后是可以接收广播的。
Service
- Service工作原理
Service组件的工作原理与Activity的工作原理,在流程上基本一致,都是从APP进程到AMS,然后又从AMS回到APP进程中,在APP主线程中完成相关生命周期的回调。
策略
但Service与Activity却有一个比较大的区别:Activity的生命周期受到用户行为的影响,而Service作为后台任务,用户的操作行为则不会影响其生命周期。用户行为是不确定的,并且只有系统才能感应到,所以,我们在Activity插件化的时候,依然依托于系统对于生命周期的管理;而Service则与用户行为无关,所以生命周期完全可以由我们手动控制,相对于Activity更简单。
但是简单的手动的操作生命周期也是不够充分的,因为Service还有个进程优先级的概念,并且优先级相对较高,因此我们仍然需要一个能够被系统能够认可的Service组件,依托于它,来得到Service组件所应具备的能力。
结论是,我们可以效仿DL插件化方案,使用的代理的方式,实现Service的插件化。实施
1.AndroidManifest.xml中显示声明假的Service,使其承载一个真正Service组件所应具备的能力(如进程优先级)。
2.Hook AMS在本进程的代理对象ActivityManagerNative,针对startService方法,将真Service替换为假Service。其他方法(stopService等)也基本一致。
3.以上两步与Activity的插件化基本一致,但第三步就不同了。在假Service的各个生命周期方法中(onStartCommand,onStart等),进行任务分发,执行真Service的onStartCommond,onStart等对应的方法。在分发的过程中,我们不得不注意以下几个问题:首先,为了插件中的所有类都可以被正常访问,需要完成插件的加载;然后,在加载插件的时候,我们存储了插件中所有的Service组件的信息,因此,只需要根据Intent中的Component信息就可以匹配到对应的真Service;接着,就是创建真Service的Java对象,这里注意一下,由于是自己管理生命周期,所以这里我们需要给创建的真Service创建Context对象。最后,执行任务分发,直接执行真Service对应的生命周期方法即可。注意
1.针对Service的多进程模式的支持,可以声明多个不同进程的假Service。side effect就是同一个进程的所有真Service都挂载在同一个假Service上,如果假Service挂掉,所应依附于它的真Service也同时都挂掉。
2.以上Service的实现方式与DL插件化方案类似。
ContentProvider
- ContentProvider工作原理
1.ContentProvider的核心是数据共享,提供CRUD服务,可以完成跨进程的大数据量传输,主要依赖于匿名共享内存(Ashmem)机制。Binder在此只是用来传递文件描述符,支持跨进程共享。另外,与其他三大组件不同,其没有生命周期。
2.之前阐述的其他三大组件的插件化方案是,只有通过插件系统启动的进程,才能感知到插件中的它们,而Android系统却无法真正的感受到它们,因而第三方的App都无法使用插件的它们。而我们的希望将插件的ContentProvider共享给整个Android系统,这才是它存在的真正意义。
3.ContentProvider在Android系统启动或者新安装App的时候就完成了信息的注册。
4.ContentProvider会优先查询本进程,获取其他进程的ContentProvider需要借助AMS。基于这一点,如果只是想把ContentProvider提供给本进程使用,那么插件化方案可以不需要Hook AMS。
5.ContentProvider有个安装的过程,这个过程可以理解为一层缓存,方便使用ContentProvider时,直接拿取使用。 另外,安装是以进程为单位的(目标进程和本地进程都安装)。
6.App的启动过程中,ContentProvider就会进行安装,这个安装的时机比Application的onCreate还要早。
实施
1.与Service的插件化几乎是相同:.Hook AMS在本进程的代理对象ActivityManagerNative,拦截getContentProvider方法和publishContentProvider方法,从而实现对于插件ContentProvider的控制。
2.另外需要注意:进行任务分发的时候,需要制定规则,以拿到真正的插件ContentProvider信息,DroidPlugin将插件信息放在查询参数里面。注意
ContentProvider的使用频度并不高,插件系统可以考虑不支持,或者轻量支持(只共享给本App,不共享给系统,这样不需要Hook AMS,实现更简单)。
总结
- 四大组件不过就是一些普通Java类,但如果仅此而已,我们完全可以简单地采用Java的动态加载技术来实现插件化。然后事实并非如此简单,四大组件还有一个重要特性是:其具有生命周期(ContentProvider除外),正是因为如此,我们的插件化实现方案还需要关注如何赋予它们“生命”。这两点,就是我们实现四大组件插件化过程中,所需要重点解决的两点。