Android 热补丁动态修复框架小结(1),0基础学android开发

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

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

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

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注Android)
img

正文

apply plugin: ‘com.android.application’

task(‘processWithJavassist’) << {

String classPath = file(‘build/intermediates/classes/debug’)//项目编译class所在目录

dodola.patch.PatchClass.process(classPath, project(‘:hackdex’).buildDir

.absolutePath + ‘/intermediates/classes/debug’)//第二个参数是hackdex的class所在目录

}

android {

applicationVariants.all { variant ->

variant.dex.dependsOn << processWithJavassist //在执行dx命令之前将代码打入到class中

}

}

你会发现,在执行dx之前,会先执行processWithJavassist这个任务。这个任务的作用呢,就和我们上面的代码一致了。而且源码也给出了,大家自己看下。

ok,到这呢,你就可以点击run了。ok,有兴趣的话,你可以反编译去看看dodola.hotfix.LoadBugClass这个类的构造方法中是否已经添加了改行代码。

关于反编译的用法,工具等,参考:http://blog.csdn.net/lmj623565791/article/details/23564065

ok,到此我们已经能够正常的安装apk并且运行了。但是目前还未涉及到打补丁的相关代码。

四、动态改变BaseDexClassLoader对象间接引用的dexElements


ok,这里就比较简单了,动态改变一个对象的某个引用我们反射就可以完成了。

不过这里需要注意的是,还记得我们之前说的,寻找class是遍历dexElements;然后我们的AntilazyLoad.class实际上并不包含在apk的classes.dex中,并且根据上面描述的需要,我们需要将AntilazyLoad.class这个类打成独立的hack_dex.jar,注意不是普通的jar,必须经过dx工具进行转化。

具体做法:

jar cvf hack.jar dodola/hackdex/*

dx --dex --output hack_dex.jar hack.jar

如果,你没有办法把那一个class文件搞成jar,去百度一下…

ok,现在有了hack_dex.jar,这个是干嘛的呢?

应该还记得,我们的app中部门类引用了AntilazyLoad.class,那么我们必须在应用启动的时候,降这个hack_dex.jar插入到dexElements,否则肯定会出事故的。

那么,Application的onCreate方法里面就很适合做这件事情,我们把hack_dex.jar放到assets目录。

下面看hotfix的源码:

/*

  • Copyright © 2015 Baidu, Inc. All Rights Reserved.

*/

package dodola.hotfix;

import android.app.Application;

import android.content.Context;

import java.io.File;

import dodola.hotfixlib.HotFix;

/**

  • Created by sunpengfei on 15/11/4.

*/

public class HotfixApplication extends Application

{

@Override

public void onCreate()

{

super.onCreate();

File dexPath = new File(getDir(“dex”, Context.MODE_PRIVATE), “hackdex_dex.jar”);

Utils.prepareDex(this.getApplicationContext(), dexPath, “hackdex_dex.jar”);

HotFix.patch(this, dexPath.getAbsolutePath(), “dodola.hackdex.AntilazyLoad”);

try

{

this.getClassLoader().loadClass(“dodola.hackdex.AntilazyLoad”);

} catch (ClassNotFoundException e)

{

e.printStackTrace();

}

}

}

ok,在app的私有目录创建一个文件,然后调用Utils.prepareDex将assets中的hackdex_dex.jar写入该文件。

接下来HotFix.patch就是去反射去修改dexElements了。我们深入看下源码:

/*

  • Copyright © 2015 Baidu, Inc. All Rights Reserved.

*/

package dodola.hotfix;

/**

  • Created by sunpengfei on 15/11/4.

*/

public class Utils {

private static final int BUF_SIZE = 2048;

public static boolean prepareDex(Context context, File dexInternalStoragePath, String dex_file) {

BufferedInputStream bis = null;

OutputStream dexWriter = null;

bis = new BufferedInputStream(context.getAssets().open(dex_file));

dexWriter = new BufferedOutputStream(new FileOutputStream(dexInternalStoragePath));

byte[] buf = new byte[BUF_SIZE];

int len;

while ((len = bis.read(buf, 0, BUF_SIZE)) > 0) {

dexWriter.write(buf, 0, len);

}

dexWriter.close();

bis.close();

return true;

}

ok,其实就是文件的一个读写,将assets目录的文件,写到app的私有目录中的文件。

下面主要看patch方法

/*

  • Copyright © 2015 Baidu, Inc. All Rights Reserved.

*/

package dodola.hotfixlib;

import android.annotation.TargetApi;

import android.content.Context;

import java.io.File;

import java.lang.reflect.Array;

import java.lang.reflect.Field;

import java.lang.reflect.InvocationTargetException;

import dalvik.system.DexClassLoader;

import dalvik.system.PathClassLoader;

/* compiled from: ProGuard */

public final class HotFix

{

public static void patch(Context context, String patchDexFile, String patchClassName)

{

if (patchDexFile != null && new File(patchDexFile).exists())

{

try

{

if (hasLexClassLoader())

{

injectInAliyunOs(context, patchDexFile, patchClassName);

} else if (hasDexClassLoader())

{

injectAboveEqualApiLevel14(context, patchDexFile, patchClassName);

} else

{

injectBelowApiLevel14(context, patchDexFile, patchClassName);

}

} catch (Throwable th)

{

}

}

}

}

这里很据系统中ClassLoader的类型做了下判断,原理都是反射,我们看其中一个分支hasDexClassLoader();

private static boolean hasDexClassLoader()

{

try

{

Class.forName(“dalvik.system.BaseDexClassLoader”);

return true;

} catch (ClassNotFoundException e)

{

return false;

}

}

private static void injectAboveEqualApiLevel14(Context context, String str, String str2)

throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException

{

PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();

Object a = combineArray(getDexElements(getPathList(pathClassLoader)),

getDexElements(getPathList(

new DexClassLoader(str, context.getDir(“dex”, 0).getAbsolutePath(), str, context.getClassLoader()))));

Object a2 = getPathList(pathClassLoader);

setField(a2, a2.getClass(), “dexElements”, a);

pathClassLoader.loadClass(str2);

}

首先查找类dalvik.system.BaseDexClassLoader,如果找到则进入if体。

在injectAboveEqualApiLevel14中,根据context拿到PathClassLoader,然后通过getPathList(pathClassLoader),拿到PathClassLoader中的pathList对象,在调用getDexElements通过pathList取到dexElements对象。

ok,那么我们的hack_dex.jar如何转化为dexElements对象呢?

通过源码可以看出,首先初始化了一个DexClassLoader对象,前面我们说过DexClassLoader的父类也是BaseDexClassLoader,那么我们可以通过和PathClassLoader同样的方式取得dexElements。

ok,到这里,我们取得了,系统中PathClassLoader对象的间接引用dexElements,以及我们的hack_dex.jar中的dexElements,接下来就是合并这两个数组了。

可以看到上面的代码使用的是combineArray方法。

合并完成后,将新的数组通过反射的方式设置给pathList.

接下来看一下反射的细节:

private static Object getPathList(Object obj) throws ClassNotFoundException, NoSuchFieldException,

IllegalAccessException

{

return getField(obj, Class.forName(“dalvik.system.BaseDexClassLoader”), “pathList”);

}

private static Object getDexElements(Object obj) throws NoSuchFieldException, IllegalAccessException

{

return getField(obj, obj.getClass(), “dexElements”);

}

private static Object getField(Object obj, Class cls, String str)

throws NoSuchFieldException, IllegalAccessException

{

Field declaredField = cls.getDeclaredField(str);

declaredField.setAccessible(true);

return declaredField.get(obj);

}

其实都是取成员变量的过程,应该很容易懂~~

private static Object combineArray(Object obj, Object obj2)

{

Class componentType = obj2.getClass().getComponentType();

int length = Array.getLength(obj2);

int length2 = Array.getLength(obj) + length;

Object newInstance = Array.newInstance(componentType, length2);

for (int i = 0; i < length2; i++)

{

if (i < length)

{

Array.set(newInstance, i, Array.get(obj2, i));

} else

{

Array.set(newInstance, i, Array.get(obj, i - length));

}

}

return newInstance;

}

ok,这里的两个数组合并,只需要注意一件事,将hack_dex.jar里面的dexElements放到新数组前面即可。

到此,我们就完成了在应用启动的时候,动态的将hack_dex.jar中包含的DexFile注入到ClassLoader的dexElements中。这样就不会查找不到AntilazyLoad这个类了。

ok,那么到此呢,还是没有看到我们如何打补丁,哈,其实呢,已经说过了,打补丁的过程和我们注入hack_dex.jar是一致的。

你现在运行HotFix的app项目,点击menu里面的测试:

会弹出:调用测试方法:bug class

接下来就看如何完成热修复。

五、完成热修复


ok,那么我们假设BugClass这个类有错误,需要修复:

package dodola.hotfix;

public class BugClass

{

public String bug()

{

return “fixed class”;

}

}

可以看到字符串变化了:bug class -> fixed class .

然后,编译,将这个类的class->jar->dex。步骤和上面是一致的。

jar cvf path.jar dodola/hotfix/BugClass.class

dx --dex --output path_dex.jar path.jar

拿到path_dex.jar文件。

正常情况下,这个玩意应该是下载得到的,当然我们介绍原理,你可以直接将其放置到sdcard上。

然后在Application的onCreate中进行读取,我们这里为了方便也放置到assets目录,然后在Application的onCreate中添加代码:

public class HotfixApplication extends Application

{

@Override

public void onCreate()

{

super.onCreate();

File dexPath = new File(getDir(“dex”, Context.MODE_PRIVATE), “hackdex_dex.jar”);

Utils.prepareDex(this.getApplicationContext(), dexPath, “hack_dex.jar”);

HotFix.patch(this, dexPath.getAbsolutePath(), “dodola.hackdex.AntilazyLoad”);

try

{

this.getClassLoader().loadClass(“dodola.hackdex.AntilazyLoad”);

} catch (ClassNotFoundException e)

{

e.printStackTrace();

}

dexPath = new File(getDir(“dex”, Context.MODE_PRIVATE), “path_dex.jar”);

Utils.prepareDex(this.getApplicationContext(), dexPath, “path_dex.jar”);

HotFix.patch(this, dexPath.getAbsolutePath(), “dodola.hotfix.BugClass”);

}

}

其实就是添加了后面的3行,这里需要说明一下,第一行依旧是复制到私有目录,如果你是sdcard上,那么操作基本是一致的,这里就别问:如果在sdcard或者网络上怎么处理~

ok,那么再次运行我们的app。

ok,最后说一下,说项目中有一个打补丁的按钮,在menu下,那么你也可以不在Application里面添加我们最后的3行。

你运行app后,先点击打补丁,然后点击测试也可以发现成功修复了。

如果先点击测试,再点击打补丁,再测试是不会变化的,因为类一旦加载以后,不会重新再去重新加载了。

ok,到此,我们的热修复的原理,已经解决方案,我相信已经很详细的介绍完成了,如果你有足够的耐心一定可以实现。中间制作补丁等操作,我们的操作比较麻烦,自动化的话,可以参考https://github.com/jasonross/Nuwa

最后就是对于QQ空间团队,以及开源作者的感谢了~~


欢迎关注我的微博:

写在最后

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

【算法合集】

【延伸Android必备知识点】

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

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
img

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

r机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

[外链图片转存中…(img-ahEbl9MB-1713682260389)]

【算法合集】

[外链图片转存中…(img-Zc5BGl5f-1713682260389)]

【延伸Android必备知识点】

[外链图片转存中…(img-7QquSQeQ-1713682260389)]

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

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-bTr65KRP-1713682260390)]

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

  • 27
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值