专栏首页Android 进阶Activity 基础知识

Activity 基础知识

摘抄自<<Android 进阶解密>>一书

热修复

热修复框架核心技术主要有三类:分别是代码修复、资源修复与动态链接库修复

很多热修复框架都参考了Instant Run资源修复原理。

Instant Run部署有三种方式,它会根据代码的情况来决定采用哪种部署方式:

  1. 热启动:效率最高,代码增量改变不要重启App,甚至不需要重启当前的activity。修改一个现有方式中的代码采用hot swap
  2. 温启动:App不需要重启,但是activity需要重启。修改或者删除一个现有资源文时会采用warm swap
  3. 冷启动:App需要重启,但是不需要重新安装。采用cold swap的情况很多,比如:添加或者删除修改一个字段与方法,添加一个类等

Instant Run的资源修复

  1. 创建AssetManager,通过反射调用addAssetPath方法加载外部资源,这样新创建的AssetManager就含有外部资源
  2. AssetManager类型的mAssets字段的引用全部替换为新创建的AssetManager

代码修复

主要有三个方案:分别是底层方法替换、类加载方案与Instant Run方案

  1. 类加载方案,基于Dex分包方案
  • 65536方法数限制主要原因是DVMByteCode限制,DVM指令集的方法调用指令invoke-kind索引为16bits,最多能引用65536个方法**
  • LinerAlloc限制:在安装应用时,可能会提示INSTALL_FAILED_DEXOPT,产生原因就是LinerAlloc限制,DVM中的LinerAlloc是一个固定的缓存区,当方法数超出了缓存区大小时会报错。 为了解决65536与LinerAlloc限制,产生dex分包方案。该方案主要做是在打包时将应用代码分成多个Dex,将应用启动时必须用到类与这些类的直接引用类放到主Dex中,其他代码放到次Dex中。当应用启动时先加载主Dex,等应用启动完在动态加载次Dex,从而缓解主dex的65536与LinerAlloc限制。Dex方案主要有两种,分别是Google官方方案、Dex自动拆包与动态加载方案。

类加载方案需要重启App后让ClassLoader重新加载新的类,为什么需要重启,因为类是无法卸载的,要想重新加载类就需要重启App,因此采用类加载方案的热修复框架无法及时生效。

虽然很多热修复框架采用了类加载方案,但是具体实现还是有区别的:

  • 比如QQ空间的超级补丁与Nuwa是按照将补丁包放在Element数组的第一个元素优先加载。
  • 微信的Tinker将旧APK做了diff,得到path.dex,再将path.dex与手机中的apkclasses.dex做合并,生成新的classes.dex,然后在运行时通过反射将classes.dex放在Element数组第一个元素。
  • 饿了么的Amigo则是将补丁包中的每个dex对应的Element取出来,之后组成新的Element数组,在运行时候通过反射用新的Element数组替换现有的Element数组。
  • 采用类加载方案的主要以腾讯系为主。

底层替换方案

与类加载方案不同,底层替换方案不会再次加载新类,而是直接在Native层修改原有类,由于在原有类进行修改限制会比较多,且不能增减原有类的方法和字段,如果我们增加了方法数,那么方法索引也会增加,这样访问方法时会无法通过索引找到正确的方法,同样的字段也是,方法反射我们可以调用java.lang.Class.getDeclaredMethod

在ART虚拟机中对应一个ArtMethod指针,ArtMethod结构体中包含了Java方法所有信息,包括执行入口、访问权限、所属类与代码执行地址等

替换ArtMethod结构体中的字段或者替换正给ArtMethod结构体,这就是底层替换方案。AndFix采用替换ArtMethod结构体中的字段,这样会有兼容问题,因为厂商可能会修改ArtMethod结构体,导致方法替换失败,Sophix采用替换整个ArtMethod结构体,这样就不存在兼容问题。底层替换直接替换了方法,可以立即生效不需要重启。采用底层替换方案主要以阿里系为主。

Instant Run方案

在第一次构建APK时,使用ASM在每一个方法中注入类似如下的代码:

IncrementalChange loaclIncrementalChange = $change;
if(loaclIncrementalChange  !=null) {
loaclIncrementalChange .access$dispatch(&quot;onCreate.(Landroid/os/Bundle;)V&quot;,new Object[]{this,paramBundle});
return;
}

当我们点击Instant Run,如果方法没有变换,则$change为null,就调用return不做处理,如果方法有变化,就生成替换类。假设MainactivityonCreate方法修改,就会生成 Mainactivity$overrid,实现了IncrementalChange 接口,同时生成一个AppPatchedLoaderImpl类,这个类的getPatchClasses方法会返回被修改类的列表,根据列表会将Mainactivity$change设置为 Mainactivity$overrid,方法变化会执行Mainactivity$overridaccess$dispatch方法,在该方法中根据参数"onCreate.(Landroid/os/Bundle;)V执行Mainactivity$overridoncreate方法,从而实现方法修改。 借鉴该方案的热修复框架有美团的Robust与美丽说蘑菇街的Aceso。

So 方案

主要指so库。热修复框架主要是更新so,基础原理就是加载so.

加载so主要用到System类的loadloadLibrary方法

System类的load方法传入参数是so在磁盘的完整路径,用于加载指定路径的so。System类的loadLibrary方法传入so的名称,用于加载App安装后自动从apk包中复制到/data/data/packagename/lib下的so.

so修复一种方案,就是将so补丁插入到NativeLibraryElement数组的前部,让so补丁的路径先返回,并调用RuntimedoLoad方法中会调用nativenativeload

Runtime nativeload函数中调用JVM_NativeLoad函数。JavaVMExt类型指针代表一个虚拟机实例,紧接着调用JavaVMExtLoadNativeLibrary函数加载so.

LoadNativeLibrary函数总结:

  1. 判断so是否加载过,两次ClassLoader是否是同一个,避免so重复加载
  2. 打开so得到so句柄,如果so句柄获取失败,就返回false。创建新的SharedLibrary,如果传入path对应的library为空指针,就将新创建SharedLibrary赋值给library,并将library存储到libraries
  3. 查找JNI_OnLoad函数指针,根据不同情况设置was_successful值,最终返回was_successful

so修复主要有两种方案:

  1. 将so补丁插入到NativeLibraryElement数组的前部,让so补丁的路径先返回和加载;
  2. 调用Systemload方法来接管so的加载入口;

动态链接修复基础

什么是so文件

so文件object的缩写,见名思义就是共享的对象,机器可以直接运行的二进制代码。大到操作系统,小到一个专用软件,都离不开so。 so主要存在于Unix和Linux系统中。so库的名称和文件名so库的名称可任意,如daking。so库的文件名必须以lib开头。如libdaking.so,其中lib是必要前缀,daking才是这个库的名称。Android中的soso是与平台相关的二进制机器码,与ABI(Application Binary Interface)相对应,一个ABI表示相应的CPU的指令集与内存页管理,也对应于不同的C运行环境,所以so是有不同的系统版本的。 随着Android系统的快速发展,搭载Android的硬件平台也早已多样化了(对比WinTel联盟,直到2012年才新发展了Windows RT来适配ARM平台,2015年的Win10才进入 Raspberry Pi 2这类基于ARM的新型设备中), 现在已经运行在7个ABI:armeabi,armeabi-v7a (armeabi-v7a-hard),arm64-v8a,x86,x86_64,mips 和 mips64。为什么使用上面主要从软件开发的角度说明了为什么设计so以及开发者为什么使用so,由于Android基于Linux Kernl的,也继承了Linux中所有so相关的设计。 除了系统方面的原因,Android开发者还要知道以下几点:so机制让开发者最大化利用已有的C和C++代码,达到重用的效果,利用软件世界积累了几十年的优秀代码so是二进制,没有解释编译的开消,用so实现的功能比纯java实现的功能要快so内存分配不受Dalivik/ART的单个应用限制,减少OOM

ABI是什么

应用程序定义的二进制文件尤其指so文件,如何运行在相应的系统平台,从使用的指令集,内存对齐到可用的系统函数库中,在Android 系统上,每一个CPU架构对应一个ABI:armeabi、armeabi-v7a、arm64-v8a、x86、x86_64、mips、mips64

不同的 Android 手机使用不同的 CPU,而不同的 CPU 支持不同的指令集。CPU 与指令集的每种组合都有专属的应用二进制接口,即 ABI。ABI 可以非常精确地定义应用的机器代码在运行时如何与系统交互。您必须为应用要使用的每个 CPU 架构指定 ABI。典型的 ABI 包含以下信息:机器代码应使用的 CPU 指令集。运行时内存存储和加载的字节顺序。可执行二进制文件(例如程序和共享库)的格式,以及它们支持的内容类型。在代码与系统之间传递数据的各种规范。这些规范包括对齐限制,以及系统调用函数时如何使用堆栈和寄存器。运行时可用于机器代码的函数符号列表 - 通常来自非常具体的库集。

什么是ABI:

  • ABI是Application Binary Interface的缩写。
  • ABI常表示两个程序模块之间的接口,且其中一个模块常为机器码级别的library或操作系统。
  • ABI定义了函数库的调用、应用的二进制文件(尤其是.so)如何运行在相应的系统平台上等细节。
  • Android目前支持以下七种ABI:armeabi、armeabi-v7a、arm64-v8a、x86、x86_64、mips、mips64。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java 锁分类

    乐观锁是一种乐观思想,认为读多写少,遇到并发的可能性低,每次拿数据时候并不会上锁,因为认为不会被别人修改。但是更新的时候会判断有没有人会更新这条数据,采取写的时...

    Yif
  • 线程优化

    Process中定义,值越小,优先级越高,默认是THREAD_PRIORITY_DEFAULT 0

    Yif
  • Android 开发艺术探索笔记二

    不管是Activity,Dialog还是Toast,它们视图都是附加在window上的,window才是view的直接管理者。

    Yif
  • Android 动态库压缩壳的实现

    计算机软件领域所说的壳实际上是一种软件加密技术。壳主要分为两大类:加密壳和压缩壳,加密壳侧重于防止软件被篡改,而压缩壳则侧重于减小软件体积。其实,在Window...

    小时光
  • SDK热更系列之如何获取应用在当前设备上的so对应的指令集

    子勰
  • Linux下so动态库一些不为人知的秘密

    Linux 下有动态库和静态库,动态库以.so为扩展名,静态库以.a为扩展名。二者都使用广泛。本文主要讲动态库方面知识。

    刘盼
  • 一种Android App在Native层动态加载so库的方案

    这篇文章通过实战案例,介绍了一种有条理的组织Native层代码层级结构的方法。并且,在良好的代码层级、作用分工的基础上,实现了动态的按需加载、卸载so库。文章...

    QQ音乐技术团队
  • 文件丢失?损坏?兼容性问题?到底是什么导致了错误

    在日常的维护中,免不了和文件打交道,文件涉及的问题有很多类,这里讨论: 文件丢失,损坏,兼容性问题。 而对于文件丢失导致的问题一般比较容易定位,而文件损坏,特...

    干点啥吧
  • Linker加载so失败问题分析

    原文链接:https://wetest.qq.com/lab/view/421.html

    WeTest质量开放平台团队
  • 爱加密so保护简单脱壳测试

    1.   最近研究so文件的保护,在网上搜索发现爱加密支持对so文件的保护,然后联系客户,本来是想让客户保护一个自己的so文件来做测试的,结果客户各种不愿意,说...

    我是小三

扫码关注云+社区

领取腾讯云代金券