Android热修复手动实现
Posted lxn_李小牛
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android热修复手动实现相关的知识,希望对你有一定的参考价值。
前言
热修复,简单的说就是在不重新下载安装app的情况下,自动修复现有app的问题,今天来做一个简单的实现。
效果图
点击TEST我们执行下面的方法
public void test(View view)
TestCaculate testCaculate = new TestCaculate();
testCaculate.caculate(this);
public class TestCaculate
public int a = 10;
public int b = 0;
public void caculate(Context context)
Toast.makeText(context, "结果" + a / b, Toast.LENGTH_SHORT).show();
很明显,这里会产生一个除数为0的异常,导致app退出。接下来我们就来修复。
热修复的前提是apk中有多个dex,所以我们要先进行multidex的支持,
implementation 'com.android.support:multidex:1.0.3' // 引入multidex库
defaultConfig
applicationId "practice.lxn.cn.weather"
minSdkVersion 15
targetSdkVersion 22
versionCode 1
versionName "1.0"
multiDexEnabled true
multiDexKeepFile file('maindexlist.txt') // maindexlist.txt文件指定哪些类在主dex中
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
dexOptions
javaMaxHeapSize "4g"
preDexLibraries = false
additionalParameters = ['--multi-dex', '--main-dex-list='+ project.rootDir.absolutePath + '/maindexlist.txt', '--minimal-main-dex',
'--set-max-idx-number=1000']
内容如下
android/support/multidex/MultiDex.class
android/support/multidex/MultiDexApplication.class
android/support/multidex/MultiDexExtractor.class
android/support/multidex/MultiDexExtractor$1.class
android/support/multidex/MultiDex$4.class
android/support/multidex/MultiDex$14.class
android/support/multidex/MultiDex$19.class
android/support/multidex/ZipUtil.class
android/support/multidex/ZipUtil$CentralDirectory.class
practice/lxn/cn/weather/MainActivity.class
practice/lxn/cn/weather/CustomApplication.class
应用的目录如下
这里我们把有问题的类和修复工具打到从dex里,编译打包,我们可以看到两个dex文件,通过dex2jar和JD-GUI可以查看,确实打进去了。
然后我们把问题修复,重新打包
public class TestCaculate
public int a = 10;
public int b = 1;
public void caculate(Context context)
Toast.makeText(context, "结果" + a / b, Toast.LENGTH_SHORT).show();
接下来把打好的修复包classes2.dex放到外部存储目录,修复的时候我们手动把它拷贝到应用自己的目录下,进行修复,整个过程代码如下
public class MainActivity extends AppCompatActivity
public static final String DEX_PATH = Environment.getExternalStorageDirectory().getAbsolutePath();
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
public void test(View view)
TestCaculate testCaculate = new TestCaculate();
testCaculate.caculate(this);
public void fix(View view)
try
// 把dex文件从外部目录复制到应用程序所在目录,方便类加载器加载
String fileName = "classes2.dex";
File dir = new File(DEX_PATH + File.separator);
File file = new File(dir + File.separator + fileName);
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(getDir("dex",MODE_PRIVATE) + fileName);
int len;
byte[] bytes = new byte[1024];
while ((len = fis.read(bytes)) != -1)
fos.write(bytes,0,len);
fis.close();
fos.close();
FixUtil.patch(this,file.getAbsolutePath(),"practice.lxn.cn.weather.test.TestCaculate");
Toast.makeText(this, "修复成功", Toast.LENGTH_SHORT).show();
catch (Exception e)
Toast.makeText(this, "修复失败" + e.getMessage(), Toast.LENGTH_SHORT).show();
e.printStackTrace();
public class FixUtil
private static final String TAG = "FixUtil";
/**
* 修复指定的类
* @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)
Log.d(TAG, "patch: " + th.getMessage());
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(String.class, String.class, String.class, ClassLoader.class).newInstance(
context.getDir("dex", 0).getAbsolutePath() + File.separator + replaceAll,
context.getDir("dex", 0).getAbsolutePath(), patchDexFile, obj);
cls.getMethod("loadClass", String.class).invoke(newInstance, 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()))));
// 获取pathList对象
Object a2 = getPathList(pathClassLoader);
//新的dexElements对象重新设置回去
setField(a2, a2.getClass(), "dexElements", a);
pathClassLoader.loadClass(str2);
/**
* 通过反射先获取到pathList对象
*
* @param obj
* @return
* @throws ClassNotFoundException e
* @throws NoSuchFieldException e
* @throws IllegalAccessException e
*/
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;
最后给出源码的下载路径
欢迎大家在下方评论和留言,有问题一起讨论
以上是关于Android热修复手动实现的主要内容,如果未能解决你的问题,请参考以下文章
Android Gradle 插件热修复实现 ① ( Android 热修复系统组成 | 热修复工作流程 | 热修复使用到的技术 | 热修复框架选择注意事项 )
Android Gradle 插件热修复实现 ① ( Android 热修复系统组成 | 热修复工作流程 | 热修复使用到的技术 | 热修复框架选择注意事项 )