Android性能优化之热修复

热修复分为两种:

  • 阿里系:从底层C的二进制来入手的。
  • 腾讯系:从Java类加载机制来入手的。

什么是热修复?
一般的bug修复,都是等下一个版本解决,然后发布新的apk。
热修复:可以直接在客户已经安装的app当中修复bug。

本文采用Java类加载机制来实现热修复

实现原理:Android的类加载器在加载一个类时会先从自身的DexPathList对象种的Element数组种获取(Element[]dexElements)对应的类。在循环中,首先遍历出来的是dex文件,然后再从dex文件中获取class。因此,我们只要让修复好的class打包成一个dex文件,放在Element数组的第一个元素,这样就能保证获取到的class是最新修复好的class了(当然,由bug的class也是存在的,不过是放在Elements数组后面,没机会被拿到)

实现步骤:
1.修复好有问题额Java文件
2.将修复好的java文件编译成class文件:可以使用Android studio的rebuild project功能将文件进行编译,然后从build目录下找到对应的class文件
3.使用dex指令打包成dex文件

dx --dex --output=c:\Users\dell\Desktop\dex\classes2.dex c:\Users\dell\Desktop\dex
命令解释:
--output=c:\Users\dell\Desktop\dex\classes2.dex   指定输出路径
c:\Users\dell\Desktop\dex   最后指定去打包哪个目录下面的class字节文件(注意要包括全路径的文件夹否则会报错,也可以有多个class)

4.将打包成的dex文件放在对应的目录下(位置一定要正确)

准备工作:
1.首先我们是要用到一个工具,这个工具就是multidex,这个工具的作用就是每个类生成单独的.class字节码文件
使用multidex就需要先配置gradle,先是要引入依赖,按如下步骤引入:

1.
dependencies {
    compile 'com.android.support:multidex:1.0.1'
}

2.
defaultConfig {
        multiDexEnabled true
    }

3.
buildTypes {
release {
    multiDexKeepFile file('dex.keep')
    def myFile = file('dex.keep')
    println("isFileExists:"+myFile.exists())
    println "dex keep"
    minifyEnabled true
    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}

2.在Application中使用

package com.dex.main;

import com.dn.fixutils.FixDexUtils;

import android.app.Application;
import android.content.Context;
import android.support.multidex.MultiDex;

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

public class MyApplication extends Application{
	@Override
	public void onCreate() {
		// TODO Auto-generated method stub
		super.onCreate();
	}
	@Override
	protected void attachBaseContext(Context base) {
		// TODO Auto-generated method stub
		MultiDex.install(base);
		FixDexUtils.loadFixedDex(base);
		super.attachBaseContext(base);
	}
}

记得在AndroidManifest里面的application标签中引入Application

<application
    ···
    android:name=".MyApplition"
    ··· >
	···
</application>

代码实现:
MainActivity:

package com.dex.main;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

import com.dn.fixutils.FixDexUtils;
import com.dn.fixutils.MyConstants;
import com.dn.test.MyTestClass;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	}

	public void test(View v){
		MyTestClass myTestClass = new MyTestClass();
		myTestClass.testFix(this);
	}
	
	public void fix(View v){
		fixBug();
	}

	private void fixBug() {//注意文件存放的路径
		//目录:/data/data/packageName/odex
		File fileDir = getDir(MyConstants.DEX_DIR,Context.MODE_PRIVATE);
		System.out.println(fileDir.getName());
		//往该目录下面放置我们修复好的dex文件。
		String name = "classes2.dex";
		String filePath = fileDir.getAbsolutePath()+File.separator+name;
		//Toast.makeText(this	,filePath, Toast.LENGTH_SHORT).show();
		File file= new File(filePath);
		if(file.exists()){
			file.delete();
		}
		//搬家:把下载好的在SD卡里面的修复了的classes2.dex搬到应用目录filePath
		InputStream is = null;
		FileOutputStream os = null;
		String filename=Environment.getExternalStorageDirectory().getAbsolutePath()+File.separator+name;
		Toast.makeText(this	,filename, Toast.LENGTH_SHORT).show();
		try {
			is = new FileInputStream(filename);
		//	System.out.println("==========="+filename);
			os = new FileOutputStream(filePath);
			int len = 0;
			byte[] buffer = new byte[1024];
			while ((len=is.read(buffer))!=-1){
				os.write(buffer,0,len);
			}

			File f = new File(filePath);
			Toast.makeText(this	,f.exists()+"", Toast.LENGTH_SHORT).show();
			if(f.exists()){
				Toast.makeText(this	,"dex 重写成功", Toast.LENGTH_SHORT).show();
			}
			//热修复
			FixDexUtils.loadFixedDex(this);

		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

MyTestClass:

package com.dn.test;


import android.content.Context;
import android.widget.Toast;

public class MyTestClass {
	public  void testFix(Context context){
		int i = 10;
		int a = 0;
		Toast.makeText(context, "shit:"+i/a, Toast.LENGTH_SHORT).show();
	}
}

FixDexUtils(热修复工具类,若不想深究,直接用该类即可):

package com.dn.fixutils;

import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashSet;


import android.content.Context;
import android.os.Build;
import android.util.Log;
import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;

public class FixDexUtils {
	private static HashSet<File> loadedDex = new HashSet<File>();
	
	static{
		loadedDex.clear();
	}

	public static void loadFixedDex(Context context){
		if(context == null){
			return ;
		}
		//遍历所有的修复的dex
		File fileDir = context.getDir(MyConstants.DEX_DIR,Context.MODE_PRIVATE);
		File[] listFiles = fileDir.listFiles();
		for(File file:listFiles){
			if(file.getName().startsWith("classes")&&file.getName().endsWith(".dex")){
				loadedDex.add(file);//存入集合
			}
		}
		//dex合并之前的dex
		doDexInject(context,fileDir,loadedDex);
	}

	private static void setField(Object obj,Class<?> cl, String field, Object value) throws Exception {
		Field localField = cl.getDeclaredField(field);
		localField.setAccessible(true);
		localField.set(obj,value);
	}

	private static void doDexInject(final Context appContext, File filesDir,HashSet<File> loadedDex) {
		String optimizeDir = filesDir.getAbsolutePath()+File.separator+"opt_dex";
		File fopt = new File(optimizeDir);
		if(!fopt.exists()){
			fopt.mkdirs();
		}
		//1.加载应用程序的dex
		try {
			PathClassLoader pathLoader = (PathClassLoader) appContext.getClassLoader();

			for (File dex : loadedDex) {
				//2.加载指定的修复的dex文件。
				DexClassLoader classLoader = new DexClassLoader(
						dex.getAbsolutePath(),//String dexPath,
						fopt.getAbsolutePath(),//String optimizedDirectory,
						null,//String libraryPath,
						pathLoader//ClassLoader parent
				);
				//3.合并
				Object dexObj = getPathList(classLoader);
				Object pathObj = getPathList(pathLoader);
				Object mDexElementsList = getDexElements(dexObj);
				Object pathDexElementsList = getDexElements(pathObj);
				//合并完成
				Object dexElements = combineArray(mDexElementsList,pathDexElementsList);
				//重写给PathList里面的lement[] dexElements;赋值
				Object pathList = getPathList(pathLoader);
				setField(pathList,pathList.getClass(),"dexElements",dexElements);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
    }

	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);
	}
	private static Object getPathList(Object baseDexClassLoader) throws Exception {
			return getField(baseDexClassLoader,Class.forName("dalvik.system.BaseDexClassLoader"),"pathList");
	}

	private static Object getDexElements(Object obj) throws Exception {
			return getField(obj,obj.getClass(),"dexElements");
	}

	/**
	 * 两个数组合并
	 * @param arrayLhs
	 * @param arrayRhs
     * @return
     */
	private static Object combineArray(Object arrayLhs, Object arrayRhs) {
		Class<?> localClass = arrayLhs.getClass().getComponentType();
		int i = Array.getLength(arrayLhs);
		int j = i + Array.getLength(arrayRhs);
		Object result = Array.newInstance(localClass, j);
		for (int k = 0; k < j; ++k) {
			if (k < i) {
				Array.set(result, k, Array.get(arrayLhs, k));
			} else {
				Array.set(result, k, Array.get(arrayRhs, k - i));
			}
		}
		return result;
	}
}

MyConstants:

package com.dn.fixutils;

public class MyConstants {
	public static final String DEX_DIR = "odex";
}
发布了61 篇原创文章 · 获赞 0 · 访问量 886

猜你喜欢

转载自blog.csdn.net/qq_36828822/article/details/103745545
今日推荐