android热修复

我的热修复例子是通过鸿洋大神的博客http://blog.csdn.net/lmj623565791/article/details/49883661里面的例子来写的。

关于热修复在这里大概的描述一下:

热修复又叫做动态加载,热更新,热修复等等等一大堆的名字,不过好像都是一个意思:就是通过打补丁的方式来有没有办法以补丁的方式动态修复紧急Bug,不再需要重新发布App,不再需要用户重新下载,覆盖安装。安卓App热补丁动态修复技术介绍具体的可以看一下这篇博客的关于动态修复的介绍。还可以看一下关于apk编译的过程,我这边主要是关于具体的实现。

在项目中如果BugClass.突然出现了一个bug,然后我们要发布一个jar包,把BugClass.jave文件给替换掉,要如何实现呢?

BugClass类:

public class BugClass{
	public String bug(){
		return "bug class";
	}
}


LoadBugClass类:

public class LoadBugClass {

	public String getBugString(){
		BugClass bugClass = new BugClass();
		return bugClass.bug();
	}
}



如果在BugClass出现问题了我们要把return返回的字符串改一改要如何实现呢?例如改成这样:

public class BugClass{
	public String bug(){
		return "fix the bug class2";
	}
}

首先java文件编译后会形成dex文件,这样才能在安卓虚拟机上运行。既然要动态修复的话,我们总不可能直接搞一个jar包,然后里面是class文件吧?!所以我们要把修复的类导出来,然后用dx工具转化成dex文件的格式。把java导出成jar包的形式我就不多说了,把jar包转化成dex文件的话,可以在sdk的build-tools文件夹下的随便一个文件夹点击进去就可以看到一个dx.bat的文件,那个就是dex的转换工具。

运行cmd命令行,然后进入到dx.bat存在的目录下。记得也要把你导出的jar文件放在同一个目录下:然后输入命令行:de --dex --output XXX.dex(你要转化的dex包的名称) XXX.jar(你导入的jar包),点击确定后你就可以开心的看到在目录下会生成个XXX.dex文件,这个就是后面需要用到的要修复的dex文件。

然后把dex文件放在assets文件夹下,这样要修复的材料准备好了,现在就要着手把这个dex文件加载到安卓虚拟机上。

一般的实现方式是:在服务器上下载dex或者jar包,然后动态加载,我是放在assets文件夹下,差别不是很大,就少了一个具体的从网络上获取dex包的过程。下面就是具体的实现方案

MainActivity类

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;



public class MainActivity extends Activity {
    private static final String TAG = "TAG";
    private Handler mHandler = new Handler(){
    	public void handleMessage(Message msg) {
    		Toast.makeText(MainActivity.this, msg.obj.toString() + "", Toast.LENGTH_SHORT).show();
    	};
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "------------start");
                //把DexLoaderUtil.SECONDARY_DEX_NAME DexLoaderUtil.THIRD_DEX_NAME在assest中的文件写入到apk的私有dex文件夹
                DexLoaderUtil.copyDex(MainActivity.this, DexLoaderUtil.SECONDARY_DEX_NAME);

                String secondDexPath = DexLoaderUtil.getDexPath(MainActivity.this, DexLoaderUtil.SECONDARY_DEX_NAME);

                final String optimizedDexOutputPath = DexLoaderUtil.getOptimizedDexPath(MainActivity.this);
                DexLoaderUtil.injectAboveEqualApiLevel14(secondDexPath, optimizedDexOutputPath, null, "com.example.cla.LoadBugClass");
                String bug = DexLoaderUtil.call(getClassLoader());
                Message msg = mHandler.obtainMessage();
                msg.obj = bug;
				mHandler.sendMessage(msg );
                Log.d(TAG, "------------end bug = "+bug);
            }
        }).start();
    }
}

下面是核心类:DexLoadUtil的具体实现:

import android.annotation.SuppressLint;
import android.content.Context;
import android.util.Log;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;

/**
 * Created by bruce on 11/14/15.
 */
public class DexLoaderUtil {
	private static final String TAG = "TAG";
	public static final String SECONDARY_DEX_NAME = "fixdex.dex";
	public static final String THIRD_DEX_NAME = "fixdex2.dex";
	private static final int BUF_SIZE = 8 * 1024;
	/**
	 * 获取在copyDex函数创建的dex私有文件下的dexName文件
	 * @param context
	 * @param dexName
	 * @return
	 */
	public static String getDexPath(Context context, String dexName) {
		return new File(context.getDir("dex", Context.MODE_PRIVATE), dexName).getAbsolutePath();
	}
	/**
	 * 取得可操作的dex地址
	 * @param context
	 * @return
	 */
	public static String getOptimizedDexPath(Context context) {
		return context.getDir("outdex", Context.MODE_PRIVATE).getAbsolutePath();
	}
	/**
	 * ok,其实就是文件的一个读写,将assets目录名称为dexName文件,写到app的私有目录中的dex文件。
	 * @param context
	 * @param dexName 要修复的文件名称
	 */
	public static void copyDex(Context context, String dexName) {
		//创建一个私有的文件 
		File dexInternalStoragePath = new File(context.getDir("dex", Context.MODE_PRIVATE),
				dexName);
		BufferedInputStream bis = null;
		OutputStream dexWriter = null;
		try {
			bis = new BufferedInputStream(context.getAssets().open(dexName));//获取Assets文件对应的dexName名称的文件的输出流
			dexWriter = new BufferedOutputStream(
					new FileOutputStream(dexInternalStoragePath));
			byte[] buf = new byte[BUF_SIZE];
			int len;
			while((len = bis.read(buf, 0, BUF_SIZE)) > 0) {
				dexWriter.write(buf, 0, len);
			}
			dexWriter.close();
			bis.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	/**
	 * @param context
	 * @param dexName
	 */
	public static void loadAndCall(Context context, String dexName) {
		final File dexInternalStoragePath = new File(context.getDir("dex", Context.MODE_PRIVATE), dexName);
		final File optimizedDexOutputPath = context.getDir("outdex", Context.MODE_PRIVATE);

		DexClassLoader cl = new DexClassLoader(dexInternalStoragePath.getAbsolutePath(),
				optimizedDexOutputPath.getAbsolutePath(),
				null,
				context.getClassLoader());
		call(cl);
	}
	/**
	 * 加载对应类对应的方法
	 * @param cl
	 */
	public static String call(ClassLoader cl) {
		Class<?> myClasz = null;
		try {
			myClasz =
					cl.loadClass("com.example.cla.LoadBugClass");
			Object instance = myClasz.getConstructor().newInstance();
			return (String) myClasz.getDeclaredMethod("getBugString").invoke(instance);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		}
		return "";
	}
	/**
	 * 热修复
	 * @param dexPath 要修复的文件路径
	 * @param defaultDexOptPath 优化后的dex文件存放目录,
	 * @param nativeLibPath 目标类中使用的C/C++库的列表,每个目录用File.pathSeparator间隔开; 可以为 null
	 * @param dummyClassName 要修改的类(用于插入到dexElements数组前的类)
	 * @return
	 */
	@SuppressLint("NewApi")
	public static synchronized Boolean injectAboveEqualApiLevel14(
			String dexPath, String defaultDexOptPath, String nativeLibPath, String dummyClassName) {
		Log.i(TAG, "--> injectAboveEqualApiLevel14");
		//根据context拿到PathClassLoader, 前执行类的装载器
		PathClassLoader pathClassLoader = (PathClassLoader) DexLoaderUtil.class.getClassLoader();

		DexClassLoader dexClassLoader = new DexClassLoader(dexPath, defaultDexOptPath, nativeLibPath, pathClassLoader);
		try {
			dexClassLoader.loadClass(dummyClassName);	//利用反射 获取类
			//通过获取PathClassLoader类加载器PathClassLoader的dexElements
			//然后再获取到要进行热修复的DexClassLoader的dexElements进行合并
			Object dexElements = combineArray(
					getDexElements(getPathList(pathClassLoader)),//在调用getDexElements通过pathList取到dexElements对象。
					getDexElements(getPathList(dexClassLoader)));
			//然后通过getPathList(pathClassLoader),拿到PathClassLoader中的pathList对象,
			Object pathList = getPathList(pathClassLoader);
			setField(pathList, pathList.getClass(), "dexElements", dexElements);
		} catch (Throwable e) {
			e.printStackTrace();
			return false;
		}
		Log.i(TAG, "<-- injectAboveEqualApiLevel14 End.");
		return true;
	}
	/**
	 * 获取 BaseDexClassLoader中的pathList对象
	 * @param baseDexClassLoader PathClassLoader对象
	 * @return 
	 * @throws IllegalArgumentException
	 * @throws NoSuchFieldException
	 * @throws IllegalAccessException
	 * @throws ClassNotFoundException
	 */
	private static Object getPathList(Object baseDexClassLoader)
			throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
		return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
	}

	/**
	 * 获取pathList对象中的DexFile的集合dexElements
	 * 	在BaseDexClassLoader中有个pathList对象,pathList中包含一个DexFile的集合dexElements
	 * 通过pathList对象可以获取到DexFile的集合dexElements
	 * @param paramObject
	 * @return
	 * @throws IllegalArgumentException
	 * @throws NoSuchFieldException
	 * @throws IllegalAccessException
	 */
	private static Object getDexElements(Object paramObject)
			throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException {
		return getField(paramObject, paramObject.getClass(), "dexElements");
	}

	/**
	 * 获取对应类所对应的字段 在这是获取类 dalvik.system.BaseDexClassLoader的pathList对象 也就是获取 BaseDexClassLoader中的pathList对象
	 * @param obj	
	 * @param cl 类
	 * @param field 对象名称
	 * @return
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	private 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);
	}

	/**
	 * 把合并成功的 dexElements数组替换掉原本的dexElements数组 完成热修复
	 * @param obj pathList对象
	 * @param cl PathClassLoader类
	 * @param field	要替换的对象字段
	 * @param value 要替换的值
	 * @throws NoSuchFieldException
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	private 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);
	}
	/**
	 * 通过获取PathClassLoader类加载器PathClassLoader的dexElements
		然后再获取到要进行热修复的DexClassLoader的dexElements进行合并合并完成后,将新的数组通过反射的方式设置给pathList.
	 * @param arrayLhs PathClassLoader的dexElements
	 * @param arrayRhs DexClassLoader的dexElements
	 * @return
	 */
	private static Object combineArray(Object arrayLhs, Object arrayRhs) {
		Class<?> componentType = arrayRhs.getClass().getComponentType();
		int length = Array.getLength(arrayRhs);
		int length2 = Array.getLength(arrayLhs) + length;
		Object newInstance = Array.newInstance(componentType, length2);
		for (int i = 0; i < length2; i++){
			if (i < length){
				Array.set(newInstance, i, Array.get(arrayRhs, i));
			} else{
				Array.set(newInstance, i, Array.get(arrayLhs, i - length));
			}
		}
		return newInstance;
	}
}

效果图:     


demo

发布了34 篇原创文章 · 获赞 30 · 访问量 20万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章