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

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

-- 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都能生效。

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

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

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

-- 热更新
 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 和  DI

  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中的资源。加载会有两种方式:
1、使用反射机制修改类加载器
2、使用代理的方式

  
-- 类加载器是符合双亲委派机制的。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的开发其实是跟随1、2、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

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值