Android之热修复实战,【2024Android最新学习路线

文章详细描述了如何在Android应用中通过插桩技术实现实时热修复,涉及获取PathClassLoader、修改DexPathList的dexElements,以及处理CLASS_ISPREVERIFIED异常。同时提到了Android开发的学习资源和面试准备路径。
摘要由CSDN通过智能技术生成

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();

}

}

}

3.插桩式热修复运行期修复落地


热修复流程

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中某些位置插入或修改一些代码。

4.字节码插桩


在Java字节码中某些位置插入或修改一些代码。

Android打包流程

  • AAPT :打包资源文件,并生成R.java和resources.arsc文件。

  • AIDI : 读取.aidl文件生成对应Java文件

  • JAVAC : 将java文件编译成.class文件;

  • DX : 多个.class文件转换打包为.dex文件

ASM

操作Java 字节码的框架,按照Class文件的格式,解析、修改、生成Class,可以动态生成类或者增强现有类的 功能。 正如GSON操作json的框架

5.Gradle插件开发


插件开发

apply plugiin: ‘com.android.application’

apply plugiin: ‘com.android.library’

| 方式 | 说明 |

| — | — |

| Build script脚 本 | 把插件写在 build.gradle 文件中,一般用于简单的逻辑,只在该 build.gradle 文件可见。 |

| buildSrc目录 | 将插件源代码放在 buildSrc/src/main/groovy/ 中,只对该项目中可见。 |

| 独立项目 | 独立项目 一个独立的 Java 项目/模块,可以将文件包发布到仓库(Jcenter), 使其他项目方便引入。 |

插件实现

正如我们开发Android项目,创建Activity需要继承android.app.Activity或其子类,插件需 要实现org.gradle.api.Plugin<Project>接口。

右值为实现了插件结构的类全限定名

插件扩展

引入某些插件可能会需要进行插件配置,如:

这些信息由使用者配置,目的是为了能够在插件执行时获得用户配置信息,进行不同的逻辑处理。

project.getExtensions().create(“xx”, JavaBean.class);

依赖插件的插件

一般情况下,我们自定义插件主要为了Android项目使用,在插件执行过程中,希望动态获得 android插件中的信息,如:APK输出目录、包名、变体(debug/release)等信息。 我们的插件需要用过Android插件编写的API获取这些参数,因此我们的插件需要依赖Android 插件。 以独立模块开发插件为例,独立模块的build.gradle中加入:

Task

Task,即任务。Android工程引入的Android插件中,创建了多个自定义任务,如:编译Java文件、生成 dex文件、打包等等,每个任务执行一项工作。

6.自动生成补丁包


自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后,面试前该准备哪些资源复习?

其实客户端开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

这里再分享一下我面试期间的复习路线:(以下体系的复习资料是我从各路大佬收集整理好的)

《Android开发七大模块核心知识笔记》

面试字节两轮后被完虐,字节面试官给你的技术面试指南,请查收

面试字节两轮后被完虐,字节面试官给你的技术面试指南,请查收

《960全网最全Android开发笔记》

面试字节两轮后被完虐,字节面试官给你的技术面试指南,请查收

《379页Android开发面试宝典》

历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

48936)]

《960全网最全Android开发笔记》

[外链图片转存中…(img-U3W4dpV3-1712378248936)]

《379页Android开发面试宝典》

历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值