Android插件化快速入门与实例解析(VirtualApk)
2017-08-31 13:51
423 查看
集成一个第三方相册功能,只需集成一个插件APK到项目中,无需集成额外代码,并且支持随时更新相册功能,无需发布版本更新,无需AndroidManifest中声明四大组件,这就是插件化。
插件化可利用性很广,但事实上大多数开发者,因为未知而放弃使用,所以本篇将深入浅出带你了解插件化原理,从基础到实现,插件化不再是你陌生的领域。
本篇主要涉及到:
一、Activity/Service的启动原理和流程。
二、插件化实现原理。
三、DiDi开源VirtualApk源码解析(Activity/Service)。
VirtualApk优化反射带来损耗的小技巧。
ps:如果你对此(一、二)已经十分了解,请自行略过。
下方图片是Activity启动的简化流程,可以看到,从
在
(Activity启动流程详见图片)
![](https://oscdn.geek-share.com/Uploads/Images/Content/201912/10/c008d8fa03c16ead263e864417e37dec)
Activity简化启动流程图
下方图片是Service启动的简化流程,同样可以看到,
(Service启动流程详见图片)
Service简化启动流程图
![](https://oscdn.geek-share.com/Uploads/Images/Content/201912/10/f9d991b80ea914adf27d290a1dcc3571)
为了更好理解插件化,如下图,是几个关键类的对应关系与实际作用,有点S/C的味道。它们的通信是通过
(下图在插件化实现中起到关键作用)
关键类关系图
![](https://oscdn.geek-share.com/Uploads/Images/Content/201912/10/229d199a9217536c57331011d3e3ce44)
Hook:拦截某个内部流程,在其中做某些修改,以实现自己的逻辑。
Instrumentation:每个Activity都有一个
4000
/p>
占坑:声明一个不存在的Activity,如:
,这样启动.A$1这个Activity可以欺骗系统检测,然后再将插件Activity注入到.A$1这个坑位中。
1、初始化Hook住
2、启动插件Activity:提前在主APP中占有坑位,通过替换Intent中的targetActivity,打开占坑声明的A$Activity,然后绕过AndroidManifest检测,再拦截newActivity方法中恢复targetActivity。
3、启动插件Service:通过启动一个代理Service统一管理,拦截所有Service方法,修改为startService到代理Service,在代理Service的
![](https://oscdn.geek-share.com/Uploads/Images/Content/201912/10/cffc07ed06c66d70f71725373f723c9b)
初始化
如下图所示,VrutalApk通过
将
![](https://oscdn.geek-share.com/Uploads/Images/Content/201912/10/7ed1af665a4e64491549252919414115)
Hook Instrumentation 图1
这里是如何拿到
因为在
![](https://oscdn.geek-share.com/Uploads/Images/Content/201912/10/6c4adc1a0eadf366965d056d9101527e)
Hook Instrumentation 图2
另外,上方图1还有设置
如下图, 是Hook Service的流程,如图中注释所示,通过
![](https://oscdn.geek-share.com/Uploads/Images/Content/201912/10/23a02d9f8f666b5f278c9107f915f032)
Hook Service 图1
![](https://oscdn.geek-share.com/Uploads/Images/Content/201912/10/ff0e548f7e350bc84347ecf5bb794913)
加载Apk插件
此处对Apk进行了复杂的解析、加载、合并等操作,大致流程如下:
解析apk的相关包信息、判断是否加载过apk。
创建一些插件工具类。
通过
ClassLoader 根据插件APK路径创建loader,判断是否合并loader中的dex,合并nativeLIbraryDirectories。
将so复制到mNativeLibDir路径。
保存Instrumentation、Activities、Services、Providers , 注册Broadcast等。
创建出Apk的Application,并call Application onCreate。
execStartActivity:入口。
newActivity:创建。
callActivityOnCreate:通知。
handleMessage:处理。
没错,如下图,在启动Activity的入口处,VirtualApk拦截了请求,然后根据Intent的参数,去匹配plugin中的Activity坑位,之后替换Intent中的Activity,以此来达到欺骗系统的效果。
但是,因为这个Activity的对象了实际上并不存在,最终我们需要启动的是实现了的targetActivity,所以需要拦截
![](https://oscdn.geek-share.com/Uploads/Images/Content/201912/10/8b3534c319e1c1de25689bf103c3e7fa)
启动Activity
在
![](https://oscdn.geek-share.com/Uploads/Images/Content/201912/10/23e2723f9bd95fe47142287e13eb665b)
创建Activity
Activity虽然创建好了,但是它对应的资源和context都还不对,所以我们需要在Activity的OnCreate之前完成好Resource的注入。前面加载Apk时,这些资源都保存在
![](https://oscdn.geek-share.com/Uploads/Images/Content/201912/10/eb79269844ded607054eaf566aecf7f4)
屏幕快照 2017-07-15 上午12.36.21.png
如下图,是
![](https://oscdn.geek-share.com/Uploads/Images/Content/201912/10/4cc726667f21c10ba57c8545cb6fe193)
ActivityManagerProxy
startService这里,主要便是提取原本目标service信息,然后转化为代理Service,发送到代理Service,下方图片为启动流程和转化流程。
![](https://oscdn.geek-share.com/Uploads/Images/Content/201912/10/a5016b00ac47a9516c5b45cb10de1d50)
启动流程
![](https://oscdn.geek-share.com/Uploads/Images/Content/201912/10/78656e5841252e5a08ab8ab28060c1d2)
转化流程
如下图,在代理service中,根据请求类型,代理service会通过classLoader加载来创建service,并操作其attach、onCreate、onStartCommand等,让service工作起来。
![](https://oscdn.geek-share.com/Uploads/Images/Content/201912/10/73205c48eea062ccee1d5dfdc95a38fe)
启动真正的目标service
自此Activity和Service都成功启动了,是不是对插件化有了不一样的了解?
![](https://oscdn.geek-share.com/Uploads/Images/Content/201912/10/849160bca00eef48eefa0b0b1200cc68)
AndroidStub
因为都用反射很浪费性能,所以有了
![](https://oscdn.geek-share.com/Uploads/Images/Content/201912/10/9c8c0d377bddb482e8719ec99c2af46c)
但是如下图,VirtualApk通过
因为
![](https://oscdn.geek-share.com/Uploads/Images/Content/201912/10/5db81e26b98294583095a2428b01f64c)
ActivityThread
![](https://oscdn.geek-share.com/Uploads/Images/Content/201912/10/f0bd8ee180895ebd6333a32161ad2404)
CoreLibrary依赖AndroidStub
终于结束了,如果你看到了这里,相信你是一个很有耐心的同志!当然插件化还是其他实现方式,如Replugin,只Hook住了ClassLoader,流程更加复杂,如有什么建议和疑问,欢迎留言讨论。
VirtualApk:https://github.com/didi/VirtualAPK
个人github:https://github.com/CarGuo
作者:恋猫月亮
链接:http://www.jianshu.com/p/a7b36d682b6f
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
插件化可利用性很广,但事实上大多数开发者,因为未知而放弃使用,所以本篇将深入浅出带你了解插件化原理,从基础到实现,插件化不再是你陌生的领域。
本篇主要涉及到:
一、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
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
相关文章推荐
- Android插件化快速入门与实例解析(VirtualApk)
- Android插件化快速入门与实例解析(VirtualApk)
- Xamarin.Android 入门实例(1)之获取与解析JSON
- Android入门之Gallery+ImageSwitcher用法实例解析
- Android注解快速入门和实用解析
- Android入门之TabHost与TabWidget实例解析
- Android注解快速入门和实用解析
- Android入门之Gallery用法实例解析
- Android入门之PopupWindow用法实例解析
- Android入门之TabHost与TabWidget实例解析
- Android入门之Style与Theme用法实例解析
- JFreeChart初学者入门实例详细解析之二
- Android通过JNI调用驱动程序(完全解析实例)
- MyBatis:快速入门实例 HelloWorld
- Android抽屉导航Navigation Drawer实例解析
- Android 插件化原理解析——Hook机制之AMS&PMS
- windows phone:Expression Blend实例中文教程(7) - 动画基础快速入门Animation
- android 快速关机问题解析
- Android Unable to instantiate activity ComponentInfo 基本入门 短信 轰炸机 项目 解析
- Android突破七.SWIG快速入门