前言
https://ke.qq.com/webcourse/index.html#course_id=130901&term_id=100146035&taid=1287279008153429&vid=r1417pykrgc
学习了下这个视频的热修复方法。成功实现,但是还是遇到了一些问题。这里记录下,帮助大家学习。
代码分析
首先先简单的代码分析一下。
//问题代码
class Calculator {
fun calc(): Int {
return 100 / 0
}
}
//修复后代码
class Calculator {
@Replace(clazz = "**.Calculator", method = "calc")
fun calc(): Int {
return 100
}
}
原理:替换Art虚拟机中有问题的类的方法,为修复后的方法。(Android 5.0以上)
视频中已有详细介绍,这里就不多赘述了。
fun loadDex(context: Context, dexName: String) {
val cacheFile: File
if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
cacheFile = context.externalCacheDir
} else {
cacheFile = context.cacheDir
}
val file = File(cacheFile, "dxtest")
file.deleteOnExit()
val dexFile = DexFile(File("/storage/emulated/0/", dexName).absolutePath)
val iterator = dexFile.entries()
while (iterator.hasMoreElements()) {
val clazzName = iterator.nextElement()
val clazz = Class.forName(clazzName)
//遍历类方法根据注解替换
clazz.declaredMethods.forEach { method ->
val replacObj = method.getAnnotation(Replace::class.java)
val replaceClazz = Class.forName(replacObj.clazz)
val replaceMethod = replaceClazz.getDeclaredMethod(replacObj.method, *method.parameterTypes)
fixMethod(replaceMethod, method)
}
}
}
....
private external fun fixMethod(replaceMethod: Method, method: Method)
先单独把修复后的Calculator.class打包,利用dx命令打包成out.dex,然后放到/storage/emulated/0/
目录下。
JNIEXPORT void JNICALL
Java_**_fixMethod(JNIEnv *env, jobject thiz,jobject replaceMethod, jobject method) {
art::mirror::ArtMethod *artReplaceMethod = (art::mirror::ArtMethod *) env->FromReflectedMethod(
replaceMethod);
art::mirror::ArtMethod *artMethod = (art::mirror::ArtMethod *) env->FromReflectedMethod(
method);
artReplaceMethod->access_flags_ = artMethod->access_flags_;
artReplaceMethod->declaring_class_ = artMethod->declaring_class_;
artReplaceMethod->dex_code_item_offset_ = artMethod->dex_code_item_offset_;
artReplaceMethod->dex_method_index_ = artMethod->dex_method_index_;
artReplaceMethod->hotness_count_ = artMethod->hotness_count_;
artReplaceMethod->method_index_ = artMethod->method_index_;
artReplaceMethod->ptr_sized_fields_.dex_cache_resolved_methods_ = artMethod->ptr_sized_fields_.dex_cache_resolved_methods_;
artReplaceMethod->ptr_sized_fields_.dex_cache_resolved_types_ = artMethod->ptr_sized_fields_.dex_cache_resolved_types_;
artReplaceMethod->ptr_sized_fields_.entry_point_from_jni_ = artMethod->ptr_sized_fields_.entry_point_from_jni_;
artReplaceMethod->ptr_sized_fields_.entry_point_from_quick_compiled_code_ = artMethod->ptr_sized_fields_.entry_point_from_quick_compiled_code_;
}
按照视频中所说的,把ArtMehod
中的变量一一替换。
问题记录
- 困扰我最久的是,访问sdcard,6.0以上需要手动申请,这也是自己粗心。视频中貌似也没有详细提到。而且最让我头疼的是
logcat
中的报错信息。
Caused by: java.io.IOException: No original dex files found for dex location /storage/emulated/0/out.dex
...
于是开始百度,google
没有讲到权限问题的。
这里值得一提的是曾今在使用VirtualApk
插件化技术的时候,也是遇到这种问题,必须要手到申请下权限,不然就会报错。
后面灵光一闪。添加了权限申请就好了。
- 视频中提到了需要使用到art_method.h,但是里面的东西又不能全部拿来用,因为会涉及到很多其他的头文件,所以只要
ArtMethod
的结构体就好了。
android源码很大,这里我没有去下载,直接访问http://androidxref.com/7.0.0_r1/xref/art/runtime/art_method.h#mirror
查看自己需要的内容。
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ART_RUNTIME_ART_METHOD_H_A
#define ART_RUNTIME_ART_METHOD_H_A
namespace art {
union JValue;
class OatQuickMethodHeader;
class ProfilingInfo;
class ScopedObjectAccessAlreadyRunnable;
class StringPiece;
class ShadowFrame;
namespace mirror {
class Array;
class Class;
class IfTable;
class PointerArray;
class ImtConflictTable {
enum MethodIndex {
kMethodInterface,
kMethodImplementation,
kMethodCount, // Number of elements in enum.
};
private:
union {
uint32_t data32_[0];
uint64_t data64_[0];
};
};
class ArtMethod {
public:
uint32_t declaring_class_;
uint32_t access_flags_;
uint32_t dex_code_item_offset_;
uint32_t dex_method_index_;
uint16_t method_index_;
uint16_t hotness_count_;
struct PtrSizedFields {
ArtMethod **dex_cache_resolved_methods_;
uint32_t *dex_cache_resolved_types_;
void *entry_point_from_jni_;
void *entry_point_from_quick_compiled_code_;
} ptr_sized_fields_;
};
}
} // namespace art
#endif // ART_RUNTIME_ART_METHOD_H_A
我这里去掉了一些不需要的内容,最重要的是要把ArtMethod结构体中的参数列出来。
- 测试,在华为荣耀v9中可以。在内置的android虚拟机中,debug模式下可以,正常运行后无法替换,比较奇怪,后续有发现原因,再补充。