Android—热修复实践
1.編譯class文件
先看下自己的javac版本
如果是用javac1.6以上的版本編譯的class文件會報錯(原因未知)。
所以需要在app的gradle下android{}中添加命令行。
compileOptions {sourceCompatibility JavaVersion.VERSION_1_6targetCompatibility JavaVersion.VERSION_1_6 }意思是用1.6版本的javac進行編譯。
?因為1.8才支持Lambda表達式,所以需要改回來。
點Make?Project
?
?將整個包路徑還有修復好的class文件復制下來。
2.class轉dex
我們把自己需要修復的java文件通過AS編譯成class文件之后
?再用sdk目錄下的dx.bat工具將class文件轉成dex文件。
打開cmd,如果你設置了環境變量,可以直接在c盤調用語句,不然你就需要把路徑切換到跟dx.bat一樣。
比如我上面就是cd?C:\Users\*****\AppData\Local\Android\Sdk\build-tools\30.0.1
把你的包放到cmd所對應的路徑下,如果配置了環境變量就可以直接把包放到桌面。
?
?回車后?
dx --dex –output=com\example\hotfixdemo\classes2.dex com\example\hotfixdemo\MainActivity.class
該命令前面對應的是生成的dex文件放置路徑+文件名,后面的就是class文件所對應的路徑。
如果你放在桌面,默認的cmd路徑是沒有附帶Desktop的,需要再后面的路徑上加上Desktop\。
3.編寫熱修復工具類
package com.example.hotfixdemo;import android.content.Context;import java.io.File; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.util.HashSet;import dalvik.system.DexClassLoader; import dalvik.system.PathClassLoader;public class FixDexUtils {private static final String DEX_SUFFIX = ".dex";private static final String APK_SUFFIX = ".apk";private static final String JAR_SUFFIX = ".jar";private static final String ZIP_SUFFIX = ".zip";private static final HashSet<File> loadedDex = new HashSet<File>();/*** 加載補丁,使用默認目錄:data/data/包名/files/odex** @param context*/public static void loadFixedDex(Context context) {loadFixedDex(context, null);}/*** 加載補丁** @param context 上下文* @param patchFilesDir 補丁所在目錄*/public static void loadFixedDex(Context context, File patchFilesDir) {if (context == null) {return;}// 遍歷所有的修復dexFile fileDir = patchFilesDir != null ? patchFilesDir : new File(context.getExternalCacheDir().getAbsolutePath());// data/data/包名/cache(這個可以任意位置)File[] listFiles = fileDir.listFiles();for (File file : listFiles) {if (file.getName().startsWith("classes") &&(file.getName().endsWith(DEX_SUFFIX)|| file.getName().endsWith(APK_SUFFIX)|| file.getName().endsWith(JAR_SUFFIX)|| file.getName().endsWith(ZIP_SUFFIX))) {loadedDex.add(file);// 存入集合}}// dex合并之前的dexdoDexInject(context);}private static void doDexInject(Context appContext) {String optimizeDir = appContext.getFilesDir().getAbsolutePath();// data/data/包名/files (這個必須是自己程序下的目錄)File fopt = new File(optimizeDir);if (!fopt.exists()) {fopt.mkdirs();}try {// 1.加載應用程序的dexPathClassLoader pathLoader = (PathClassLoader) appContext.getClassLoader();for (File dex : FixDexUtils.loadedDex) {// 2.加載指定的修復的dex文件DexClassLoader dexLoader = new DexClassLoader(dex.getAbsolutePath(),// 修復好的dex(補丁)所在目錄fopt.getAbsolutePath(),// 存放dex的解壓目錄(用于jar、zip、apk格式的補丁)null,// 加載dex時需要的庫pathLoader// 父類加載器);// 3.合并Object dexPathList = getPathList(dexLoader);Object pathPathList = getPathList(pathLoader);Object leftDexElements = getDexElements(dexPathList);Object rightDexElements = getDexElements(pathPathList);// 合并完成Object dexElements = combineArray(leftDexElements, rightDexElements);// 重寫給PathList里面的Element[] dexElements;賦值Object pathList = getPathList(pathLoader);// 一定要重新獲取,不要用pathPathList,會報錯setField(pathList, pathList.getClass(), dexElements);}} catch (Exception e) {e.printStackTrace();}}/*** 反射給對象中的屬性重新賦值*/private static void setField(Object obj, Class<?> cl, Object value) throws NoSuchFieldException, IllegalAccessException {Field declaredField = cl.getDeclaredField("dexElements");declaredField.setAccessible(true);declaredField.set(obj, value);}/*** 反射得到對象中的屬性值*/private static Object getField(Object obj, Class<?> cl, String field) throws NoSuchFieldException, IllegalAccessException {Field localField = cl.getDeclaredField(field);localField.setAccessible(true);return localField.get(obj);}/*** 反射得到類加載器中的pathList對象*/private static Object getPathList(Object baseDexClassLoader) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");}/*** 反射得到pathList中的dexElements*/private static Object getDexElements(Object pathList) throws NoSuchFieldException, IllegalAccessException {return getField(pathList, pathList.getClass(), "dexElements");}/*** 數組合并*/private static Object combineArray(Object left, Object right) {Class<?> componentType = left.getClass().getComponentType();int i = Array.getLength(left);// 得到左數組長度(補丁數組)int j = Array.getLength(right);// 得到原dex數組長度int k = i + j;// 得到總數組長度(補丁數組+原dex數組)Object result = Array.newInstance(componentType, k);// 創建一個類型為componentType,長度為k的新數組System.arraycopy(left, 0, result, 0, i);System.arraycopy(right, 0, result, i, j);return result;} }?4.測試
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);TextView tv1 = findViewById(R.id.tv1);TextView tv2 = findViewById(R.id.tv2);tv1.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {FixDexUtils.loadFixedDex(MainActivity.this);}});tv2.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(MainActivity.this, Text.getString(),Toast.LENGTH_SHORT).show();}});} }直接看代碼注釋,這里我們把修復好的文件放在了外部存儲的緩存文件夾中,我們可以調試手機直接把文件放進去
?
加載到我們的dex文件后他會存放到我們指定的另一個內部存儲文件夾中。
我們主要修復的是Test類。
修復前:
public class Text {public static String getString(){return "出錯了!";} }修復后:
public class Text {public static String getString(){return "修復了";} }這里需要注意你所修復的類被加載的時機,如果你這里修復的是MainActivity,該activity已經被加載了你再去修復是沒有用的,因為該類已經被加載成一個對象存在內存中。這里我們是點了tv2才會去加載Test類。
類被加載時機:
如果我們先點擊tv1再點tv2此時就會顯示修復了,如果先點tv2則一直顯示是出錯了。
總結
以上是生活随笔為你收集整理的Android—热修复实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android—AspectJ实践
- 下一篇: 怎样让手中的钱成为生财工具