性能優化16_熱更新(編輯中)

Android性能優化彙總
熱修復

  1. 阿里系:DeXposed。andfix
    從底層C的二進制來入手的。

  2. 騰訊系:tinker
    Java類加載機制來入手的。

一 什麼是熱修復?

一般的bug修復,都是等下一個版本解決,然後發佈新的apk。
熱修復: 可以直接在客戶已經安裝的程序當中修復bug。bug一般會出現在某個類的某個方法地方。我們需要動態地將客戶手機裏面的apk裏面的某個類給替換成我們已經修復好的類。

二 原理

  • java的類加載機制:
    安卓系統通過classLoader加載dex文件。
public class PathClassLoader extends BaseDexClassLoader {
用來加載應用程序的dex

public class DexClassLoader extends BaseDexClassLoader {
可以加載指定的某個dex文件。(限制:必須要在應用程序的目錄下面)
  • 系統通過BaseDexClassLoader類 中的DexPathList,再通過DexPathList類中的Element[] dexElements,找到對應的第一個java類,從而進行類的加載;所以,我們只需把我們修改後的dex文件元素利用反射技術,插入到dexElements數組的中去,並在數組的前面。這樣,程序在運行時,會先拿到我們修復的類,運行無異常,從而實現了修復

  • 安卓java文件最終打包生產dex文件,我們修改bug後的java文件要編譯成dex文件,然後去“替換”已經存在的dex文件中對應的那個類

  • 通過tools文件夾下工具可以將java文件打包生產dex文件

三 實現方式

  • 修改後的java文件,編譯生成dex文件
  • io流讀入文件到應用中
  • classLoader加載文件獲取加載的dex文件中對應的元素
  • 將加載進來的元素合併到已經存在的DexPathList的Element[] dexElements 中去

四 主要的dex文件加載代碼

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;
	}
//	[12345] [9876]
//	[9876  12345]

}

五 使用

1 Activity中報錯

MyTestClass 報錯了,a= 0,a不能作爲分母

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

Activity中使用了MyTestClass

public class Lsn16Activity extends AppCompatActivity {

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

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

2 定義一個修復的方法

public void fix(View v) {
        //目錄:/data/data/packageName/odex
        File fileDir = getDir(MyConstants.DEX_DIR, Context.MODE_PRIVATE);
        //往該目錄下面放置我們修復好的dex文件。
        String name = "classes2.dex";
        String filePath = fileDir.getAbsolutePath() + File.separator + name;
        File file = new File(filePath);
        if (file.exists()) {
            file.delete();
        }
        //搬家:把下載好的在SD卡里面的修復了的classes2.dex搬到應用目錄filePath
        InputStream is = null;
        FileOutputStream os = null;
        try {
            is = new FileInputStream(Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + name);
            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);
            if(f.exists()){
                Toast.makeText(this	,"dex 重寫成功", Toast.LENGTH_SHORT).show();
            }
            //熱修復
            FixDexUtils.loadFixedDex(this);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

3 修改報錯的地方

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

4 編譯MyTestClass.class 生成dex文件

5 加載dex文件

實際運用中通過網絡請求,從服務器中獲取dex文件,這裏用點擊事件fix()方法代替

6 點擊修復,重新運行,發現bug修復了

六 Demo

Lsn16Activity

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章