Android APP热更新中的插件化(Hook技术:反射或动态代理),Demo (2)

2019年12月05日 阅读数:82
这篇文章主要向大家介绍Android APP热更新中的插件化(Hook技术:反射或动态代理),Demo (2),主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

修改AAPT,资源分区,用于Android插件化- https://github.com/BaoBaoJianqiang/AAPTjava

-- Android下的挂钩(hook)和代码注入(inject)
  api hook技术有2种elf hook 和inline hook。Elf hook 经过修改动态链接库的PLT/GOT表,从而达到函数调用的重定向目的,这种方法只能hook模块的外部调用,例如hook打开文件的系统函数检测程序打开文件的状况,hook系统时间相关的函数,达到加速的目的(市面上的加速外挂基本都是采起这种方法)。可是这种方法不能hook模块的内部调用,由于模块内部调用不须要查GOT表。而游戏引擎的功能都封装在一个动态链接库里,基本都是内部调用,ELF HOOK没法生效。本文所采用的是另一个方法:INLINE HOOK。
  INLINE HOOK的思路大体是这样:首先找到目标函数在内存中的地址,而后把该地址块设置为可写,修改目标函数地址的内容,让游戏调用目标函数时跳转到咱们本身的函数地址,咱们的函数执行完后再跳转回来。这样不管是模块内部调用或外部调用,INLINE HOOK都能生效。android

 古河提供的libinject框架。Android so注入(inject)和Hook技术。
 xposed框架使用的基本思路即是首先定位到本身想要进行hook的函数,在xposed框架中进行注册,而后获取了该函数的处理权,即可以进行相应的操做。git

Android so注入( inject)和Hook(挂钩)的实现思路讨论- https://blog.csdn.net/qq1084283172/article/details/54095995github

  Dexposed、AndFix,(HotFix)Sophix,Qzone超级补丁的类Nuwa方式,微信的Tinker, 大众点评的nuwa、百度金融的rocooFix, 饿了么的amigo以及美团的robust、腾讯的Bugly热更新。api

-- 热更新
 DexPathList对象中的dexElements列表是类加载的一个核心,一个类若是能被成功加载,那么它的dex必定 
会出如今dexElements所对应的dex文件中,而且dexElements中出现的顺序也很重要,在dexElements前面出现的dex会被优先加载,一旦Class被加载成功, 就会当即返回,也就是说,咱们的若是想作hotpatch,必定要保证咱们的hotpacth dex文件出如今dexElements列表的前面。数组

要实现热更新,就须要咱们在运行时去更改PathClassLoader.pathList.dexElements,因为这些属性都是private的,所以须要经过反射来修改。另外,构造咱们本身的dex文件 所对应的dexElements数组的时候,咱们也能够采起一个比较取巧的方式,就是经过构造一个DexClassLoader对象来加载咱们的dex文件,而且调用一次dexClassLoader.loadClass(dummyClassName); 
方法,这样,dexClassLoader.pathList.dexElements中,就会包含咱们的dex,经过把dexClassLoader.pathList.dexElements插入到系统默认的classLoader.pathList.dexElements列表前面,就可让系统优先加载咱们的dex中的类,从而能够实现热更新了。
百度的同窗的实现 HotFix- https://github.com/dodola/HotFix
点评的同窗的实现 Nuwa- https://github.com/jasonross/Nuwa缓存

> 插件化(Hook与ClassLoader)
 -- 注解、反射和动态代理:
 1.注解:编译时和运行时注解
 2.反射:在运行态的时候能够动态地建立对象、调用对象的方法、修改对象的属性。
 3.代理模式的概念就是:为原对象提供一个代理对象,原对象和代理对象对外提供相同的 API 方法,客户经过代理对象间接地操做原对象,可是客户并不知道它操做的是代理对象。安全

 -- 静态代理和动态代理:(编译时和运行时)
  1.静态代理:代理类在编译后就已经实现好了,也就是在编译以后,代理类就是一个 class 文件
  2.动态代理:代理类是在运行期间生成的。也就是说,在编译以后,并无代理类对应的 class 文件,而是在运行期间动态的生成字节码文件,并加载到虚拟机中的。微信

 -- IoC 和  DIapp

  IoC:在 Java 开发中最著名的 IoC 框架莫过于 Spring,在 Android 开发中,IoC 框架也有不少,好比:ButterKnife、EventBus、Dagger、Dagger2 等框架都运用了 IoC 的思想。https://github.com/lijiankun24/IOCPractice
  DI:依赖注入(Dependence Injection,简称DI),DI 的概念是指组件之间的依赖关系由容器在运行期间决定。

  研究过 Retrofit 或者 RxCache 的源码的同窗都知道,其底部原理都是基于Java 的动态代理,经过反射的方式解析接口方法中的配置(好比参数/返回值/注解),生成对应的 proxy对象。

> 插件化,组件化,动态加载

 1.Android 插件化 —— 指将一个程序划分为不一样的部分,好比通常 App 的皮肤样式就能够当作一个插件;
 
 2.Android 组件化 —— 这个概念实际跟上面相差不那么明显,组件和插件较大的区别就是:组件是指通用及复用性较高的构件,好比图片缓存就能够当作一个组件被多个 App 共用;
 
 3.Android动态加载 — 这个实际是更高层次的概念,也有叫法是热加载或 Android 动态部署,指容器(App)在运⾏状态下动态加载某个模块,从而新增功能或改变某⼀部分行为.

> 插件化,Hook技术是反射吗

Hook技术分两种:经过反射或动态代理。

-- Android插件化原理(一)Activity插件化- https://blog.csdn.net/itachi85/article/details/80574390
  Activity插件化主要有三种实现方式,分别是反射实现、接口实现和Hook技术实现。反射实现会对性能有所影响,主流的插件化框架没有采用此方式,关于接口实现能够阅读dynamic-load-apk的源码
  Hook技术实现主要有两种解决方案 ,一种是经过Hook IActivityManager来实现,另外一种是Hook Instrumentation实现。
  Activity的启动过程主要分为两种,一种是根Activity的启动过程,一种是普通Activity的启动过程。

thumb16 thumb32 arm32 inlineHook in Android- https://github.com/ele7enxxh/Android-Inline-Hook

Java层hook插件:Xposed- https://github.com/fourbrother/xposedhookdemo
Native层hook:SubstrateCydia- https://github.com/fourbrother/CydiaSubstrateHook
Android 动态代理以及利用动态代理实现 ServiceHook- http://blog.csdn.net/self_study/article/details/55050627

-- 黑科技 hook方案 :Art Hook 技术方案,Dalvik Hook,Java Hook,Native Hook
比较成熟的几个HOOK框架及其应用:XPOSED,frida,substrate。
Android主流HOOK框架介绍与应用--游戏破解游戏外挂的必杀技- http://blog.csdn.net/asmcvc/article/details/55047842/
Android Art Hook 技术方案- http://blog.csdn.net/l173864930/article/details/45035521
Android Java Hook 方法- http://blog.csdn.net/wangbaochu/article/details/41898577

HookAMS菊花(GitHubDemo)- https://github.com/BolexLiu/AndroidHookStartActivity
HookPMS代码下载地址- https://github.com/fourbrother/HookPmsSignature
免root实现Hook Android系统服务拦截方法- http://blog.csdn.net/jiangwei0910410003/article/details/52523679
thumb16 thumb32 arm32 inlineHook in Android- https://github.com/ele7enxxh/Android-Inline-Hook

安卓插件化与热修复的选型- http://blog.csdn.net/starry_eve/article/details/52770959
Android插件化:从入门到放弃- http://blog.csdn.net/smallspot/article/details/52221049
Android插件化:从入门到放弃- http://www.infoq.com/cn/articles/android-plug-ins-from-entry-to-give-up
微店 Android 插件化实践- http://blog.csdn.net/Px01Ih8/article/details/79060707
Android中的动态加载机制-- http://blog.csdn.net/jiangwei0910410003/article/details/17679823
Android中插件开发篇之----类加载器-- http://blog.csdn.net/jiangwei0910410003/article/details/41384667
Android中插件开发篇总结和概述-- http://blog.csdn.net/jiangwei0910410003/article/details/48104581

-- 插件化技术听起来高深莫测,实际要解决的就是三个问题:
 1.代码加载,ClassLoader 机制;
 2.资源加载,都是用 AssetManager 的 @hide 方法 addAssetPath;
 3.组件的生命周期, Hook AMS、PMS 等。 

-- 插件化发展到目前,总结一下,有两种实现方式,一是基于SDK层组件Delegate(委托&代理)的方式,这种方式不用关心系统底层源码,框架实现成本较低,但插件开发成本较高;二是Framework hook,这种方式是对运行时环境作不少的hook和patch,使得插件的约束很小,随着这种方式的成熟,渐渐就发展成了双开,从而诞生了一系列双开APP。固然,对于Weex、React Native这些,他们的发展可能更局限在界面层,对于内容丰富的app,例如天猫、淘宝,就很适合,可是绝对作不到双开那种程度。
   插件开发的原理就是:动态加载技术。Dalvik虚拟机毕竟不算是标准的Java虚拟机,所以在类加载机制上,它们有相同的地方,也有不一样之处。

-- Android中插件开发篇之- 应用换肤原理解析-http://blog.csdn.net/jiangwei0910410003/article/details/47679843
  将插件apk中资源添加到宿主apk中。这时候就须要用一种方式了,采用反射的机制:
经过调用AssetManager中的addAssetPath方法,咱们能够将一个apk中的资源加载到Resources中,因为addAssetPath是隐藏api咱们没法直接调用,因此只能经过反射,下面是它的声明,经过注释咱们能够看出,传递的路径能够是zip文件也能够是一个资源目录,而apk就是一个zip,因此直接将apk的路径传给它,资源就加载到AssetManager中了,而后再经过AssetManager来建立一个新的Resources对象,这个对象就是咱们可使用的apk中的资源了。

  应用换肤的原理,核心技术就是:如何加载插件Apk中的资源。加载会有两种方式:
一、使用反射机制修改类加载器
二、使用代理的方式

  
-- 类加载器是符合双亲委派机制的。PathClassLoader和DexClassLoader类加载器的父类BaseDexClassloader
  在使用反射机制来动态加载Activity的时候,有两个思路:
1>、替换LoadApk类中的mClassLoader变量的值,将咱们动态加载类DexClassLoader设置为mClassLoader的值
2>、合并系统默认加载器PathClassLoader和动态加载器DexClassLoader中的dexElements数组
这两个的思路原理都是同样的:就是让咱们动态加载进来的Activity可以具有正常的启动流程和生命周期。

-- Android免Root无侵入AOP框架Dexposed- http://blog.csdn.net/u010687392/article/details/48316051
 插件化开发—动态加载技术加载已安装和未安装的apk- http://blog.csdn.net/u010687392/article/details/47121729
使用动态加载技术时,通常须要用到这两个类加载器:
  PathClassLoader - 只能加载已经安装的apk,即/data/app目录下的apk。
  DexClassLoader  - 能加载手机中未安装的apk、jar、dex,只要能在找到对应的路径。
优雅的App彻底退出方案(没有任何内存泄漏隐患)- http://blog.csdn.net/u010687392/article/details/46879081

  DexClassloader和PathClassloader动态加载插件的实现:
DexClassLoader:可加载jar、apk和dex,能够从SD卡中加载(本文使用这种方式);
PathClassLoader:只能加载已经安装搭配Android系统中的apk文件。
  不少博客里说PathClassLoader只能加载已安装的apk的dex,其实这说的应该是在dalvik虚拟机上,在art虚拟机上PathClassLoader能够加载未安装的apk的dex(在art平台上已验证)。

-- 插件两种方式的比较:
 第一种方式:使用反射机制来实现,
  优势:能够不用太多的关心插件中的Activity的生命周期方法,由于他加载进来以后就是一个真正意义上的Activity了
  缺点:须要在宿主工程中进行声明,若是插件中的Activity多的话,那么就不灵活了。
 第二种方式:使用代理机制来实现,
  优势:不须要在宿主工程中进行声明太多的Activity了,只须要有一个代理Activity的声明就能够了,很灵活
  缺点:须要管理手动的去管理插件中Activity的生命周期方法,难度复杂。

> 各公司插件化示例

携程实现app的插件化开发和热更新(caapt)- https://github.com/CtripMobile/DynamicAPK

1)插件化-360的DroidPlugin与RePlugin
  插件化是用来发布新功能的。通常来讲,大版本是一个月一次,中途想上一个功能,这时候才是插件化的最好使用场景。因此说,要把新功能发布和热修复拆开成两套机制,而不要混为一谈。
  为了解决这个尴尬的问题,咱们曾经试图把App作成多进程的,每一个模块的插件都是一个单独的进程,插件升级会自动杀以前插件的进程,而后再启动新的进程来运行新版本的插件。这就是张勇的DroidPlugin的思想,在他的框架出来以前,国内的插件化机制都是单进程的,都有我刚才说的这个问题。而后就有人给出一种解决方案让App崩溃后重启,虽然也能解决问题,但就会产生一种搞笑的场景,用户正在酒店模块下单,这时由于机票模块的插件升级而重启,这是很让人抓狂的事情。
  Service这个组件,大都用于在线音乐的App,由于后台也要能播放音乐。此外,无论是哪款App,只要涉及到聊天,都须要Service插件来帮忙修改App图标上的未读消息数。
  Broadcast和ContentProvider何时使用?像手机助手、安全卫士这类的App会着重依赖于这些技术,因此你会看到DroidPlugin必需要实现这两个组件的插件化技术,由于做者是在360这家公司,而任玉刚的开源框架(目前途牛在使用),以及携程的框架,他们只支持了Activity和Service就够了,由于大多数电商App有这两个就够了。

-- DroidPlugin
Droid Plugin技术文档- https://github.com/Qihoo360/DroidPlugin/blob/master/readme_cn.md
  DroidPlugin应用免安装领域,RePlugin全面插件化领域:
  DroidPlugin主要解决的是各个独立功能拼装在一块儿,可以快速发布,其间不须要有任何的交互。目前市面上的一些双开应用,和DroidPlugin的思路有共同之处。固然了,要作到完整的双开,则仍须要大量的修改,如Native Hook等。DroidPlugin- https://github.com/Qihoo360/DroidPlugin
  RePlugin解决的是各个功能模块能独立升级,又能须要和宿主、插件之间有必定交互和耦合。RePlugin- https://github.com/Qihoo360/RePlugin

小白也能看懂的插件化DroidPlugin原理(二)-- 反射机制和Hook入门- https://blog.csdn.net/a545415/article/details/77148214
  DroidPlugin 原理中涉及到的动态代理模式;DroidPlugin 框架中经常使用到的另外两个知识点--反射机制和Hook技术。
  JAVA反射机制是在运行状态中,对于任意一个类,都可以知道这个类的全部属性和方法;对于任意一个对象,都可以调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
  目前对 Hook 的理解,通俗来将就是经过某种手段对一件事物进行偷梁换柱,从而劫持目标来以达到控制目标的行为的目的。从技术角度来讲,就是替换原有的对象,拦截目标函数/方法,从而改变其原有的行为。

   奇虎360 DroidPlugin- https://github.com/Qihoo360/DroidPlugin, 360工程师开发的 DroidPlugin,DroidPlugin支持对完整apk的动态加载,可是没有关于非独立插件的加载(资源分区要复杂得多);插件开发的技术,固然本质仍是动态加载。
  -- 限制和缺陷:
 1.没法在插件中发送具备自定义资源的Notification,例如: a. 带自定义RemoteLayout的Notification b. 图标经过R.drawable.XXX指定的通知(插件系统会自动将其转化为Bitmap)
 2.没法在插件中注册一些具备特殊Intent Filter的Service、Activity、BroadcastReceiver、ContentProvider等组件以供Android系统、已经安装的其余APP调用。
 3.缺少对Native层的Hook,对某些带native代码的apk支持很差,可能没法运行。好比一部分游戏没法看成插件运行。
  -- 特色:
 1.支持Androd 2.3以上系统
 2.插件APK彻底不需作任何修改,能够独立安装运行、也能够作插件运行。要以插件模式运行某个APK,你无需从新编译、无需知道其源码。
 3.插件的四大组件彻底不须要在Host程序中注册,支持Service、Activity、BroadcastReceiver、ContentProvider四大组件
 4.插件之间、Host程序与插件之间会互相认为对方已经"安装"在系统上了。
 5.API低侵入性:极少的API。HOST程序只是须要一行代码便可集成Droid Plugin
 6.超强隔离:插件之间、插件与Host之间彻底的代码级别的隔离:不能互相调用对方的代码。通信只能使用Android系统级别的通信方法。
 7.支持全部系统API
 8.资源彻底隔离:插件之间、与Host之间实现了资源彻底隔离,不会出现资源窜用的状况。
 9.实现了进程管理,插件的空进程会被及时回收,占用内存低。
 10.插件的静态广播会被看成动态处理,若是插件没有运行(即没有插件进程运行),其静态广播也永远不会被触发。

-- RePlugin 
360手机卫士插件化RePlugin今日开源- http://geek.csdn.net/news/detail/208697 
RePlugin 接入指南- https://github.com/Qihoo360/RePlugin/wiki/%E4%B8%BB%E7%A8%8B%E5%BA%8F%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
RePlugin技术文档- https://github.com/Qihoo360/RePlugin/blob/master/README_CN.md
  使用RePlugin插件化的话,子工程plugin以Jar/AAR包的形式供宿主Host App调用。以下:
RePlugin.startActivity(MainActivity.this, RePlugin.createIntent("demo1", "com.qihoo360.replugin.sample.demo1.MainActivity"));

2)Lody开源插件化框架- Direct-load-apk

 Legend是Lody开源的一个Android免Root环境下的一个APK Hook框架(Art Hook方案)- https://github.com/asLody/legend

3)百度工程师开发的 Dynamic-load-apk,dynamic-load-apk使用代理的方式实现Activity生命周期,插件中不能用this,不够透明; 动态加载- https://github.com/singwhatiwanna/dynamic-load-apk

动态加载Apk,热部署,利用 ClassLoader 以及 Activity 代理的方式解决- https://github.com/singwhatiwanna/dynamic-load-apk

4)melbcat开源的 Direct-Load-apk,Direct-Load-apk对dynamic-load-apk进行了改进,支持this。但也是用代理Activity,实现较为繁琐;

5)limpoxe开源的 Android-Plugin-Framework,Android-Plugin-Framework是一个相对完整的框架,但资源分区方案还不够理想,不支持加载.so插件;

6)bunnyblue开源的 ACDD

7)携程工程师开发的 DynamicAPK,ACDD 使用了osgi,没有细看。坑点是资源分区要使用修改aapt源码再从新编译的方案;DynamicAPK 坑点:修改aapt源码,不支持.so插件;
  这些框架彷佛都不支持AppCompat包(但这很重要,材料设计的Design包等都依赖AppCompat);
==Small的开发实际上是跟随一、二、3走过来的。从实际场景出发,基于“轻量、透明、极小化、跨平台”的理念:
把核心代码量控制在了一个文件(ApkBundleLauncher)500行之内
不修改aapt源码,实现了资源id PP段的再分配(原理见Dynamic load resources)
经过对aapt生成的二进制文件的后期加工,最大化分离无用的资源,使得插件包最小达到4k左右
支持对本地化网页进行插件打包,实现跨平台
  早期的基于静态代理思想的 DynamicLoadApk,随后的基于占坑思想的 DynamicApk、Small,还有360手机助手的 DroidPlugin。

8)酷狗 Android App 插件化实施过程- http://www.diycode.cc/topics/442

9)滴滴插件化框架VirtualAPK- https://github.com/didi/VirtualAPK
滴滴插件化框架VirtualAPK原理解析(一)之插件Activity管理- http://blog.csdn.net/u012124438/article/details/74118905
VirtualAPK:滴滴 Android 插件化的实践之路- http://geek.csdn.net/news/detail/130917
滴滴插件化方案 VirtualApk 源码解析- http://blog.csdn.net/lmj623565791/article/details/75000580
 -- VritualApk大致方案以下:
 1.Activity:在宿主apk中提早占几个坑,而后经过“欺上瞒下”(这个词好像是360以前的ppt中提到)的方式,启动插件apk的Activity;由于要支持不一样的launchMode以及一些特殊的属性,须要占多个坑。
 2.Service:经过代理Service的方式去分发;主进程和其余进程,VirtualAPK使用了两个代理Service。
 3.BroadcastReceiver:静态转动态。
 4.ContentProvider:经过一个代理Provider进行分发。

10) 点评的实现方式,不是用代理 Activity 的方式实现而是用 Fragment 以及 Schema 的方式实现

点评用 Fragment 以及 Schema 的方式实现:https://github.com/mmin18/AndroidDynamicLoader

11)其余插件化

Android App插件式插件开发,插件必须先安装:https://github.com/wyouflf/xCombine
Android插件式开发:https://github.com/umeng/apf
安装多 dex 的 classloader :https://github.com/casidiablo/multidex