動態加載與熱修復知識

是很久之前的技術了,之前也寫過一篇類似的,有了一些新的理解看法,就寫的詳細一點。

學習這個之前,需要了解一下class文件到底是個啥。許多人可能寫了這麼多年java,但是根本就不知道.class文件是怎麼組成的。大部分時候實現功能也用不上知道。所以很多時候你都只能浮於表面

.Class文件

因爲JVM是加載.class文件,使得JVM的可移植性很強,因爲它只管加載.class文件就行了,無需管你是什麼語言編譯出來的.class。

.class文件是由無符號數與表組成:

無符號數:屬於基本的數據類型,以u1、u2、u4、u8來分別代表1個字節、2個字節、4個字節和8個字節的無符號數。

表:表是由多個無符號數或者其他表作爲數據項構成的複合數據類型,class文件中所有的表都以“_info”結尾。

那表是什麼東西呢,首先要先知道.class文件的組成結構

魔數是一個4個字節的固定值,JVM用來校驗你這個文件是不是.class文件。這裏只着重說一下常量池。

常量池有18種TAG用來表示表的類型

我只拿出幾個比較用的多的類型舉例。尤其說下String類型這張表。它是由1個字節的TAG以及2個字節的地址下標組成。

會指向一個UTF8_info類型的表。UTF8_info由1個字節的TAG,2個字節長度的length以及一個bytes數組組成。bytes就是存放的char值,長度就是length。

常量池後面的類型也是由各種表組成,感興趣的朋友可以自行查閱相關文檔。

我們的android虛擬機是一個特殊的JVM,JVM是用來加載.class文件,基於棧結構,Dalvik是加載.dex文件,是寄存器結構。

.dex文件對.class文件進行了優化,去除一些冗餘的字段,而且字節碼上也有進行優化。

類加載器

BootClassLoader:Android系統啓動時會使用BootClassLoader來預加載常用類。

PathClassLoader:8.0之後和DexClassLoader沒啥區別。用來加載Dex與Apk文件。

重要的是類加載的機制。

雙親委派機制

1、防止重複加載同一個.class。通過委託去向上面問一問,加載過了,就不用再加載一遍。保證數據安全。

2、保證核心.class不能被篡改。通過委託方式,不會去篡改核心.clas,即使篡改也不會去加載,即使加載也不會是同一個.class對象了。不同的加載器加載同一個.class也不是同一個Class對象。這樣保證了Class執行安全。

最簡單的熱修復方式就是藉助這個機制。讓被修復的類在後面加載,修復的類先被加載。5.0之後就不會有CLASS_ISPREVERIFIED標誌了。所以只要你最小版本號高於5.0就無需去進行插樁,需要學習插樁看我另外一篇博客。

實現

public class HotFixUtil {
    private static HotFixUtil hotFixUtil = new HotFixUtil();
    private DexClassLoader dexClassLoader;

    public static HotFixUtil getInstance() {
        return hotFixUtil;
    }

    /**
     * 加載修復的dex
     * @param context
     * @param name
     */
    public void loadDex(Context context, String name) {
        File fileDir = context.getDir("dex", Context.MODE_PRIVATE);
        String dexPath = fileDir.getAbsoluteFile() + File.separator + name;
        File dexFile = new File(dexPath);
        if (!dexFile.exists()) {
            try {
                dexFile.createNewFile();
                copyFiles(context, name, dexFile);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //獲取dexElements
    public void initDex(Context context) {
        File fileDir=context.getDir("dex", Context.MODE_PRIVATE);
        List<File> listDex = getList(fileDir);
        try {
            //獲取pathList
            Field pathListField = getField(context.getClassLoader(), "pathList");
            Object pathList = pathListField.get(context.getClassLoader());
            //獲取老的dexElements
            Field dexElementsField = getField(pathList, "dexElements");
            Object[] oldDexElements = (Object[]) dexElementsField.get(pathList);
            //獲取 makeDexElements 方法
            Method makeDexElements = getMethod(pathList, "makeDexElements", List.class, File.class,
                    List.class, ClassLoader.class);
            //裝載fix.dex
            ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
            Object[] myDexElements = (Object[]) makeDexElements.invoke(pathList, listDex, fileDir, suppressedExceptions, context.getClassLoader());

            //合併
            Object[] newDexElements = (Object[]) Array.newInstance(oldDexElements.getClass().getComponentType(), oldDexElements.length + myDexElements.length);
            System.arraycopy(myDexElements,0,newDexElements,0,myDexElements.length);
            System.arraycopy(oldDexElements,0,newDexElements,myDexElements.length,oldDexElements.length);

            //設置
            dexElementsField.set(pathList,newDexElements);
            Toast.makeText(context,"裝載完成",Toast.LENGTH_SHORT).show();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private List<File> getList(File fileDir){
        File[] files = fileDir.listFiles();
        List<File> list=new ArrayList<>();
        for (File file : files) {
            if(file.getName().endsWith(".dex")){
                list.add(file);
            }
        }
        return list;
    }


    public DexClassLoader getDexClassLoader() {
        return dexClassLoader;
    }

    public static Field getField(Object object, String name) throws NoSuchFieldException {
        Class<?> aClass = object.getClass();
        while (aClass != null) {
            try {
                Field declaredField = aClass.getDeclaredField(name);
                if (!declaredField.isAccessible()) {
                    declaredField.setAccessible(true);
                }
                return declaredField;
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
                aClass=aClass.getSuperclass();
            }
        }
        throw new NoSuchFieldException("找不到字段");
    }

    public static Method getMethod(Object object, String name,Class... value) throws NoSuchMethodException {
        Class<?> aClass = object.getClass();
        while (aClass != null) {
            try {
                Method declaredMethod = aClass.getDeclaredMethod(name, value);
                if (!declaredMethod.isAccessible()) {
                    declaredMethod.setAccessible(true);
                }
                return declaredMethod;
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
                aClass=aClass.getSuperclass();
            }
        }
        throw new NoSuchMethodException("找不到方法");
    }

    public static void copyFiles(Context context, String fileName, File desFile) {
        InputStream in = null;
        OutputStream out = null;

        try {
            in = context.getAssets().open(fileName);
            out = new FileOutputStream(desFile.getAbsolutePath());
            byte[] bytes = new byte[1024];
            int len = 0;
            while ((len = in.read(bytes)) != -1)
                out.write(bytes, 0, len);
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null)
                    in.close();
                if (out != null)
                    out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

註釋比較清晰了,代碼也在git上,需要的自行下載吧~

代碼地址:https://github.com/VjayYi/HotFixDemo

博客有借鑑一些其他博客內容,圖片PPT均爲自己所畫。

 

 

 

.

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