对每个函数都在编译打包阶段自动的插入了一段代码。类似于代理,将方法执行的代码重定向到其他方法中。
Tinker
Tinker通过计算对比指定的Base Apk中的dex与修改后的Apk中的dex的区别,补丁包中的内容即为两者差分的描述。
运行时将Base Apk中的dex与补丁包进行合成,重启后加载全新的合成后的dex文件。
Qzone
双亲委托机制
某个类加载器在加载类时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务或者没有父类加载器时,才自己去加载。
1、避免重复加载,当父加载器已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
2、安全性考虑,防止核心API库被随意篡改。
在线源码阅读:www.androidos.net.cn/或http://and…
类查找流程
类加载实现热修复
类是怎么被加载的? 怎么使用补丁包中的类? 已经被加载过的类还能够替换修复吗? 怎样保证补丁包中正确class 的dex先加载?
加载修复后的类
EnjoyFix.installPatch(this, new File(“/sdcard/patch.jar”));
EnjoyFix.class
public class EnjoyFix {
private static final String TAG = “EnjoyFix”;
private static File initHack(Context context) {
File hackFile = new File(context.getExternalFilesDir(“”), “hack.dex”);
FileOutputStream fos = null;
InputStream is = null;
try {
fos = new FileOutputStream(hackFile);
is = context.getAssets().open(“hack.dex”);
int len;
byte[] buffer = new byte[2048];
while ((len = is.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return hackFile;
}
/**
-
1、获取程序的PathClassLoader对象
-
2、反射获得PathClassLoader父类BaseDexClassLoader的pathList对象
-
3、反射获取pathList的dexElements对象 (oldElement)
-
4、把补丁包变成Element数组:patchElement(反射执行makePathElements)
-
5、合并patchElement+oldElement = newElement (Array.newInstance)
-
6、反射把oldElement赋值成newElement
-
@param application
-
@param patch
*/
public static void installPatch(Application application, File patch) {
File hackDex = initHack(application);
List patchs = new ArrayList<>();
patchs.add(hackDex);
if (patch.exists()) {
patchs.add(patch);
}
//1、获取程序的PathClassLoader对象
ClassLoader classLoader = application.getClassLoader();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
try {
ClassLoaderInjector.inject(application, classLoader, patchs);
} catch (Throwable throwable) {
}
return;
}
//2、反射获得PathClassLoader父类BaseDexClassLoader的pathList对象
try {
Field pathListField = ShareReflectUtil.findField(classLoader, “pathList”);
Object pathList = pathListField.get(classLoader);
//3、反射获取pathList的dexElements对象 (oldElement)
Field dexElementsField = ShareReflectUtil.findField(pathList, “dexElements”);
Object[] oldElements = (Object[]) dexElementsField.get(pathList);
//4、把补丁包变成Element数组:patchElement(反射执行makePathElements)
Object[] patchElements = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Method makePathElements = ShareReflectUtil.findMethod(pathList, “makePathElements”,
List.class, File.class,
List.class);
ArrayList ioExceptions = new ArrayList<>();
patchElements = (Object[])
makePathElements.invoke(pathList, patchs, application.getCacheDir(), ioExceptions);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Method makePathElements = ShareReflectUtil.findMethod(pathList, “makeDexElements”,
ArrayList.class, File.class, ArrayList.class);
ArrayList ioExceptions = new ArrayList<>();
patchElements = (Object[])
makePathElements.invoke(pathList, patchs, application.getCacheDir(), ioExceptions);
}
//5、合并patchElement+oldElement = newElement (Array.newInstance)
//创建一个新数组,大小 oldElements+patchElements
// int[].class.getComponentType() ==int.class
Object[] newElements = (Object[]) Array.newInstance(oldElements.getClass().getComponentType(),
oldElements.length + patchElements.length);
System.arraycopy(patchElements, 0, newElements, 0, patchElements.length);
System.arraycopy(oldElements, 0, newElements, patchElements.length, oldElements.length);
//6、反射把oldElement赋值成newElement
dexElementsField.set(pathList, newElements);
} catch (Exception e) {
e.printStackTrace();
}
}
}
热修复流程
1.获取到当前应用的PathClassloader; 2.反射获取到DexPathList属性对象pathList; 3.反射修改pathList的dexElements
-
把补丁包patch.dex转化为Element[] (patch)
-
获得pathList的dexElements属性(old)
-
patch+dexElements合并,并反射赋值给pathList的dexElements
Element数组
补丁包其实就是一个dex或者包含dex的jar包。怎么把dex变成Element?
Android N混合编译
ART 是在 Android KitKat(Android 4.0)引入并在 Lollipop(Android 5.0)中设为默认运行环境,可以看作Dalvik2.0。ART模式在Android N(7.0)之前安装APK时会采用AOT(Ahead of time:提前编译、静态编译)预编译为机器码。而在Android N使用混合模式的运行时。应用在安装时不做编译,而是运行时解释字节码,同时在JIT编译了一 些代码后将这些代码信息记录至Profile文件,等到设备空闲的时候使用AOT(All-Of-the-Time compilation:全 时段编译)编译生成称为app_image的base.art(类对象映像)文件,这个art文件会在apk启动时自动加载(相当 于缓存)。根据类加载原理,类被加载了无法被替换,即无法修复。
解决方案
运行时替换PathClassLoader方案 App image中的class是插入到PathClassloader中的ClassTable中。假设我们完全废弃掉PathClassloader,而 采用一个新建Classloader来加载后续的所有类,即可达到将cache无用化的效果。
CLASS_ISPREVERIFIED异常
如果MainActivity类中只引用了:Utils类。当打包dex时, MainActivity与Utils都在classes.dex中,则加载 时MainActivity类被标记为 CLASS_ISPREVERIFIED。
如果使用补丁包中的Utils类取代出现bug的Utils,则会导致MainActivity与其引用的Utils不在同一个Dex, 但MainActivity已经被打上标记,此时出现冲突。导致校验失败!
解决方案
阻止标记:防止类被打上CLASS_ISPREVERIFIED标志。
AntilazyLoad类会被打包成单独的hack.dex,这样当安装apk的时候,classes.dex内的类都会引用一个在不相同dex中的 AntilazyLoad类,防止了类被打上CLASS_ISPREVERIFIED的标志了,只要没被打上这个标志的类都可以进行打补丁操作。
字节码插桩:在Java字节码Class中某些位置插入或修改一些代码。
在Java字节码中某些位置插入或修改一些代码。
Android打包流程
-
AAPT :打包资源文件,并生成R.java和resources.arsc文件。
-
AIDI : 读取.aidl文件生成对应Java文件
-
JAVAC : 将java文件编译成.class文件;
-
DX : 多个.class文件转换打包为.dex文件
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
![](https://i-blog.csdnimg.cn/blog_migrate/a927b7928d83d12b7cfc14bd04db9284.jpeg)
结语
看到这篇文章的人不知道有多少是和我一样的Android程序员。
35岁,这是我们这个行业普遍的失业高发阶段,这种情况下如果还不提升自己的技能,进阶发展,我想,很可能就是本行业的职业生涯的终点了。
我们要有危机意识,切莫等到一切都成定局时才开始追悔莫及。只要有规划的,有系统地学习,进阶提升自己并不难,给自己多充一点电,你才能走的更远。
千里之行始于足下。这是上小学时,那种一元钱一个的日记本上每一页下面都印刷有的一句话,当时只觉得这句话很短,后来渐渐长大才慢慢明白这句话的真正的含义。
有了学习的想法就赶快行动起来吧,不要被其他的事情牵绊住了前行的脚步。不要等到裁员时才开始担忧,不要等到面试前一晚才开始紧张,不要等到35岁甚至更晚才开始想起来要学习要进阶。
给大家一份系统的Android学习进阶资料,希望这份资料可以给大家提供帮助。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
阶段,这种情况下如果还不提升自己的技能,进阶发展,我想,很可能就是本行业的职业生涯的终点了。
我们要有危机意识,切莫等到一切都成定局时才开始追悔莫及。只要有规划的,有系统地学习,进阶提升自己并不难,给自己多充一点电,你才能走的更远。
千里之行始于足下。这是上小学时,那种一元钱一个的日记本上每一页下面都印刷有的一句话,当时只觉得这句话很短,后来渐渐长大才慢慢明白这句话的真正的含义。
有了学习的想法就赶快行动起来吧,不要被其他的事情牵绊住了前行的脚步。不要等到裁员时才开始担忧,不要等到面试前一晚才开始紧张,不要等到35岁甚至更晚才开始想起来要学习要进阶。
给大家一份系统的Android学习进阶资料,希望这份资料可以给大家提供帮助。
[外链图片转存中…(img-G8Ji5qxX-1712378187478)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!