Android熱修復實現(xiàn)原理以及方法

現(xiàn)在主要由兩大方法
1.阿里AndFix畜疾,主要是采用Ndk實現(xiàn)對方法指針的替換
2.騰訊Tinker
現(xiàn)在主要說的是tinker的實現(xiàn)方法:
一. 首先介紹下兩個概念:
public class PathClassLoader extends BaseDexClassLoader {
用來加載已安裝應用程序的dex

public class DexClassLoader extends BaseDexClassLoader {
可以加載指定的某個dex文件聚蝶。(限制:必須要在應用程序的目錄下面抬驴,所以下載下來需要復制到本目錄里)

二. 很明顯我們需要使用DexClassLoader來實現(xiàn)需求

public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);
        this.originalPath = dexPath;
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }

上面創(chuàng)建了一個DexPathList

    public DexPathList(ClassLoader definingContext, String dexPath,
            String libraryPath, File optimizedDirectory) {
        ……
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory);
    }

    private static Element[] makeDexElements(ArrayList<File> files,
            File optimizedDirectory) {
        ArrayList<Element> elements = new ArrayList<Element>();
        for (File file : files) {
            ZipFile zip = null;
            DexFile dex = null;
            String name = file.getName();
            if (name.endsWith(DEX_SUFFIX)) {
                dex = loadDexFile(file, optimizedDirectory);
            } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
                    || name.endsWith(ZIP_SUFFIX)) {
                zip = new ZipFile(file);
            }
            ……
            if ((zip != null) || (dex != null)) {
                elements.add(new Element(file, zip, dex));
            }
        }
        return elements.toArray(new Element[elements.size()]);
    }

    private static DexFile loadDexFile(File file, File optimizedDirectory)
            throws IOException {
        if (optimizedDirectory == null) {
            return new DexFile(file);
        } else {
            String optimizedPath = optimizedPathFor(file, optimizedDirectory);
            return DexFile.loadDex(file.getPath(), optimizedPath, 0);
        }
    }

    /**
     * Converts a dex/jar file path and an output directory to an
     * output file path for an associated optimized dex file.
     */
    private static String optimizedPathFor(File path,
            File optimizedDirectory) {
        String fileName = path.getName();
        if (!fileName.endsWith(DEX_SUFFIX)) {
            int lastDot = fileName.lastIndexOf(".");
            if (lastDot < 0) {
                fileName += DEX_SUFFIX;
            } else {
                StringBuilder sb = new StringBuilder(lastDot + 4);
                sb.append(fileName, 0, lastDot);
                sb.append(DEX_SUFFIX);
                fileName = sb.toString();
            }
        }
        File result = new File(optimizedDirectory, fileName);
        return result.getPath();
    }
optimizedDirectory是用來緩存我們需要加載的dex文件的麦轰,并創(chuàng)建一個DexFile對象递鹉,如果它為null,那么會直接使用dex文件原有的路徑來創(chuàng)建DexFile
對象休玩。

加載類的過程:
ClassLoader通過loadClass來加載需要的類

 public Class<?> loadClass(String className) throws ClassNotFoundException {
        return loadClass(className, false);
    }

    protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
        Class<?> clazz = findLoadedClass(className);
        if (clazz == null) {
            ClassNotFoundException suppressed = null;
            try {
                clazz = parent.loadClass(className, false);
            } catch (ClassNotFoundException e) {
                suppressed = e;
            }

            if (clazz == null) {
                try {
                    clazz = findClass(className);
                } catch (ClassNotFoundException e) {
                    e.addSuppressed(suppressed);
                    throw e;
                }
            }
        }
        return clazz;
    }

最后還是調用的DexPathList的findClass

    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;
    }

從上面代碼看出如果element里面找到了class,for循環(huán)就會退出
所以我們只要修改dexElements這個集合就行了劫狠,把我們創(chuàng)建的dexElements跟原始的dexElements合并拴疤。
dexA:原app的dex文件
dexB:補丁dex文件,可以是多個
1.先把下載下來的dex文件用file形式保存到一個集合中

        //遍歷所有的修復的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);//存入集合
            }
        }

2.使用反射獲取dexA,dexB的classLoader独泞,dexA的classLoader使用PathClassLoader獲取呐矾,dexB的classLoader由dexClassLoader獲取

            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.通過classloader獲取dexA和dexB的pathList

                Object dexObj = getPathList(classLoader);
                Object pathObj = getPathList(pathLoader);

4.通過pathList獲取dexA和dexB的dexElements阐肤,并把這兩個合并

                Object mDexElementsList = getDexElements(dexObj);
                Object pathDexElementsList = getDexElements(pathObj);
                //合并完成
                Object dexElements = combineArray(mDexElementsList,pathDexElementsList);

5.把合并之后的dexElements賦值給dexA的classLoader的pathList中的dexElements

                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);

那么具體步驟就已經完成了凫佛。

6.怎么才能打出一個dex包?
通過dx.bat這個系統(tǒng)提供的工具就可以

1.找到MyTestClass.class
    com\app\build\intermediates\bin\MyTestClass.class
2.配置dx.bat的環(huán)境變量
    Android\sdk\build-tools\23.0.3\dx.bat
3.命令
dx --dex --output=D:\Users\jiang\Desktop\dex\classes2.dex D:\Users\jiang\Desktop\dex
命令解釋:
    --output=D:\Users\jiang\Desktop\dex\classes2.dex   指定輸出路徑
    D:\Users\jiang\Desktop\dex    最后指定去打包哪個目錄下面的class字節(jié)文件(注意要包括全路徑的文件夾孕惜,也可以有多個class)

代碼地址如下:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末愧薛,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子衫画,更是在濱河造成了極大的恐慌毫炉,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件削罩,死亡現(xiàn)場離奇詭異瞄勾,居然都是意外死亡,警方通過查閱死者的電腦和手機弥激,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門进陡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人微服,你說我怎么就攤上這事趾疚。” “怎么了以蕴?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵糙麦,是天一觀的道長。 經常有香客問我丛肮,道長赡磅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任宝与,我火速辦了婚禮焚廊,結果婚禮上冶匹,老公的妹妹穿的比我還像新娘。我一直安慰自己节值,他們只是感情好徙硅,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著搞疗,像睡著了一般嗓蘑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上匿乃,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天桩皿,我揣著相機與錄音,去河邊找鬼幢炸。 笑死泄隔,一個胖子當著我的面吹牛,可吹牛的內容都是我干的宛徊。 我是一名探鬼主播佛嬉,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼炎滞,長吁一口氣:“原來是場噩夢啊……” “哼芳室!你這毒婦竟也來了?” 一聲冷哼從身側響起场躯,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤苞氮,失蹤者是張志新(化名)和其女友劉穎湾揽,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體笼吟,經...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡库物,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了贷帮。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片戚揭。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖撵枢,靈堂內的尸體忽然破棺而出毫目,到底是詐尸還是另有隱情,我是刑警寧澤诲侮,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站箱蟆,受9級特大地震影響沟绪,放射性物質發(fā)生泄漏。R本人自食惡果不足惜空猜,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一绽慈、第九天 我趴在偏房一處隱蔽的房頂上張望恨旱。 院中可真熱鬧,春花似錦坝疼、人聲如沸搜贤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仪芒。三九已至,卻和暖如春耕陷,著一層夾襖步出監(jiān)牢的瞬間掂名,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工哟沫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留饺蔑,地道東北人。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓嗜诀,卻偏偏與公主長得像猾警,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子隆敢,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內容