最全【Android进阶笔记】热修复(代码、资源、动态链接库(1),吊打面试官系列

总结

算法知识点繁多,企业考察的题目千变万化,面对越来越近的“金九银十”,我给大家准备好了一套比较完善的学习方法,希望能帮助大家在有限的时间里尽可能系统快速的恶补算法,通过高效的学习来提高大家面试中算法模块的通过率。

这一套学习资料既有文字档也有视频,里面不仅仅有关键知识点的整理,还有案例的算法相关部分的讲解,可以帮助大家更好更全面的进行学习,二者搭配起来学习效果会更好。

部分资料展示:




有了这套学习资料,坚持刷题一周,你就会发现自己的算法知识体系有明显的完善,离大厂Offer的距离更加近。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

/* … */

GcRootmirror::Class declaring_class_;

std::atomicstd::uint32_t access_flags_;

uint32_t dex_method_index_;

uint16_t method_index_;

uint16_t hotness_count_;

uint16_t imt_index_;

struct PtrSizedFields {

ArtMethod** dex_cache_resolved_methods_;

void* data_;

void* entry_point_from_quick_compiled_code_;

} ptr_sized_fields_;

}

2.2.2. 修复方案

  • 方法①:将待修复的 java 方法对应的 ArtMethod 结构体中的每个字段进行替换。

  • 方法②:将待修复的 java 方法对应的 ArtMethod 结构体整个进行替换。

不同的框架采用了不同方案:

  • AndFix 采用方法①,不同版本和不同厂商 ArtMethod 可能不同,存在兼容问题,导致方法替换失败。

  • Sophix 采用方法②,不存在兼容问题 。

无论采用哪种方案,由于类加载后,类的结构和方法数量就已经固定了,因此该方案有以下不适场景:

  • 增加或减少方法和字段的个数。

  • 改变原有类的结构。

Sophix 结合了底层替换方案和类加载方案各自的优点,以底层替换方案为主,类加载方案为辅,在热部署无法使用的情况下,自动降级为冷部署。

2.3. Instant Run 方案


在 Android Studio 2.0 版本上,支持了一个新特性 Instant Run,实现了对代码修改的实时生效(热插拔)。

采用 Instant Run 方案的主要是 Robust 和 Aceso。

2.3.1. Instant Run 原理

在第一次构建 Apk 时:

  • 在每一个类中注入了一个 $change 的成员变量,它实现了 IncrementalChange 接口。

  • 在每一个方法的第一行,插入了一段判断执行逻辑。

public class TestActivity {

// 注入一个类型为IncrementalChange的成员

IncrementalChange localIncrementalChange = $change;

public void onCreate(Bundle savedInstanceState){

// 当localIncrementalChange不为null时,可能会执行到access$dispatch从而替换掉之前老的逻辑

if (localIncrementalChange != null) {

localIncrementalChange.access$dispatch(

“onCreate.(Landroid/os/Bundle;)V”, new Object[] { this, paramBundle });

return;

}

super.onCreate(savedInstanceState);

}

}

当我们点击 Android Studio 的 InstantRun 按钮时:

  • 如果方法没有变化,则 $changenull,执行方法中的旧逻辑。

  • 如果方法有变化,则:

  • 动态生成替换类 TestActivity$overrideAppPatchesLoaderImpl 类。

  • AppPatchesLoaderImpl 类的 getPatchedClasses 方法会返回被修改的类的列表,根据这个列表,TestActivity 中的 $change 会被赋值为 TestActivity$override

  • 判断条件成立,access$dispatch() 方法会执行 TestActivity$override 类中的 onCreate 方法,从而实现对现有 onCreate 方法的修改。

2.3.2. 修复方案

Robust 为例

  • 在编译打包阶段对每个方法都自动的插入了一段代码。

  • 动态下发包含有 PatchesInfoImpl.javaPatch.javapatch.dex 到客户端,用 DexClassLoader 加载 patch.dex,反射拿到 PatchesInfoImpl.java 这个 class 并创建对象。

  • 然后通过这个对象的 getPatchedClassesInfo 方法,获得需要修复的 class 的混淆后名字,再反射得到当前运行环境中的该 class。

  • 其中的 changeQuickRedirect 字段赋值为用 patch.dex 中的 Patch.java 这个 class new 出来的对象。


3. 资源修复

==========================================================================

很多热修复框架的资源修复都参考了 Instant Run 的资源修复原理。由于 Instant Run 不是 Android 的源码,需要反编译才能知道。

Instant Run 资源修复的核心逻辑在 MonkeyPatcher 类的 monkeyPatchExistingResources 方法中。

public class MonkeyPatcher {

public static void monkeyPatchExistingResources(

Context context, String externalResourceFile, Collection activities) {

if (externalResourceFile == null) {

return;

}

try {

// 反射创建新的AssetManager

AssetManager newAssetManager = AssetManager.class.getConstructor(

new Class[0]).newInstance(new Object[0]);

Method mAddAssetPath = AssetManager.class.getDeclaredMethod(

“addAssetPath”, new Class[]{String.class});

mAddAssetPath.setAccessible(true);

// 反射调用addAssetPath方法加载外部资源

if (((Integer) mAddAssetPath.invoke(

newAssetManager, new Object[]{externalResourceFile})).intValue() == 0) {

throw new IllegalStateException(“Could not create new AssetManager”);

}

Method mEnsureStringBlocks = AssetManager.class.getDeclaredMethod(

“ensureStringBlocks”, new Class[0]);

mEnsureStringBlocks.setAccessible(true);

mEnsureStringBlocks.invoke(newAssetManager, new Object[0]);

if (activities != null) {

for (Activity activity : activities) {

Resources resources = activity.getResources();

try {

// 把Resources中的mAssets替换为newAssetManager

Field mAssets = Resources.class.getDeclaredField(“mAssets”);

mAssets.setAccessible(true);

mAssets.set(resources, newAssetManager);

} catch (Throwable ignore) {

/* … */

}

// 获取Activity的主题

Resources.Theme theme = activity.getTheme();

try {

try {

// 把Resources.Theme中的mAssets替换为newAssetManager

Field ma = Resources.Theme.class.getDeclaredField(“mAssets”);

ma.setAccessible(true);

ma.set(theme, newAssetManager);

} catch (NoSuchFieldException ignore) {

/* … */

}

/* … */

} catch (Throwable e) {

/* … */

}

}

Collection<WeakReference> references = null;

/* …根据不同SDK版本,用不同方式得到Resources的弱引用集合 */

for (WeakReference wr : references) {

Resources resources = wr.get();

if (resources != null) {

try {

// 把每个Resources中的mAssets替换为newAssetManager

Field mAssets = Resources.class.getDeclaredField(“mAssets”);

mAssets.setAccessible(true);

mAssets.set(resources, newAssetManager);

} catch (Throwable ignore) {

/* … */

}

resources.updateConfiguration(

resources.getConfiguration(), resources.getDisplayMetrics());

}

}

}

} catch (Throwable e) {

throw new IllegalStateException(e);

}

}

}

资源热修复总结为两个步骤:

  • 反射创建新的 AssetManager 对象,反射调用 addAssetPath 方法加载外部的资源。

  • 将 AssetManager 类型的 mAssets 字段的引用全部替换为新创建的 AssetManager 对象。


4. 动态链接库修复

=============================================================================

Android 的动态链接库主要是 so 库。

4.1. so 的加载


加载 so 主要用到了 System 类的 loadloadLibarary 方法。

public final class System {

// 传入so的名字,会直接从系统的目录去加载so文件,

// 系统的路径包括/data/data/${package_name}/lib、/system/lib、/vender/lib等

public static void load(String filename) {

Runtime.getRuntime().load0(Reflection.getCallerClass(), filename);

}

// 传入so的绝对路径,直接从这个路径加载自定义外部so文件

public static void loadLibrary(String libname) {

Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);

}

}

实际上这两个方法最后都调用 nativeLoad 这个 native 方法去加载 so 库,参数 fileName 为 so 库在磁盘中的完整路径名。

而 nativeLoad 会调用 LoadNativeLibrary 函数来实现 so 的加载:

  • 判断 so 是否被加载过,两次 ClassLoader 是否是同一个,避免 so 重复加载。

  • 打开 so 并得到 so 句柄,如果 so 句柄获取失败,就返回false。创建新的 SharedLibrary,如果传入 path 对应的 library 为空指针,就将新创建的 SharedLibrary 赋值给 library,并将 library 存储到 libraries_ 中。

  • 查找 JNI_OnLoad 的函数指针,根据不同情况设置 was_successful 的值,最终返回该 was_ successful。

4.2. 注册 Native 方法


4.2.1. 静态注册Native方法

通过 javah -jni 命令生成的包含 JNI 的头文件,接口的命名方式一般是 Java_<PackageName>_<ClassName>_<MethodName>,程序执行时系统会根据这种命名规则来调用对应的 Native 方法。

  • 注册方式方便简单。

  • JNI函数名过长,可读性差,不能灵活改变。

4.2.2. 动态注册

在加载函数库(.a或.so)的时候进行注册,即在 JNI_OnLoad 方法里进行注册。

重要知识点

下面是有几位Android行业大佬对应上方技术点整理的一些进阶资料。

高级进阶篇——高级UI,自定义View(部分展示)

UI这块知识是现今使用者最多的。当年火爆一时的Android入门培训,学会这小块知识就能随便找到不错的工作了。不过很显然现在远远不够了,拒绝无休止的CV,亲自去项目实战,读源码,研究原理吧!

  • 面试题部分合集

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

1196)]

高级进阶篇——高级UI,自定义View(部分展示)

UI这块知识是现今使用者最多的。当年火爆一时的Android入门培训,学会这小块知识就能随便找到不错的工作了。不过很显然现在远远不够了,拒绝无休止的CV,亲自去项目实战,读源码,研究原理吧!

[外链图片转存中…(img-QS0wccTU-1715413841196)]

  • 面试题部分合集
    [外链图片转存中…(img-TXHx95fy-1715413841197)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值