在 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 按钮时:
-
如果方法没有变化,则
$change
为null
,执行方法中的旧逻辑。 -
如果方法有变化,则:
-
动态生成替换类
TestActivity$override
和AppPatchesLoaderImpl
类。 -
AppPatchesLoaderImpl
类的getPatchedClasses
方法会返回被修改的类的列表,根据这个列表,TestActivity
中的$change
会被赋值为TestActivity$override
。 -
判断条件成立,
access$dispatch()
方法会执行TestActivity$override
类中的onCreate
方法,从而实现对现有onCreate
方法的修改。
2.3.2. 修复方案
以 Robust 为例
-
在编译打包阶段对每个方法都自动的插入了一段代码。
-
动态下发包含有
PatchesInfoImpl.java
和Patch.java
的patch.dex
到客户端,用DexClassLoader
加载patch.dex
,反射拿到PatchesInfoImpl.java
这个 class 并创建对象。 -
然后通过这个对象的
getPatchedClassesInfo
方法,获得需要修复的 class 的混淆后名字,再反射得到当前运行环境中的该 class。 -
其中的
changeQuickRedirect
字段赋值为用patch.dex
中的Patch.java
这个 classnew
出来的对象。
======================================================================
很多热修复框架的资源修复都参考了 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 对象。
=========================================================================
Android 的动态链接库主要是 so 库。
加载 so 主要用到了 System
类的 load
和 loadLibarary
方法。
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.1. 静态注册Native方法
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
最后
我坚信,坚持学习,每天进步一点,滴水穿石,我们离成功都很近!
以下是总结出来的字节经典面试题目,包含:计算机网络,Kotlin,数据结构与算法,Framework源码,微信小程序,NDK音视频开发,计算机网络等。
字节高级Android经典面试题和答案
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
最后
我坚信,坚持学习,每天进步一点,滴水穿石,我们离成功都很近!
以下是总结出来的字节经典面试题目,包含:计算机网络,Kotlin,数据结构与算法,Framework源码,微信小程序,NDK音视频开发,计算机网络等。
字节高级Android经典面试题和答案
[外链图片转存中…(img-p2W6lCNi-1712707456579)]
[外链图片转存中…(img-4S6iDMLl-1712707456580)]
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-V8vvPpyJ-1712707456580)]