Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android熱修復學習(HotFix)

Android熱修復學習(HotFix)

編輯:關於Android編程

一:熱修復相關

熱修復概念: 以補丁的方式動態修復緊急Bug,不再需要重新發布App,不再需要用戶重新下載。

PathClassloader和DexClassLoader:

(1)PathClassloader作為其系統類和應用類的加載器,只能去加載已經安裝到Android系統中的apk文件。

(2)DexClassLoader可以用來從.jar和.apk類型的文件內部加載classes.dex文件。可以用來執行非安裝的程序代碼。

(3)Android使用PathClassLoader作為其類加載器,DexClassLoader可以從.jar和.apk類型的文件內部加載classes.dex文件。

熱修復原理:

PathClassLoader和DexClassLoader都繼承自BaseDexClassLoader

在BaseDexClassLoader中有如下源碼:

#BaseDexClassLoader
@Override
protected Class findClass(String name) throws ClassNotFoundException {
    Class clazz = pathList.findClass(name);

    if (clazz == null) {
        throw new ClassNotFoundException(name);
    }

    return clazz;
}

#DexPathList
public Class findClass(String name) {
    for (Element element : dexElements) {
        DexFile dex = element.dexFile;

        if (dex != null) {
            Class clazz = dex.loadClassBinaryName(name, definingContext);
            if (clazz != null) {
                return clazz;
            }
        }
    }

    return null;
}

#DexFile
public Class loadClassBinaryName(String name, ClassLoader loader) {
    return defineClass(name, loader, mCookie);
}
private native static Class defineClass(String name, ClassLoader loader, int cookie);1  n j                                                                                                                         
n 

1 BaseDexClassLoader中有個pathList對象,pathList中包含一個DexFile的集合dexElements,而對於類加載呢,就是遍歷這個集合,通過DexFile去尋找。

2 一個ClassLoader可以包含多個dex文件,每個dex文件是一個Element,多個dex文件排列成一個有序的數組dexElements,當找類的時候,會按順序遍歷dex文件,然後從當前遍歷的dex文件中找類,如果找類則返回,如果找不到從下一個dex文件繼續查找。

3 理論上,如果在不同的dex中有相同的類存在,那麼會優先選擇排在前面的dex文件的類.

4 把有問題的類打包到一個dex(patch.dex)中去,然後把這個dex插入到Elements的最前面.

二:阻止相關類打上CLASS_ISPREVERIFIED標志

dex校驗: 如果兩個相關聯的類在不同的dex中就會報錯,例如ClassA 引用了ClassB,但是發現這這兩個類所在的dex不在一起,其中:

1. ClassA 在classes.dex中

2. ClassB 在patch.dex中

結果發生了錯誤。

dex校驗的前提: 如果引用者這個類被打上了CLASS_ISPREVERIFIED標志,那麼就會進行dex的校驗。

相關類打上CLASS_ISPREVERIFIED標志的發生場景:

在虛擬機啟動的時候,當verify選項被打開的時候,如果static方法、private方法、構造函數等,其中的直接引用(第一層關系)到的類都在同一個dex文件中,那麼這個類就會被打上CLASS_ISPREVERIFIED

其中AntilazyLoad類會被打包成單獨的hack.dex,這樣當安裝apk的時候,classes.dex內的類都會引用一個在不相同dex中的AntilazyLoad類,這樣就防止了類被打上CLASS_ISPREVERIFIED的標志了,只要沒被打上這個標志的類都可以進行打補丁操作。

在class文件中插入代碼來阻止相關類打上CLASS_ISPREVERIFIED標志:在dx工具執行之前,將LoadBugClass.class文件呢,進行修改,再其構造中添加System.out.println(dodola.hackdex.AntilazyLoad.class),然後繼續打包的流程。

原始代碼

package dodola.hackdex;
public class AntilazyLoad
{

}

package dodola.hotfix;
public class BugClass
{
    public String bug()
    {
        return "bug class";
    }
}

package dodola.hotfix;
public class LoadBugClass
{
    public String getBugString()
    {
        BugClass bugClass = new BugClass();
        return bugClass.bug();
    }
}

三:插入jar

插入代碼

System.out.println(dodola.hackdex.AntilazyLoad.class)

在構造函數中插入操作代碼(javassist)

package test;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;

public class InjectHack
{
    public static void main(String[] args)
    {
        try
        {
            String path = "/Users/zhy/develop_work/eclipse_android/imooc/JavassistTest/";
            ClassPool classes = ClassPool.getDefault();
            classes.appendClassPath(path + "bin");//項目的bin目錄即可
            CtClass c = classes.get("dodola.hotfix.LoadBugClass");
            CtConstructor ctConstructor = c.getConstructors()[0];
            ctConstructor
                    .insertAfter("System.out.println(dodola.hackdex.AntilazyLoad.class);");
            c.writeFile(path + "/output");
        } catch (Exception e)
        {
            e.printStackTrace();
        }

    }
}

把AntilazyLoad.class打包成jar包,然後寫入App的私有目錄,最後把該jar對應的dexElements文件插入到數組的最前面。

public class HotfixApplication extends Application
{

    @Override
    public void onCreate()
    {
        super.onCreate();
       //創建jar對應的文件
        File dexPath = new File(getDir("dex", Context.MODE_PRIVATE), "hackdex_dex.jar");
       //將asset文件中的jar寫到App的私有目錄下面。
        Utils.prepareDex(this.getApplicationContext(), dexPath, "hackdex_dex.jar");
       // 把jar對應的dexElements插入到dex數組最前面。
        HotFix.patch(this, dexPath.getAbsolutePath(), "dodola.hackdex.AntilazyLoad");
        try
        {
            this.getClassLoader().loadClass("dodola.hackdex.AntilazyLoad");
        } catch (ClassNotFoundException e)
        {
            e.printStackTrace();
        }

    }
}

創建jar對應的文件

public class Utils {
    private static final int BUF_SIZE = 2048;

    public static boolean prepareDex(Context context, File dexInternalStoragePath, String dex_file) {
        BufferedInputStream bis = null;
        OutputStream dexWriter = null;
        bis = new BufferedInputStream(context.getAssets().open(dex_file));
        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();
        return true;

}

找相應的ClassLoader進行操作

public final class HotFix
{
    public static void patch(Context context, String patchDexFile, String patchClassName)
    {
        if (patchDexFile != null && new File(patchDexFile).exists())
        {
            try
            {
                if (hasLexClassLoader())
                {
                    injectInAliyunOs(context, patchDexFile, patchClassName);
                } else if (hasDexClassLoader())
                {
                    injectAboveEqualApiLevel14(context, patchDexFile, patchClassName);
                } else
                {

                    injectBelowApiLevel14(context, patchDexFile, patchClassName);

                }
            } catch (Throwable th)
            {
            }
        }
    }
 }

Combine(合並)App的DexElements和*AntilazyLoad.class的DexElements*

 private static boolean hasDexClassLoader()
{
    try
    {
        Class.forName("dalvik.system.BaseDexClassLoader");
        return true;
    } catch (ClassNotFoundException e)
    {
        return false;
    }
}


 private static void injectAboveEqualApiLevel14(Context context, String str, String str2)
            throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException
{
    PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();
    Object a = combineArray(getDexElements(getPathList(pathClassLoader)),
            getDexElements(getPathList(
                    new DexClassLoader(str, context.getDir("dex", 0).getAbsolutePath(), str, context.getClassLoader()))));
    Object a2 = getPathList(pathClassLoader);
    setField(a2, a2.getClass(), "dexElements", a);
    pathClassLoader.loadClass(str2);
}

將Patch.jar補丁插入到APP中,過程和插入AntilazyLoad.class一樣

public class HotfixApplication extends Application
{

    @Override
    public void onCreate()
    {
        super.onCreate();
        File dexPath = new File(getDir("dex", Context.MODE_PRIVATE), "hackdex_dex.jar");
        Utils.prepareDex(this.getApplicationContext(), dexPath, "hack_dex.jar");
        HotFix.patch(this, dexPath.getAbsolutePath(), "dodola.hackdex.AntilazyLoad");
        try
        {
            this.getClassLoader().loadClass("dodola.hackdex.AntilazyLoad");
        } catch (ClassNotFoundException e)
        {
            e.printStackTrace();
        }

        dexPath = new File(getDir("dex", Context.MODE_PRIVATE), "path_dex.jar");
        Utils.prepareDex(this.getApplicationContext(), dexPath, "path_dex.jar");
        HotFix.patch(this, dexPath.getAbsolutePath(), "dodola.hotfix.BugClass");

    }
}

四:總結

(1)因為我們的Patch是以獨立的jar包,插入到APP的DexElements中,

所以如果APP中中的類引用了Patch中的類,就會在校驗時報錯。因為當進行dex校驗時,如果兩個相關聯的類在不同的dex中就會報錯。(LoadBugClass引用BugClass)

(2) 為了防止上述錯誤就要阻止Dex校驗,阻止Dex校驗的方法是阻止相關類打上CLASS_ISPREVERIFIED標志。

(3) 阻止相關類打上CLASS_ISPREVERIFIED標志的做法是:在相關引用的類(LoadBugClass.class)的構造方法中,引用另外一個jar中類AntilazyLoad.class

(4)因為AntilazyLoad.class在另一個jar中,所以需要把該jar對應的dex插入到App中。並且在Application中的onCreate()方法中將該類加載進來。

(5)把Patch.jar對應的dexElement加載進App中。因為Patch.jar放在dex數組的第一個位置,所以首先被加載。即:如果在不同的dex中有相同的類存在,那麼會優先選擇排在前面的dex文件的類。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved