您的位置:首页 > 移动开发 > Android开发

Android插件化快速入门与实例解析(VirtualApk)

2017-08-31 13:51 423 查看
集成一个第三方相册功能,只需集成一个插件APK到项目中,无需集成额外代码,并且支持随时更新相册功能,无需发布版本更新,无需AndroidManifest中声明四大组件,这就是插件化。

  插件化可利用性很广,但事实上大多数开发者,因为未知而放弃使用,所以本篇将深入浅出带你了解插件化原理,从基础到实现,插件化不再是你陌生的领域。

本篇主要涉及到:

一、Activity/Service的启动原理和流程。
二、插件化实现原理。
三、DiDi开源VirtualApk源码解析(Activity/Service)。
VirtualApk优化反射带来损耗的小技巧。
ps:如果你对此(一、二)已经十分了解,请自行略过。

一、 Activity/Service启动流程

  Activity和Service的启动流程十分复杂,一个
startActivity
的背后是无数的逻辑实现,这里不深入讨论,但需要理解这个流程,因为插件化是在流程上动手脚,以达到绕过系统限制的目的。

  下方图片是Activity启动的简化流程,可以看到,从
Instrumentation
开始,到
ActivityManagerService
ActivityThread
结束,启动一个Activity,流程并不简单。

  在
Instrumentation
execStartActivity
开始启动,到通过
checkStartActivityResult
校验Activity是否在Manifest中声明,从图中可以看出,流程还是相当繁琐的。

  

Activity启动流程详见图片



Activity简化启动流程图

  下方图片是Service启动的简化流程,同样可以看到,
ActivityManagerService
ActivityThread
同样起到了关键性的作用。插件化的关键,就在于
Instrumentation
ActivityManagerService
ActivityThread


Service启动流程详见图片

Service简化启动流程图



  为了更好理解插件化,如下图,是几个关键类的对应关系与实际作用,有点S/C的味道。它们的通信是通过
IBinder
,实现进程通信的,可以看出,启动Activity和Service,
ActivityThread
ActivityManagerService
是关键,并且上面我们知道,
Instrumentation
是Activity的启动入口,所以实现插件化的流程,便可以在这些关键类上开刀。

下图在插件化实现中起到关键作用

关键类关系图



提前说明

  好了,带了一波基础姿势的节奏,稍安勿躁,先这里在补充几个概念,如果你已经习得,可以跳过:

Hook:拦截某个内部流程,在其中做某些修改,以实现自己的逻辑。

Instrumentation:每个Activity都有一个
Instrumentation
对象,它是在Activity启动是被赋予的
Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity();
这便是startActivityForResult的启动,同时返回启动结果。<
4000
/p>

占坑:声明一个不存在的Activity,如:
<activity android:name=".A$1" android:launchMode="standard"/>


,这样启动.A$1这个Activity可以欺骗系统检测,然后再将插件Activity注入到.A$1这个坑位中。

二、插件化实现原理。

  插件化的实现就是在于加载、绕过系统限制、启动和管理插件等过程。按照VirtualApk的实现,大致流程为:

1、初始化Hook住
Instrumentation
ActivityThread
等。通过PackageParser(插件apk包信息)、AssetManager(资源文件Resources)、ClassLoader等加载一个Apk插件。

2、启动插件Activity:提前在主APP中占有坑位,通过替换Intent中的targetActivity,打开占坑声明的A$Activity,然后绕过AndroidManifest检测,再拦截newActivity方法中恢复targetActivity。

3、启动插件Service:通过启动一个代理Service统一管理,拦截所有Service方法,修改为startService到代理Service,在代理Service的
onStartCommond
统一管理,创建/停止目标service 。

三、VirtualApk源码解析

1、初始化

  初始化过程中,VirtualApk 创建了PluginManager ,并且hook住了
Instrumentation
和SystemService,如下图所示。



                                                                                                   初始化

  如下图所示,VrutalApk通过
Instrumentation
创建了一个
VAInstrumentation
对象,
VAInstrumentation
是一个继承
Instrumentation
的类。

  将
VAInstrumentation
反射插入到
ActivityThread
中,这样系统接下来关于
Instrumentation
的操作,就会回到
VAInstrumentation
中,被VrtualApk接管。



                                                                                           Hook Instrumentation 图1

  这里是如何拿到
Instrumentation
的?

  因为在
ActivityThread
内部有一个
sCurrentActivityThread
静态变量。如下图,通过反射
sCurrentActivityThread
我们可以获取当前
ActivityThread
,而
ActivityThread
的公开方法
getInstrumentation
即可拿到Instrumentation对象。



                                                                                             Hook Instrumentation 图2

  另外,上方图1还有设置
HandlerCallback
的流程,其实就是拦截了
ActivityThread
中的
mH
这个Handler的Callback,从【 一、 Activity/Service启动流程】流程图可以看到,mH的handleMessage处理很多Activity的启动状态。

  如下图, 是Hook Service的流程,如图中注释所示,通过
ActivityManagerNative
getDefault
,拿到
AndroidManagerService
(详见启动流程图),而VirtualApk通过自定义
ActivityManagerProxy
,重新生成了一个
IActivityManager
,然后注入回
AndroidManagerService
中,这样接管了系统启动、管理service等操作。



                                                                                                    Hook Service 图1

2、加载插件Apk

  加载插件APK是通过
PluginManager
loadPlugin
方法,如下图所示,此处就是将apk拆开,解析,读取,加载,组装为LoadedPlugin并保存,以方便后面管理与使用。



                                                                                                                      加载Apk插件

  此处对Apk进行了复杂的解析、加载、合并等操作,大致流程如下:

解析apk的相关包信息、判断是否加载过apk。
创建一些插件工具类。
通过
AssetManager
创建
Resource
对象,平台用AssetManager创建出Resource,判断是否和宿主Apk合并资源。
ClassLoader 根据插件APK路径创建loader,判断是否合并loader中的dex,合并nativeLIbraryDirectories。
将so复制到mNativeLibDir路径。
保存Instrumentation、Activities、Services、Providers , 注册Broadcast等。
创建出Apk的Application,并call Application onCreate。

3、启动插件Activity

  那么是时候启动插件Activity了。通过startActivity便可以启动。从上面的流程我们知道启动是从
Instrumentation.execStartActivity();
开始的,而系统的
Instrumentation
已经被
VAInstrumentation
替换,其中
VAInstrumentation
重写了几个关键方法:

execStartActivity:入口。
newActivity:创建。
callActivityOnCreate:通知。
handleMessage:处理。
  没错,如下图,在启动Activity的入口处,VirtualApk拦截了请求,然后根据Intent的参数,去匹配plugin中的Activity坑位,之后替换Intent中的Activity,以此来达到欺骗系统的效果。

  但是,因为这个Activity的对象了实际上并不存在,最终我们需要启动的是实现了的targetActivity,所以需要拦截
Instrumentation
的第二个方法
newActivity
,因为在
ActivityTread
performLaunchActivity
中,会调用
Instrumentation
newActivity




                                                                                                                       启动Activity

  在
newActivity
中,如下图,类没有找到时(坑位类肯定找不到啦),那么就去获取原本保存在Intent的目标Activity,然后调用创建
VAInstrumentation
时保存的
Instrumentation
(mBase)去创建Activity。



                                                                                                                            创建Activity

  Activity虽然创建好了,但是它对应的资源和context都还不对,所以我们需要在Activity的OnCreate之前完成好Resource的注入。前面加载Apk时,这些资源都保存在
Plugin
中。所以我们拦截
callActivityOnCreate
方法,如下图,将Activity的Context、Application、Reource,都替换成Plugin中对应的对象。一个Activity就这样绕过AndroidManifest启动起来了。



                                                                                                     屏幕快照 2017-07-15 上午12.36.21.png

4、启动插件Service

  startService启动Service时,还记得上面我们通过
ActivityManagerProxy
生成
IActivityManager
吗?它主要拦截了Service相关启动和停止等方法,然后将其都转化为对应的startService方法,指向代理Service。因为startService方法的特性,他们最终都会在代理Service的
onStartCommand
中被统一处理。

  如下图,是
ActivityManagerProxy
,其中invoke拦截了所有相关的服务请求,并做了转化处理,下面以startService为例。



                                                                                          ActivityManagerProxy

  startService这里,主要便是提取原本目标service信息,然后转化为代理Service,发送到代理Service,下方图片启动流程转化流程



                                                                                                                启动流程



                                                                                                                  转化流程

  如下图,在代理service中,根据请求类型,代理service会通过classLoader加载来创建service,并操作其attach、onCreate、onStartCommand等,让service工作起来。



                                                                                         启动真正的目标service

自此Activity和Service都成功启动了,是不是对插件化有了不一样的了解?

5、AndroidStub

容许这里插入这一块,安利下Virtual中的AndroidStub模块,如下图



                                                                                            AndroidStub

  因为都用反射很浪费性能,所以有了
AndroidStub
,它是用来欺骗编译器的。正常情况下你想操作
ActivityThread
就会出现如下图情况,因为它是一个
@hide
类,这时候除了反射得到
ActivityThread
,你还需要再反射需要执着方法才能执行,这在一定程度会损耗一些性能。



  但是如下图,VirtualApk通过
AndroidStub
,模拟源码创建了如
ActivityThread
类,这里你就可以如图正常使用
ActivityThread
了,而
AndroidStub
中的
ActivityThread
,其实只是定义了和原码中一摸一样的方法,并没有其他实现。

  因为
CoreLibrary
依赖
AndroidStub
使用的是
provided
,因为
provided
依赖是不打包依赖包,而是运行时提供,所以成功欺骗了编辑器,以此提高了性能。很神奇吧?



                                                                                              ActivityThread



                                                                       CoreLibrary依赖AndroidStub

  终于结束了,如果你看到了这里,相信你是一个很有耐心的同志!当然插件化还是其他实现方式,如Replugin,只Hook住了ClassLoader,流程更加复杂,如有什么建议和疑问,欢迎留言讨论。

VirtualApk:https://github.com/didi/VirtualAPK

个人github:https://github.com/CarGuo

作者:恋猫月亮

链接:http://www.jianshu.com/p/a7b36d682b6f

來源:简书

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: