Android热修复技术——QQ空间补丁方案解析(2)

news/2024/11/14 11:52:04/

接下来的几篇博客我会用一个真实的demo来介绍如何实现热修复。具体的内容包括:

  • 如何打包补丁包
  • 如何将通过ClassLoader加载补丁包

1. 创建Demo

demo很简单,创建一个只有一个Activity的demo:

package com.biyan.demo
public class MainActivity extends Activity {private Calculator mCal;@Overrideprotected 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也很清楚了,在此就不演示了。

2.创建补丁包

首先修复Calculator的bug。

package com.biyan.demo
Public 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上加载该补丁包。

3. 加载补丁

根据上一篇博客的介绍,加载补丁的思路如下:

  • 在Application的onCreate()方法中获取应用本身的BaseDexClassLoader,然后通过反射得到对应的dexElements
  • 创建一个新的DexClassLoader实例,然后加载sdCard上的补丁包,然后通过同样的方法得到对应的dexElements
  • 将两个dexElements合并,然后再利用反射将合并后的dexElements赋值给应用本身的BaseDexClassLoader

接下来看下具体代码:

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 {@Overridepublic 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的dexElementsClass<?> 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);// 合并两个ElementsObject combineElements = combineArray(dexElements, baseElements);// 将合并后的Element数组重新赋值给app的classLoadersetField(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了。。。
DingTalk20170220205018
原因是类预校验问题引起的:
- 在apk安装的时候系统会将dex文件优化成odex文件,在优化的过程中会涉及一个预校验的过程
- 如果一个类的static方法,private方法,override方法以及构造函数中引用了其他类,而且这些类都属于同一个dex文件,此时该类就会被打上CLASS_ISPREVERIFIED
- 如果在运行时被打上CLASS_ISPREVERIFIED的类引用了其他dex的类,就会报错
- 所以MainActivityonCreate()方法中引用另一个dex的类就会出现上文中的问题
- 正常的分包方案会保证相关类被打入同一个dex文件
- 想要使得patch可以被正常加载,就必须保证类不会被打上CLASS_ISPREVERIFIED标记。而要实现这个目的就必须要在分完包后的class中植入对其他dex文件中类的引用
- 要在已经编译完成后的类中植入对其他类的引用,就需要操作字节码,惯用的方案是插桩。常见的工具有javaassist,asm等。

其实QQ空间补丁方案的关键就在于字节码的注入而不是dex的注入。下一篇博客将会介绍字节码注入的相关细节。


http://www.ppmy.cn/news/395570.html

相关文章

一键删除qq空间说说

var delay1 2500; var delay2 2200; var delay3 4000; var i 0; var next “#pager_next_”; function del() {try{document.querySelector(‘.app_canvas_frame’).contentDocument.querySelector(‘.del_btn’).click();setTimeout(“yes()”, delay2); }catch(err){nex…

QQ空间 安卓App热补丁动态修复技术介绍

【原文地址 点击打开链接】 【各自热修复框架介绍 点击打开链接】 【AndFix使用说明 AndFix使用说明】 AndFix与Nuwa对比 Nuwa是另一个热补丁框架&#xff0c;原理是基于QQ空间团队提出的安卓App热补丁动态修复技术介绍。 与Nuwa相比&#xff0c;AndFix有一下优点&#xf…

电脑修复损坏文件--修复命令;系统盘磁盘空间不足

目录 sfc/scannow--修复损坏的系统文件命令解析command parse步骤 命令修复磁盘&#xff1b;磁盘空间的释放输入“sfc /purgecache”命令&#xff08;不含双引号&#xff0c;下同&#xff09;输入“cleanmgr”命令输入“CHKDSK D:/F”&#xff0c;修复D驱动器号 sfc/scannow–修…

基于QQ空间热修复原理实践

基于QQ空间热修复原理实践 关于热修复技术&#xff0c;去年真是火的一塌糊涂&#xff0c;俺们没有及时赶上&#xff0c;好在现在赶上也不算晚&#xff0c;好了废话不多说&#xff0c;直接进入正题。 原理&#xff1a; 简单阐述一下&#xff0c;具体的还是看原文吧。 说白了…

QQ空间无法修改“空间描述”修复方法

今天修改QQ空间描述&#xff0c;提示对话框“用户信息更新失败&#xff01;”弄得我满处百度&#xff0c;几般尝试&#xff0c;可就是无法修改描述。结果&#xff0c;果断想起是不是没修改资料&#xff0c;跑去QQ个人中心官网&#xff0c;把星座那些刚才改生日自动填写的资料全…

QQ空间里面的照片变模糊了怎么办?时间越久越模糊怎么修复清晰?

很多人是不是都拿QQ空间当图片存档的地方&#xff0c;照片放进去你以为可以不管了&#xff0c;可以永久保存了&#xff01;哪知道图片越放越模糊&#xff0c;甚至10年前的照片居然全成了马赛克&#xff0c;根本看不清楚了&#xff0c;哭都没地方去。 网上许多网友在看为什么存…

QQ空间热修复原理深入解析

一、背景 App的上线发布是我们程序猿开心的事情,证明着一段时间来成果的进步和展现。但是随着App的上线手机App市场,接下来的更新维护工作便成了”家常便饭“。尤其是在创业公司,随着业务等不稳定性因素,前期App的更新工作更为频繁,可能两天一小改,三天一大改的情况经常发…

Qzone 超级补丁热修复方案原理

介绍 Qzone 超级补丁技术基于dex分包方案&#xff0c;使用了多dex加载(multidex)的原理&#xff0c;大致的过程就是&#xff1a;把BUG方法修复以后&#xff0c;放到一个单独的dex文件&#xff0c;然后插入到dexElements数组的最前面&#xff0c;让虚拟机去加载修复完后的方法。…