https://www.jianshu.com/p/b65e5da3dff2
-
先了解一下原理和实现方式
Java编译为class
javac xxx.java.
class打包为jar包
jar cvf xxx.jar x/x/x/class #可用.来代替目录 意思为当前目录和所有的子目录打包
将as编译好的jar包拆开
unzip a.jar -d outfile
jar -xvf a.jar
jar包转为dex
./d8 --output xxx-file xxx.jar
代码
工具类
package com.example.hotfix;
import android.content.Context;
import android.util.Log;
import android.widget.Toast;
import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;
public class MyFix {
public static void fixDexFile(Context context, String fixDexFilePath) {
if (fixDexFilePath != null && new File(fixDexFilePath).exists()) {
try {
injectDexToClassLoader(context, fixDexFilePath);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private static void injectDexToClassLoader(Context context, String fixDexFilePath)
throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException{
// 旧数组
PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();
Object basePathList = getPathList(pathClassLoader);
Object baseElements = getDexElements(basePathList);
// System.out.println("原数组为:" + Arrays.toString((Object[]) baseElements));
// 修复数组
String baseDexAbsolutePath = context.getDir("dex", 0).getAbsolutePath();
DexClassLoader fixDexClassLoader = new DexClassLoader(
fixDexFilePath, baseDexAbsolutePath, fixDexFilePath, context.getClassLoader());
Object fixPathList = getPathList(fixDexClassLoader);
Object fixElements = getDexElements(fixPathList);
// 新数组
Object newElements = combineArray(baseElements, fixElements);
System.out.println("新数组为:" + Arrays.toString((Object[]) newElements));
Object basePathList2 = getPathList(pathClassLoader);
//新数组替换旧数组
setField(basePathList2, basePathList2.getClass(), "dexElements", newElements);
}
public static Object getPathList(Object obj) throws ClassNotFoundException, NoSuchFieldException,
IllegalAccessException {
return getField(obj, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
}
public 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);
}
private static Object combineArray(Object baseElements, Object fixElements) {
Class componentType = fixElements.getClass().getComponentType();
int length = Array.getLength(fixElements);
int length2 = Array.getLength(baseElements) + length;
Object newInstance = Array.newInstance(componentType, length2);
for (int i = 0; i < length2; i++) {
if (i < length) {
//修复数在新数组的“0”
Array.set(newInstance, i, Array.get(fixElements, i));
} else {
Array.set(newInstance, i, Array.get(baseElements, i - length));
}
}
return newInstance;
}
}
MainActivity
package com.example.hotfix;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import java.io.File;
import java.lang.reflect.Array;
import java.util.Arrays;
import dalvik.system.BaseDexClassLoader;
import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;
public class MainActivity extends AppCompatActivity {
private static final String DEX_DIR = "patch";
private static String TAG = "Mainactivity";
private Context context;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn_run = findViewById(R.id.btn_run);
Button btn_repair = findViewById(R.id.btn_repair);
context = MainActivity.this;
checkFix();
init();
btn_run.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
new BugTest().getbug();
}
});
btn_repair.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
checkFix();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
private void init() {
File patchDir = new File(this.getExternalFilesDir(null), DEX_DIR);
if (!patchDir.exists()) {
patchDir.mkdirs();
}
}
// private void checkStoragePermission() {
// if (ContextCompat.checkSelfPermission(this,
// Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
// || ContextCompat.checkSelfPermission(this,
// Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// // 如果权限没有被授予,则请求权限
// ActivityCompat.requestPermissions(this,
// new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
// android.Manifest.permission.WRITE_EXTERNAL_STORAGE},
// REQUEST_CODE_STORAGE_PERMISSION);
// Log.e(TAG, "开始请求权限");
// } else {
// Log.e(TAG, "权限已经拿到");
// // 如果权限已经被授予,则可以执行相应的操作
// // 例如读取或写入文件
// }
// }
//
// @Override
// public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
// super.onRequestPermissionsResult(requestCode, permissions, grantResults);
// if (requestCode == REQUEST_CODE_STORAGE_PERMISSION) {
// if (grantResults.length > 0
// && grantResults[0] == PackageManager.PERMISSION_GRANTED
// && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
//
// } else {
// // 如果用户拒绝了权限请求,则可以显示一个提示信息,或者采取其他措施
// if (shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE)
// || shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// // 再次请求权限
// ActivityCompat.requestPermissions(this,
// new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
// Manifest.permission.WRITE_EXTERNAL_STORAGE},
// REQUEST_CODE_STORAGE_PERMISSION);
// } else {
// // 如果用户拒绝了权限请求,并且选择了“不再询问”选项,可以显示一个提示信息
// Toast.makeText(this, "权限被拒绝,某些功能可能无法正常工作", Toast.LENGTH_SHORT).show();
// }
// }
// }
// }
//
// public Context getContext() {
// Context context = MainActivity.this;
// return context;
// }
private void checkFix() {
try {
// String dexPath = Environment.getExternalStorageDirectory() + "/classes.dex";
// String dexPath = String.valueOf(context.getExternalFilesDir(DEX_DIR+"/classes.dex"));
String dexPath = "/storage/emulated/0/Android/data/com.example.hotfix/files/patch/classes.dex";
// String dexPath="/data/data/com.example.hotfix/cache/classes.dex";
Log.e(TAG, "dex文件路径为"+dexPath);
new MyFix().fixDexFile(this, dexPath);
} catch (Exception e) {
Toast.makeText(this, "修复失败" + e.getMessage(), Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
}
private void getarrly() {
try {
PathClassLoader pathClassLoader = (PathClassLoader) MainActivity.this.getClassLoader();
Object basePathList = new MyFix().getPathList(pathClassLoader);
Object baseElements = new MyFix().getDexElements(basePathList);
System.out.println("原数组为:" + Arrays.toString((Object[]) baseElements));
}catch (Exception e){
e.printStackTrace();
}
}
@Override
protected void onStart() {
super.onStart();
getarrly();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
@Override
protected void onRestart() {
super.onRestart();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onStop() {
super.onStop();
}
}
补充
-
修改后的dex数组因为双亲模型的原因 需要尽快的进行加载 重写attachBaseContext 将加载放置于之中
-
双亲模型特性同一个classloader,同一个class 只会加载一次