一文带你手动实现最简单的Android热修复

Toast.makeText(this, “修复失败” + e.getMessage(), Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case REQUEST_CODE: {
if (grantResults.length > 0) {
// permission was granted
} else {
// permission denied
}
return;
}
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}

然后就是我们热修复的工具类,怎么使用,在MainActivity中已经有使用的代码了,工具类中用到了反射的知识,但是不是本文的重点,有需要的小伙伴自行学习相关知识,这个工具类中,最重要的两个类就是DexClassLoader和PathClassLoader,看名字就知道这是两个类加载器,用来加载类的,想知道具体实现的,下面就是源码

public final class HotFix {
/**

  • 修复指定的类
  • @param context 上下文对象
  • @param patchDexFile dex文件
  • @param patchClassName 被修复类名
    /
    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) {
    }
    }
    }
    private static boolean hasLexClassLoader() {
    try {
    Class.forName(“dalvik.system.LexClassLoader”);
    return true;
    } catch (ClassNotFoundException e) {
    return false;
    }
    }
    private static boolean hasDexClassLoader() {
    try {
    Class.forName(“dalvik.system.BaseDexClassLoader”);
    return true;
    } catch (ClassNotFoundException e) {
    return false;
    }
    }
    private static void injectInAliyunOs(Context context, String patchDexFile, String patchClassName)
    throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException,
    InstantiationException, NoSuchFieldException {
    PathClassLoader obj = (PathClassLoader) context.getClassLoader();
    String replaceAll = new File(patchDexFile).getName().replaceAll(“\.[a-zA-Z0-9]+”, “.lex”);
    Class cls = Class.forName(“dalvik.system.LexClassLoader”);
    Object newInstance =
    cls.getConstructor(new Class[]{String.class, String.class, String.class, ClassLoader.class}).newInstance(
    new Object[]{context.getDir(“dex”, 0).getAbsolutePath() + File.separator + replaceAll,
    context.getDir(“dex”, 0).getAbsolutePath(), patchDexFile, obj});
    cls.getMethod(“loadClass”, new Class[]{String.class}).invoke(newInstance, new Object[]{patchClassName});
    setField(obj, PathClassLoader.class, “mPaths”,
    appendArray(getField(obj, PathClassLoader.class, “mPaths”), getField(newInstance, cls, “mRawDexPath”)));
    setField(obj, PathClassLoader.class, “mFiles”,
    combineArray(getField(obj, PathClassLoader.class, “mFiles”), getField(newInstance, cls, “mFiles”)));
    setField(obj, PathClassLoader.class, “mZips”,
    combineArray(getField(obj, PathClassLoader.class, “mZips”), getField(newInstance, cls, “mZips”)));
    setField(obj, PathClassLoader.class, “mLexs”,
    combineArray(getField(obj, PathClassLoader.class, “mLexs”), getField(newInstance, cls, “mDexs”)));
    }
    @TargetApi(14)
    private static void injectBelowApiLevel14(Context context, String str, String str2)
    throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
    PathClassLoader obj = (PathClassLoader) context.getClassLoader();
    DexClassLoader dexClassLoader =
    new DexClassLoader(str, context.getDir(“dex”, 0).getAbsolutePath(), str, context.getClassLoader());
    dexClassLoader.loadClass(str2);
    setField(obj, PathClassLoader.class, “mPaths”,
    appendArray(getField(obj, PathClassLoader.class, “mPaths”), getField(dexClassLoader, DexClassLoader.class,
    “mRawDexPath”)
    ));
    setField(obj, PathClassLoader.class, “mFiles”,
    combineArray(getField(obj, PathClassLoader.class, “mFiles”), getField(dexClassLoader, DexClassLoader.class,
    “mFiles”)
    ));
    setField(obj, PathClassLoader.class, “mZips”,
    combineArray(getField(obj, PathClassLoader.class, “mZips”), getField(dexClassLoader, DexClassLoader.class,
    “mZips”)));
    setField(obj, PathClassLoader.class, “mDexs”,
    combineArray(getField(obj, PathClassLoader.class, “mDexs”), getField(dexClassLoader, DexClassLoader.class,
    “mDexs”)));
    obj.loadClass(str2);
    }
    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);
    //新的dexElements对象重新设置回去
    setField(a2, a2.getClass(), “dexElements”, a);
    pathClassLoader.loadClass(str2);
    }
    /
    *
  • 通过反射先获取到pathList对象
  • @param obj
  • @return
  • @throws ClassNotFoundException
  • @throws NoSuchFieldException
  • @throws IllegalAccessException
    /
    private static Object getPathList(Object obj) throws ClassNotFoundException, NoSuchFieldException,
    IllegalAccessException {
    return getField(obj, Class.forName(“dalvik.system.BaseDexClassLoader”), “pathList”);
    }
    /
    *
  • 从上面获取到的PathList对象中,进一步反射获得dexElements对象
  • @param obj
  • @return
  • @throws NoSuchFieldException
  • @throws IllegalAccessException
    */
    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 void setField(Object obj, Class cls, String str, Object obj2)
    throws NoSuchFieldException, IllegalAccessException {
    Field declaredField = cls.getDeclaredField(str);
    declaredField.setAccessible(true);//设置为可访问
    declaredField.set(obj, obj2);
    }
    //合拼dexElements
    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;
    }
    private static Object appendArray(Object obj, Object obj2) {
    Class componentType = obj.getClass().getComponentType();
    int length = Array.getLength(obj);
    Object newInstance = Array.newInstance(componentType, length + 1);
    Array.set(newInstance, 0, obj2);
    for (int i = 1; i < length + 1; i++) {
    Array.set(newInstance, i, Array.get(obj, i - 1));
    }
    return newInstance;
    }
    }

布局文件就两个按钮,就不贴了,占空间,好了,代码准备完毕,接着下一步吧。
接下来,我们生成项目对应的dex文件,网上资料有点少,,而且有的还是错的,各种莫名其妙的操作,哎说多了都是泪,但是也还是有正确的,我这里采用了一种相对简单的方式,首先在app的module下的build.gradle文件中加入代码,不要加入到某个节点下。最终代码如下

apply plugin: ‘com.android.application’
android {
compileSdkVersion 28
defaultConfig {
applicationId “com.aiiage.testhotfix”
minSdkVersion 26
targetSdkVersion 28
versionCode 1
versionName “1.0”
testInstrumentationRunner “android.support.test.runner.AndroidJUnitRunner”
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’
}
}
}
dependencies {
implementation fileTree(dir: ‘libs’, include: [‘*.jar’])
implementation ‘com.android.support:appcompat-v7:28.0.0-alpha3’
implementation ‘com.android.support.constraint:constraint-layout:1.1.2’
testImplementation ‘junit:junit:4.12’
androidTestImplementation ‘com.android.support.test🏃1.0.2’
androidTestImplementation ‘com.android.support.test.espresso:espresso-core:3.0.2’
}
//加入的代码从这里开始
task clearJar(type: Delete) {
delete(‘libs/log.jar’)
}
task makeJar(type: org.gradle.api.tasks.bundling.Jar) {
//指定生成的jar名称
baseName ‘log’
//从哪里打包class文件
from(‘build/intermediates/classes/debug/com/aiiage/testhotfix/’)
//打包到jar后的目录结构
into(‘com/aiiage/testhotfix/’)
//去掉不需要打包的目录和文件
exclude(‘text/’, ‘BuildConfig.class’, ‘R.class’, ‘BuildConfig.class’)
exclude {
it.name.startsWith('R/pre>);
}
}
makeJar.dependsOn(clearJar, build)

加入的代码代表什么意思注释已经很清楚了,这个过程最终会生成一个jar包,然后打开AndroidStudio底下的命令行,如图

在命令行中,我们输入gradlew makeJar 注意,不要输错了,等待约2分钟左右,看到如下的字样,代表生成jar成功

生成的jar包存放的地方在配置文件中配置了,比如我这里就在这个目录下,如图

我这里的,名字叫log,所以最终得到的是一个名为log.jar的文件,现在我们用这个jar来得到dex文件,需要用到的工具是dx,这个工具在哪里呢,就是SDK目录下的build-tools,然后随便选择一个版本进去就可以看到名为dx.bat的文件,这个就是我们需要使用的。

我们将log.jar文件复制到这个目录下,按住shift右击鼠标在该目录下打开命令行,输入命令

dx --dex --output=D:/test log.jar

其中D:/test为保存生产的dex文件的目录,同时注意空格。回车若没有错误说明生产成功,我们来到指定的D:/test目录,发现我们的最终目标正静静的躺在里面等着我们,嘿嘿!

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

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

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

img

img

img

img

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

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

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

最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上我搜集整理的2019-2021BATJ 面试真题解析,我把大厂面试中常被问到的技术点整理成了PDF,包知识脉络 + 诸多细节。

节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

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

《379页Android开发面试宝典》

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

如何使用它?

1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数

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

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

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

腾讯、字节跳动、阿里、百度等BAT大厂 2019-2021面试真题解析

资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图

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

限于阅读各大系统源码,还包括各种优秀的开源库。

[外链图片转存中…(img-RvxXKIIO-1713444990688)]

腾讯、字节跳动、阿里、百度等BAT大厂 2019-2021面试真题解析

[外链图片转存中…(img-qTjnYqKp-1713444990689)]

资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值