Android-FixBug热修复框架的使用及源码分析(不发版修复bug)
2015-12-24 13:59
651 查看
前面几篇博文已经介绍了2种热修复框架的使用及源码分析,AndFix兼容性比较好,而Dexposed Art处于Beta版。
AndFix和Dexposed都是阿里的开源项目。
Alibaba-AndFix Bug热修复框架的使用
Alibaba-AndFix Bug热修复框架原理及源码解析
Alibaba-Dexposed框架在线热补丁修复的使用
Alibaba-Dexposed Bug框架原理及源码解析
今天主要介绍的框架是根据腾讯的博客使用ClassLoader写的热修复框架。
腾讯博客:【新技能get】让App像Web一样发布新版本
首先,看需要修复的类部分:
以上是手机上安装的apk存在bug的位置。
接下,是修复完成Bug后的类:
修复好了,build工程(工具也会自动build),把java编译成class文件。
由于我使用debug模式调试安装的apk,所以我build完成后找的也是debug下的class文件,如下:
红色框标注的部分就是MainActivity build完成后生成class文件,如果你想问我怎么是三个class文件,原因是:onCreate中有2个注册的点击时间监听器,每个监听器都生成了一个新的class文件。
把相关修复bug的类的class文件复制到一个文件下(fixbugdex),当前我也保存了class所在的包。如下:
以下,拷贝方式不可取:
如果在工具中看class文件,效果如下:
这里看到的仅有一个MainActivity.class,切记,这是工具为了方便你查看class文件,显示上做了处理。不能从此位置单独一个个class文件拷贝,例如从此位置单独拷贝出MainActivity.class,这个class就不是完成的类了,文件内容如下:
所以此方式不可取,当然可以直接拷贝整个包。
然后cmd到刚才的fixbugdex文件
然后执行命令 jar cvf fixbug.jar cn/*
这条命令就是把cn下的所有文件打包到fixbug.jar文件中
执行完成后:
查看fixbug.jar内容:
接下,需要把jar文件转换成dex文件:
工具:dx
下载工具并解压到AndroidSdk–>platform-tools目录
同时,也可以把fixbug.jar文件拷贝到AndroidSdk–>platform-tools目录,然后你也可以使用绝对路径.
cmd到AndroidSdk–>platform-tools目录下执行命令:
注:–output 后面可以接绝对路径。
执行完成后的结果:
查看一下patch.jar文件的内容:
打开应用执行Toast按钮:
我为了测试方便,把patch.jar文件放到了sdcard根目录中,当然也可以选择网上下载的方式,其实都是一样的。
启动后效果如下:
然后,点击加载补丁文件,并推出应用重新进入,并点击Toast按钮:
到此,bug已经被修复完成。
Api接口介绍:
首先,把FixBugManage.java文件引入到你的项目中
首先,在自定义Application中初始化:
init(versionCode);
当versionCode与之前的versionCode不同,会自动清除掉之前addPatch所有的补丁文件
当versionCode与之前的versionCode相同,会自动加载之前addPatch所有的补丁文件
添加新补丁文件接口:
addPatch(patchPath);
清除所有补丁文件的接口:
到此接口已介绍完。
中途遇到的坑:
执行
异常:bad class file magic (cafebabe) or version (0033.0000)
解决方法:在build.gradle中jdk的版本修改为1.6
FixBugManage源码分析:
代码地址:FixBug
如有bug或者不足,可以即时告知我,我会即时修改。
AndFix和Dexposed都是阿里的开源项目。
Alibaba-AndFix Bug热修复框架的使用
Alibaba-AndFix Bug热修复框架原理及源码解析
Alibaba-Dexposed框架在线热补丁修复的使用
Alibaba-Dexposed Bug框架原理及源码解析
今天主要介绍的框架是根据腾讯的博客使用ClassLoader写的热修复框架。
腾讯博客:【新技能get】让App像Web一样发布新版本
首先,看需要修复的类部分:
[code]package cn.coolspan.open.fixbug; import java.io.File; import android.app.Activity; import android.os.Bundle; import android.os.Environment; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Toast; /** * MainActivity 2015-12-22 下午10:30:57 * * @author 乔晓松 965266509@qq.com */ public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.button1).setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { MyApplication myApplication = (MyApplication) getApplication(); File patch = new File( Environment.getExternalStorageDirectory(), "patch.jar"); Log.e("file:", "" + patch.exists()); myApplication.fixBugManage.addPatch(patch.getAbsolutePath()); } }); findViewById(R.id.button2).setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { //修复此位置的bug Toast.makeText(MainActivity.this, "...bug...", Toast.LENGTH_SHORT).show(); } }); } }
以上是手机上安装的apk存在bug的位置。
接下,是修复完成Bug后的类:
[code]...... ...... findViewById(R.id.button2).setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { Toast.makeText(MainActivity.this, "fix...bug...", Toast.LENGTH_SHORT).show(); } }); ...... ......
修复好了,build工程(工具也会自动build),把java编译成class文件。
由于我使用debug模式调试安装的apk,所以我build完成后找的也是debug下的class文件,如下:
红色框标注的部分就是MainActivity build完成后生成class文件,如果你想问我怎么是三个class文件,原因是:onCreate中有2个注册的点击时间监听器,每个监听器都生成了一个新的class文件。
把相关修复bug的类的class文件复制到一个文件下(fixbugdex),当前我也保存了class所在的包。如下:
以下,拷贝方式不可取:
如果在工具中看class文件,效果如下:
这里看到的仅有一个MainActivity.class,切记,这是工具为了方便你查看class文件,显示上做了处理。不能从此位置单独一个个class文件拷贝,例如从此位置单独拷贝出MainActivity.class,这个class就不是完成的类了,文件内容如下:
所以此方式不可取,当然可以直接拷贝整个包。
然后cmd到刚才的fixbugdex文件
然后执行命令 jar cvf fixbug.jar cn/*
这条命令就是把cn下的所有文件打包到fixbug.jar文件中
执行完成后:
查看fixbug.jar内容:
接下,需要把jar文件转换成dex文件:
工具:dx
下载工具并解压到AndroidSdk–>platform-tools目录
同时,也可以把fixbug.jar文件拷贝到AndroidSdk–>platform-tools目录,然后你也可以使用绝对路径.
cmd到AndroidSdk–>platform-tools目录下执行命令:
[code]dx --dex --output patch.jar fixbug.jar
注:–output 后面可以接绝对路径。
执行完成后的结果:
查看一下patch.jar文件的内容:
打开应用执行Toast按钮:
我为了测试方便,把patch.jar文件放到了sdcard根目录中,当然也可以选择网上下载的方式,其实都是一样的。
启动后效果如下:
然后,点击加载补丁文件,并推出应用重新进入,并点击Toast按钮:
到此,bug已经被修复完成。
Api接口介绍:
首先,把FixBugManage.java文件引入到你的项目中
首先,在自定义Application中初始化:
init(versionCode);
当versionCode与之前的versionCode不同,会自动清除掉之前addPatch所有的补丁文件
当versionCode与之前的versionCode相同,会自动加载之前addPatch所有的补丁文件
[code]package cn.coolspan.open.fixbug; import android.app.Application; public class MyApplication extends Application { public FixBugManage fixBugManage; @Override public void onCreate() { super.onCreate(); this.fixBugManage = new FixBugManage(this); this.fixBugManage.init("1.0"); } }
添加新补丁文件接口:
addPatch(patchPath);
[code] MyApplication myApplication = (MyApplication) getApplication(); File patch = new File( Environment.getExternalStorageDirectory(), "patch.jar"); myApplication.fixBugManage.addPatch(patch.getAbsolutePath());
清除所有补丁文件的接口:
[code]removeAllPatch();
到此接口已介绍完。
中途遇到的坑:
执行
jar cvf fixbug.jar cn/*
异常:bad class file magic (cafebabe) or version (0033.0000)
解决方法:在build.gradle中jdk的版本修改为1.6
[code] compileOptions { sourceCompatibility JavaVersion.VERSION_1_6 targetCompatibility JavaVersion.VERSION_1_6 }
FixBugManage源码分析:
[code]package cn.coolspan.open.fixbug; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.math.BigInteger; import java.security.MessageDigest; import dalvik.system.DexClassLoader; import dalvik.system.PathClassLoader; import android.content.Context; import android.content.SharedPreferences; /** * FixBugManage 2015-12-22 下午9:59:28 * * @author 乔晓松 965266509@qq.com */ public class FixBugManage { private Context context; private static final int BUF_SIZE = 2048; private File patchs; private File patchsOptFile; public FixBugManage(Context context) { this.context = context; this.patchs = new File(this.context.getFilesDir(), "patchs");// 存放补丁文件 this.patchsOptFile = new File(this.context.getFilesDir(), "patchsopt");// 存放预处理补丁文件压缩处理后的dex文件 } /** * 初始化版本号 * * @param versionCode */ public void init(String versionCode) { SharedPreferences sharedPreferences = this.context .getSharedPreferences("fixbug", Context.MODE_PRIVATE); String oldVersionCode = sharedPreferences .getString("versionCode", null); if (oldVersionCode == null || !oldVersionCode.equalsIgnoreCase(versionCode)) { this.initPatchsDir();// 初始化补丁文件目录 this.clearPaths();// 清楚所有的补丁文件 sharedPreferences.edit().clear().putString("versionCode", versionCode) .commit();// 存储版本号 } else { this.loadPatchs();// 加载已经添加的补丁文件(.jar) } } /** * 读取补丁文件夹并加载 */ private void loadPatchs() { if (patchs.exists() && patchs.isDirectory()) {// 判断文件是否存在并判断是否是文件夹 File patchFiles[] = patchs.listFiles();// 获取文件夹下的所有的文件 for (int i = 0; i < patchFiles.length; i++) { if (patchFiles[i].getName().lastIndexOf(".jar") == patchFiles[i] .getName().length() - 4) {// 仅处理.jar文件 this.loadPatch(patchFiles[i].getAbsolutePath());// 加载jar文件 } } } else { this.initPatchsDir(); } } /** * 加载单个补丁文件 * * @param patchPath */ private void loadPatch(String patchPath) { try { injectDexAtFirst(patchPath, patchsOptFile.getAbsolutePath());// 读取jar文件中dex内容 } catch (Exception e) { e.printStackTrace(); } } /** * patch所在文件目录 * * @param patchPath */ public void addPatch(String patchPath) { File inFile = new File(patchPath); File outFile = new File(patchs, inFile.getName()); this.copyFile(outFile, inFile); this.loadPatch(patchPath); } /** * 移除所有的patch文件 */ public void removeAllPatch() { this.clearPaths(); } /** * 清除所有的补丁文件 */ private void clearPaths() { if (patchs.exists() && patchs.isDirectory()) { File patchFiles[] = patchs.listFiles(); for (int i = 0; i < patchFiles.length; i++) { if (patchFiles[i].getName().lastIndexOf(".jar") == patchFiles[i] .getName().length() - 4) { patchFiles[i].delete(); } } } } /** * 初始化存放补丁的文件目录 */ private void initPatchsDir() { if (!this.patchs.exists()) { this.patchs.mkdirs(); } if (!this.patchsOptFile.exists()) { this.patchsOptFile.mkdirs(); } } /** * 复制文件从inFile到outFile * * @param outFile * @param inFile * @return */ private boolean copyFile(File outFile, File inFile) { BufferedInputStream bis = null; OutputStream dexWriter = null; try { MessageDigest digests = MessageDigest.getInstance("MD5"); bis = new BufferedInputStream(new FileInputStream(inFile)); dexWriter = new BufferedOutputStream(new FileOutputStream(outFile)); byte[] buf = new byte[BUF_SIZE]; int len; while ((len = bis.read(buf, 0, BUF_SIZE)) > 0) { digests.update(buf, 0, len); dexWriter.write(buf, 0, len); } dexWriter.close(); bis.close(); BigInteger bi = new BigInteger(1, digests.digest()); String result = bi.toString(16); File toFile = new File(outFile.getParentFile(), result + ".jar"); outFile.renameTo(toFile); return true; } catch (Exception e) { if (dexWriter != null) { try { dexWriter.close(); } catch (IOException ioe) { ioe.printStackTrace(); } } if (bis != null) { try { bis.close(); } catch (IOException ioe) { ioe.printStackTrace(); } } return false; } } public static void injectDexAtFirst(String dexPath, String defaultDexOptPath) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException { DexClassLoader dexClassLoader = new DexClassLoader(dexPath, defaultDexOptPath, dexPath, getPathClassLoader());// 把dexPath文件补丁处理后放入到defaultDexOptPath目录中 Object baseDexElements = getDexElements(getPathList(getPathClassLoader()));// 获取当面应用Dex的内容 Object newDexElements = getDexElements(getPathList(dexClassLoader));// 获取补丁文件Dex的内容 Object allDexElements = combineArray(newDexElements, baseDexElements);// 把当前apk的dex和补丁文件的dex进行合并 Object pathList = getPathList(getPathClassLoader());// 获取当前的patchList对象 setField(pathList, pathList.getClass(), "dexElements", allDexElements);// 利用反射设置对象的值 } private static PathClassLoader getPathClassLoader() { PathClassLoader pathClassLoader = (PathClassLoader) FixBugManage.class .getClassLoader();// 获取类加载器 return pathClassLoader; } private static Object getDexElements(Object paramObject) throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException { return getField(paramObject, paramObject.getClass(), "dexElements");// 利用反射获取到dexElements属性 } private static Object getPathList(Object baseDexClassLoader) throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException { return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");// 利用反射获取到pathList属性 } /** * 此方法是合并2个数组,把补丁dex中的内容放到数组最前,达到修复bug的目的 * * @param firstArray * @param secondArray * @return */ private static Object combineArray(Object firstArray, Object secondArray) { Class<?> localClass = firstArray.getClass().getComponentType(); int firstArrayLength = Array.getLength(firstArray); int allLength = firstArrayLength + Array.getLength(secondArray); Object result = Array.newInstance(localClass, allLength); for (int k = 0; k < allLength; ++k) { if (k < firstArrayLength) { Array.set(result, k, Array.get(firstArray, k)); } else { Array.set(result, k, Array.get(secondArray, k - firstArrayLength)); } } return result; } public static Object getField(Object obj, Class<?> cl, String field) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException { Field localField = cl.getDeclaredField(field); localField.setAccessible(true);// 强制反射 return localField.get(obj);// 获取值 } public static void setField(Object obj, Class<?> cl, String field, Object value) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException { Field localField = cl.getDeclaredField(field); localField.setAccessible(true);// 强制反射 localField.set(obj, value);// 设置值 } }
代码地址:FixBug
如有bug或者不足,可以即时告知我,我会即时修改。
相关文章推荐
- Android Studio界面介绍
- Android之.XML布局
- Android中Bitmap和Drawable(转)
- android design support library——NavigationView
- Android 利用Gson生成或解析json
- Android默认加载页设置
- Android ANR 类型
- Android EventBus源代码解析 带你深入理解EventBus
- 使用新版Android Studio检测内存泄露和性能
- 使用新版Android Studio检测内存泄露和性能
- Android - Activity,A,B,C跳过B直接返回A
- Android给Button设置drawableRigh 在代码中动态改变
- 6.Android之switch和togglebutton按钮学习
- Android中设定EditText的输入长度
- Android Studio 多渠道打包
- android中ListView多次重复执行getView的问题
- Android 内存泄漏分析工具LeakCanary
- 360项目总结01
- Activity之间传递Bitmap
- 《Android群英传》读书笔记(11)第十章:Android性能优化