Android的在线热更新方案_自实现
01.参考文章:https://blog.csdn.net/qq_39799899/article/details/102478355
02.Java层修复(dex文件覆盖)
03.Native层修复:
04.参考文章:https://blog.csdn.net/u013132758/article/details/96644721
1、创建工具类FixDexUtil:
注意:设置补丁文件存放位置REPAIR_FILE_PATH
public class FixDexUtil {
//这下面两个属性可自己修改
private final static String REPAIR_FILE_NAME = "BugClass"; //修复文件名
private final static String REPAIR_FILE_PATH = "fix"; //修复文件路径(默认初始路径为根目录)
private static final String DEX_SUFFIX = ".dex";
private static final String APK_SUFFIX = ".apk";
private static final String JAR_SUFFIX = ".jar";
private static final String ZIP_SUFFIX = ".zip";
private static final String DEX_DIR = "odex";
private static final String OPTIMIZE_DEX_DIR = "optimize_dex";
private static HashSet<File> loadedDex = new HashSet<>();
static {
loadedDex.clear();
}
/**
* 开启修复
*
* @param context
*/
public static void startRepair(final Context context) {
File externalStorageDirectory = context.getExternalCacheDir();
// 遍历所有的修复dex , 因为可能是多个dex修复包
File fileDir = externalStorageDirectory != null ?
new File(externalStorageDirectory, FixDexUtil.REPAIR_FILE_PATH) :
new File(context.getFilesDir(), FixDexUtil.DEX_DIR);// data/user/0/包名/files/odex(这个可以任意位置)
if (!fileDir.exists()) {//如果目录不存在就创建所有目录,这里需要添加权限
fileDir.mkdirs();
}
if (FixDexUtil.isGoingToFix(context)) {
FixDexUtil.loadFixedDex(context, context.getExternalCacheDir());
Log.i("GT_", "正在修复");
}
}
/**
* 加载补丁,使用默认目录:data/data/包名/files/odex
*
* @param context
*/
public static void loadFixedDex(Context context) {
loadFixedDex(context, null);
}
/**
* 加载补丁
*
* @param context 上下文
* @param patchFilesDir 补丁所在目录
*/
public static void loadFixedDex(Context context, File patchFilesDir) {
// dex合并之前的dex
doDexInject(context, loadedDex);
}
/**
* @author bthvi
* @time 2019/10/10 11:42
* @desc 验证是否需要热修复
*/
public static boolean isGoingToFix(@NonNull Context context) {
boolean canFix = false;
File externalStorageDirectory = context.getExternalCacheDir();
// 遍历所有的修复dex , 因为可能是多个dex修复包
File fileDir = externalStorageDirectory != null ?
new File(externalStorageDirectory, REPAIR_FILE_PATH) :
new File(context.getFilesDir(), DEX_DIR);// data/data/包名/files/odex(这个可以任意位置)
File[] listFiles = fileDir.listFiles();
if (listFiles != null) {
for (File file : listFiles) {
if (file.getName().startsWith(REPAIR_FILE_NAME) &&
(file.getName().endsWith(DEX_SUFFIX)
|| file.getName().endsWith(APK_SUFFIX)
|| file.getName().endsWith(JAR_SUFFIX)
|| file.getName().endsWith(ZIP_SUFFIX))) {
loadedDex.add(file);// 存入集合
//有目标dex文件, 需要修复
canFix = true;
}
}
}
return canFix;
}
private static void doDexInject(Context appContext, HashSet<File> loadedDex) {
String optimizeDir = appContext.getFilesDir().getAbsolutePath() +
File.separator + OPTIMIZE_DEX_DIR;
// data/data/包名/files/optimize_dex(这个必须是自己程序下的目录)
File fopt = new File(optimizeDir);
if (!fopt.exists()) {
fopt.mkdirs();
}
try {
// 1.加载应用程序dex的Loader
PathClassLoader pathLoader = (PathClassLoader) appContext.getClassLoader();
for (File dex : loadedDex) {
// 2.加载指定的修复的dex文件的Loader
DexClassLoader dexLoader = new DexClassLoader(
dex.getAbsolutePath(),// 修复好的dex(补丁)所在目录
fopt.getAbsolutePath(),// 存放dex的解压目录(用于jar、zip、apk格式的补丁)
null,// 加载dex时需要的库
pathLoader// 父类加载器
);
// 3.开始合并
// 合并的目标是Element[],重新赋值它的值即可
/**
* BaseDexClassLoader中有 变量: DexPathList pathList
* DexPathList中有 变量 Element[] dexElements
* 依次反射即可
*/
//3.1 准备好pathList的引用
Object dexPathList = getPathList(dexLoader);
Object pathPathList = getPathList(pathLoader);
//3.2 从pathList中反射出element集合
Object leftDexElements = getDexElements(dexPathList);
Object rightDexElements = getDexElements(pathPathList);
//3.3 合并两个dex数组
Object dexElements = combineArray(leftDexElements, rightDexElements);
// 重写给PathList里面的Element[] dexElements;赋值
Object pathList = getPathList(pathLoader);// 一定要重新获取,不要用pathPathList,会报错
setField(pathList, pathList.getClass(), "dexElements", dexElements);
}
Toast.makeText(appContext, "修复完成", Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 反射给对象中的属性重新赋值
*/
private static void setField(Object obj, Class<?> cl, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
Field declaredField = cl.getDeclaredField(field);
declaredField.setAccessible(true);
declaredField.set(obj, value);
}
/**
* 反射得到对象中的属性值
*/
private static Object getField(Object obj, Class<?> cl, String field) throws NoSuchFieldException, IllegalAccessException {
Field localField = cl.getDeclaredField(field);
localField.setAccessible(true);
return localField.get(obj);
}
/**
* 反射得到类加载器中的pathList对象
*/
private static Object getPathList(Object baseDexClassLoader) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
}
/**
* 反射得到pathList中的dexElements
*/
private static Object getDexElements(Object pathList) throws NoSuchFieldException, IllegalAccessException {
return getField(pathList, pathList.getClass(), "dexElements");
}
/**
* 数组合并
*/
private static Object combineArray(Object arrayLhs, Object arrayRhs) {
Class<?> clazz = arrayLhs.getClass().getComponentType();
int i = Array.getLength(arrayLhs);// 得到左数组长度(补丁数组)
int j = Array.getLength(arrayRhs);// 得到原dex数组长度
int k = i + j;// 得到总数组长度(补丁数组+原dex数组)
Object result = Array.newInstance(clazz, k);// 创建一个类型为clazz,长度为k的新数组
System.arraycopy(arrayLhs, 0, result, 0, i);
System.arraycopy(arrayRhs, 0, result, i, j);
return result;
}
}
2、获取原始Apk和获取补丁Apk:
01.获取原始Apk
02.获取补丁Class文件
03.Class文件转换为dex文件
.\dx --dex --no-strict --output=C:\Users\...\Desktop\dex\BugClass.dex C:\Users\...\Desktop\dex\BugClass.class
04.adb命令push生成的dex文件到手机指定位置
adb push C:\Users\...\Desktop\test\BugClass.dex /sdcard/Android/data/com.gsls.thermalremediation/cache/fix
3、项目下载地址:
https://download.csdn.net/download/huye930610/370761764、存在的问题:
- 01.无法成功修复Release版本的Apk(加上了混淆)
混淆会使修复失败(Apk签名没有影响)
尝试:使用重复混淆的方式------Project的build.gradle中添加依赖
使用重复混淆的方式------proguard文件中设置复用混淆规则
proguard文件中添加混淆的规则
-keep class * extends java.lang.annotation.Annotation
-keepclasseswithmembernames class * {native ;}
-keep class com.alipay.euler.andfix.** { *; }
结果:没什么卵用!!!!!
- 02.项目中使用了Dagger2注解,无法获取到Class文件
1-1、Native层修复:
创建工具类FixDexManager
public class FixDexManager {
private final static String TAG = "FixDexUtil";
private static final String DEX_SUFFIX = ".dex";
private static final String APK_SUFFIX = ".apk";
private static final String JAR_SUFFIX = ".jar";
private static final String ZIP_SUFFIX = ".zip";
public static final String DEX_DIR = "odex";
private static final String OPTIMIZE_DEX_DIR = "optimize_dex";
private Context context;
public FixDexManager(Context context) {
this.context = context;
}
public void isGoingToFix() {
File externalStorageDirectory = context.getExternalCacheDir();
// 遍历所有的修复dex , 因为可能是多个dex修复包
File fileDir = externalStorageDirectory != null ?
new File(externalStorageDirectory,"007"):
new File(context.getFilesDir(), DEX_DIR);// data/data/包名/files/odex(这个可以任意位置)
Log.i("yh55", "1---->>>>这里");
File[] listFiles = fileDir.listFiles();
if (listFiles != null){
Log.i("yh55", "2---->>>>这里" + listFiles.length);
System.out.println("TAG==目录下文件数量="+listFiles.length);
for (File file : listFiles) {
System.out.println("TAG==文件名称="+file.getName());
Log.i("yh55", "3---->>>>这里" + file.getName());
if (file.getName().startsWith("BugClass") &&
(file.getName().endsWith(DEX_SUFFIX))) {
loadDex(file);// 开始修复
//有目标dex文件, 需要修复
}
}
}
}
/**
* 加载Dex文件
* @param file
*/
public void loadDex(File file) {
try {
DexFile dexFile = DexFile.loadDex(file.getAbsolutePath(),
new File(context.getCacheDir(), "opt").getAbsolutePath(), Context.MODE_PRIVATE);
//当前的dex里面的class 类名集合
Enumeration<String> entry=dexFile.entries();
while (entry.hasMoreElements()) {
//拿到Class类名
String clazzName= entry.nextElement();
//通过加载得到类 这里不能通过反射,因为当前的dex没有加载到虚拟机内存中
Class realClazz= dexFile.loadClass(clazzName, context.getClassLoader());
if (realClazz != null) {
Log.i("yh55", "4---->>>>这里");
fixClazz(realClazz);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 修复有bug的方法
* @param realClazz
*/
private void fixClazz(Class realClazz) {
//得到类中所有方法
Method[] methods=realClazz.getMethods();
//遍历方法 通过注解 得到需要修复的方法
for (Method rightMethod : methods) {
//拿到注解
Replace replace = rightMethod.getAnnotation(Replace.class);
if (replace == null) {
continue;
}
//得到类名
String clazzName=replace.clazz();
//得到方法名
String methodName=replace.method();
try {
//反射得到本地的有bug的方法的类
Class wrongClazz = Class.forName(clazzName);
//得到有bug的方法(注意修复包中的方法参数名和参数列表必须一致)
Method wrongMethod = wrongClazz.getDeclaredMethod(methodName, rightMethod.getParameterTypes());
Log.i("yh55", "5---->>>>这里");
//调用native方法替换有bug的方法
replace(wrongMethod, rightMethod);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public native static void replace(Method wrongMethod, Method rightMethod) ;
}
1-2、创建Native方法native-lib.cpp:
#include <jni.h>
#include <string>
#include "art_method.h"
extern "C"
JNIEXPORT void JNICALL
Java_com_example_bthvi_mycloassloaderapplication_FixDexManager_replace(JNIEnv *env, jclass type, jobject wrongMethod,
jobject rightMethod) {
//ArtMethod存在于Android 系统源码中,只需要导入我们需要的部分(art_method.h)
art::mirror::ArtMethod *wrong= (art::mirror::ArtMethod *)env->FromReflectedMethod(wrongMethod);
art::mirror::ArtMethod *right= (art::mirror::ArtMethod *)env->FromReflectedMethod(rightMethod);
// method --->class ----被加载--->ClassLoader
//错误的成员变量替换为正确的成员变量
wrong->declaring_class_ = right->declaring_class_;
wrong->dex_cache_resolved_methods_ = right->dex_cache_resolved_methods_;
wrong->access_flags_ = right->access_flags_;
wrong->dex_cache_resolved_types_ = right->dex_cache_resolved_types_;
wrong->dex_code_item_offset_ = right->dex_code_item_offset_;
// 这里 方法索引的替换
wrong->method_index_ = right->method_index_;
wrong->dex_method_index_ = right->dex_method_index_;
}
1-3、创建ArtMethod:
namespace art {
namespace mirror {
class Object{
// The Class representing the type of the object.
uint32_t klass_;
// Monitor and hash code information.
uint32_t monitor_;
};
//简化ArtMethod 只需要关注我们需要的,只需要成员变量声明
class ArtMethod : public Object {
public:
uint32_t access_flags_;
uint32_t dex_code_item_offset_;
// Index into method_ids of the dex file associated with this method
//方法再dex中的索引
uint32_t method_dex_index_;
uint32_t dex_method_index_;
//在方法表的索引
uint32_t method_index_;
const void *native_method_;
const uint16_t *vmap_table_;
// Short cuts to declaring_class_->dex_cache_ member for fast compiled code access.
uint32_t dex_cache_resolved_methods_;
//方法 自发
// Short cuts to declaring_class_->dex_cache_ member for fast compiled code access.
uint32_t dex_cache_resolved_types_;
// Field order required by test "ValidateFieldOrderOfJavaCppUnionClasses".
// The class we are a part of.
//所属的函数
uint32_t declaring_class_;
};
}
}
1-4、存在的问题:同上
1-5、项目Demo下载资源
https://download.csdn.net/download/huye930610/40128562