一文带你手动实现最简单的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目录,发现我们的最终目标正静静的躺在里面等着我们,嘿嘿!

最后我想说

为什么很多程序员做不了架构师?
1、良好健康的职业规划很重要,但大多数人都忽略了
2、学习的习惯很重要,持之以恒才是正解。
3、编程思维没能提升一个台阶,局限在了编码,业务,没考虑过选型、扩展
4、身边没有好的架构师引导、培养。所处的圈子对程序员的成长影响巨大。

金九银十面试季,跳槽季,整理面试题已经成了我多年的习惯!在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
oid复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中…(img-4lXTHPLp-1714983909172)]

里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值