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学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!