本文共 5265 字,大约阅读时间需要 17 分钟。
接下来的几篇博客我会用一个真实的demo来介绍如何实现热修复。具体的内容包括:
demo很简单,创建一个只有一个Activity的demo:
package com.biyan.demopublic class MainActivity extends Activity { private Calculator mCal; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mCal = new Calculator(); } public void click(View view) { Toast.makeText(this, String.valueOf(mCal.calculate()),Toast.LENGTH_SHORT).show(); }}
Public class Caculoator { public float calculate() { return 1 / 0; }}
demo的代码很简单,运行会出什么bug也很清楚了,在此就不演示了。
首先修复Calculator的bug。
package com.biyan.demoPublic class Caculoator { public float calculate() { return 1 / 1; }}
重新编译项目,在build目录下找到Calculator.class文件,将其拷出来,准备打包。放置在于Calculator包名相同的路径下。
将其打成jar包:jar -cvf patch.jar com
然后再将对应的jar包打成dex包:
dx --dex --output=patch_dex.jar patch.jar
dx是讲jar包打成dex包的工具,安装在path-android-sdk/build-tools/version(如24.0.0)/dx。
patch_dex.jar就是补丁包,接下来将其安装在sdCard中,接下来应用从sdCard上加载该补丁包。根据上一篇博客的介绍,加载补丁的思路如下:
BaseDexClassLoader
,然后通过反射得到对应的dexElementsBaseDexClassLoader
接下来看下具体代码:
package com.hotpatch.demo;import android.app.Application;import android.os.Environment;import android.util.Log;import java.io.File;import java.lang.reflect.Array;import java.lang.reflect.Field;import dalvik.system.DexClassLoader;/** * Created by hp on 2016/4/6. */public class HotPatchApplication extends Application { @Override public void onCreate() { super.onCreate(); // 获取补丁,如果存在就执行注入操作 String dexPath = Environment.getExternalStorageDirectory().getAbsolutePath().concat("/patch_dex.jar"); File file = new File(dexPath); if (file.exists()) { inject(dexPath); } else { Log.e("BugFixApplication", dexPath + "不存在"); } } /** * 要注入的dex的路径 * * @param path */ private void inject(String path) { try { // 获取classes的dexElements Class cl = Class.forName("dalvik.system.BaseDexClassLoader"); Object pathList = getField(cl, "pathList", getClassLoader()); Object baseElements = getField(pathList.getClass(), "dexElements", pathList); // 获取patch_dex的dexElements(需要先加载dex) String dexopt = getDir("dexopt", 0).getAbsolutePath(); DexClassLoader dexClassLoader = new DexClassLoader(path, dexopt, dexopt, getClassLoader()); Object obj = getField(cl, "pathList", dexClassLoader); Object dexElements = getField(obj.getClass(), "dexElements", obj); // 合并两个Elements Object combineElements = combineArray(dexElements, baseElements); // 将合并后的Element数组重新赋值给app的classLoader setField(pathList.getClass(), "dexElements", pathList, combineElements); //======== 以下是测试是否成功注入 ================= Object object = getField(pathList.getClass(), "dexElements", pathList); int length = Array.getLength(object); Log.e("BugFixApplication", "length = " + length); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } } /** * 通过反射获取对象的属性值 */ private Object getField(Class cl, String fieldName, Object object) throws NoSuchFieldException, IllegalAccessException { Field field = cl.getDeclaredField(fieldName); field.setAccessible(true); return field.get(object); } /** * 通过反射设置对象的属性值 */ private void setField(Class cl, String fieldName, Object object, Object value) throws NoSuchFieldException, IllegalAccessException { Field field = cl.getDeclaredField(fieldName); field.setAccessible(true); field.set(object, value); } /** * 通过反射合并两个数组 */ private Object combineArray(Object firstArr, Object secondArr) { int firstLength = Array.getLength(firstArr); int secondLength = Array.getLength(secondArr); int length = firstLength + secondLength; Class componentType = firstArr.getClass().getComponentType(); Object newArr = Array.newInstance(componentType, length); for (int i = 0; i < length; i++) { if (i < firstLength) { Array.set(newArr, i, Array.get(firstArr, i)); } else { Array.set(newArr, i, Array.get(secondArr, i - firstLength)); } } return newArr; }}
核心代码就这么多,接下来运行一下程序。程序还是Crash了。。。
原因是类预校验问题引起的:CLASS_ISPREVERIFIED
CLASS_ISPREVERIFIED
的类引用了其他dex的类,就会报错MainActivity
的onCreate()
方法中引用另一个dex的类就会出现上文中的问题CLASS_ISPREVERIFIED
标记。而要实现这个目的就必须要在分完包后的class中植入对其他dex文件中类的引用其实QQ空间补丁方案的关键就在于字节码的注入而不是dex的注入。下一篇博客将会介绍字节码注入的相关细节。