一步步手动实现热修复(一)-dex文件的生成与加载
*本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家發布
熱修復技術自從QQ空間團隊搞出來之后便漸漸趨于成熟。
我們這個系列主要介紹如何一步步手動實現基本的熱修復功能,無需使用第三方框架。
在開始學習之前,需要對基本的熱修復技術有些了解,以下文章可以幫助到你:
- 安卓App熱補丁動態修復技術介紹
- 【騰訊Bugly干貨分享】Android Patch 方案與持續交付
- Android dex分包方案
- dodola/HotFix
本節課程主要分為3塊:
- 1.一步步手動實現熱修復(一)-dex文件的生成與加載
- 2.一步步手動實現熱修復(二)-類的加載機制簡要介紹
- 3.一步步手動實現熱修復(三)-Class文件的替換
本節示例所用到的任何資源都已開源,項目中包含工程中所用到代碼、示例圖片、說明文檔。項目地址為:
https://code.csdn.net/u011064099/sahadevhotfix/tree/master
dex文件的生成與加載
我們在這部分主要做的流程有:
- 1.編寫基本的Java文件并編譯為.class文件。
- 2.將.class文件轉為.dex文件。
- 3.將轉好的dex文件放入創建好的Android工程內并在啟動時將其寫入本地。
- 4.加載解壓后的.dex文件中的類,并調用其方法進行測試。
Note: 在閱讀本節之前最好先了解一下類加載器的雙親委派原則、DexClassLoader的使用以及反射的知識點。
編寫基本的Java文件并編譯為.class文件
首先我們在一個工程目錄下開始創建并編寫我們的Java文件,你可能會選擇各種IDE來做這件事,但我在這里勸你不要這么做,因為有坑在等你。等把基本流程搞清楚可以再選擇更進階的方法。這里我們可以選擇文本編輯器比如EditPlus來對Java文件進行編輯。
新建一個Java文件,并命名為:ClassStudent.java,并在java文件內鍵入以下代碼:
public class ClassStudent {private String name;public ClassStudent() {}public void setName(String name) {this.name = name;}public String getName(){return this.name + ".Mr"; } }Note: 這里要注意,不要對類添加包名,因為在后期對class文件處理時會遇到問題,具體問題會稍后說明。上面的getName方法在返回時對this.name屬性添加了一段字符串,這里請注意,后面會用到。
在文件創建好之后,對Java文件進行編譯:
將.class文件轉為.dex文件
好,現在我們使用class文件生成對應的dex文件。生成dex文件所需要的工具為dx,dx工具位于sdk的build-tools文件夾內,如下圖所示:
Tips: 為了方便使用,建議將dx的路徑添加到環境變量中。如果對dx工具不熟悉的,可以在終端中輸入dx –help以獲取幫助。
dx工具的基本用法是:
dx --dex [--output=<file>] [<file>.class | <file>.{zip,jar,apk} | <directory>]Tips: 剛開始自己摸索的時候,就沒有仔細看命令,導致后面兩個參數的順序顛倒了,搞出了一些讓人疑惑難解的問題,最后又不得不去找dx工具的源碼調試,最后才發現自己的問題在哪。如果有對dx工具感興趣的,可以對dx的包進行反編譯或者獲取dx的相關源代碼進行了解。dx.lib文件位于dx.bat的下級目錄lib文件夾中,可以使用JD-GUI工具對其進行查看或導出。如果需要獲取源代碼的,請使用以下命令進行克隆:
git clone https://android.googlesource.com/platform/dalvik
我們使用以下命令生成dex文件:
dx --dex --output=user.dex ClassStudent.class這里我為了防止出錯,提前在當前目錄下新建好了user.dex文件。上述命令依賴編譯.class文件的JDK版本,如果使用的是JDK8編譯的class會提示以下問題:
PARSE ERROR: unsupported class file version 52.0 ...while parsing ClassStudent.class 1 error; aborting這里的52.0意味著class文件不被支持,需要使用JDK8以下的版本進行編譯,但是dx所需的環境還是需要為JDK8的,這里我編譯class文件使用的是JDK7,請注意。
上面我們提到了為什么先不要在ClassStudent中使用包名,因為在執行dx的時候會報以下異常,這是因為以下第二項條件沒有通過,該代碼位于com.android.dx.cf.direct.DirectClassFile文件內:
String thisClassName = thisClass.getClassType().getClassName();if(!(filePath.endsWith(".class") && filePath.startsWith(thisClassName) && (filePath.length()==(thisClassName.length()+6)))){throw new ParseException("class name (" + thisClassName + ") does not match path (" + filePath + ")");}運行截圖如下所示:
好了,到此為止我們的目錄應該如下:
寫入dex到本地磁盤
接下來將生成好的user.dex文件放入Android工程的res\raw文件夾下:
在系統啟動時將其寫入到磁盤,這里不再貼出具體的寫入代碼,項目的MainActivity中包含了此部分代碼。
加載dex中的類并測試
在寫入完畢之后使用DexClassLoader對其進行加載。DexClassLoader的構造方法需要4個參數,這里對這4個參數進行簡要說明:
- String dexPath:dex文件的絕對路徑。在這里我將其放入了應用的cache文件夾下。
- String optimizedDirectory:優化后的dex文件存放路徑。DexClassLoader在構造完畢之后會對原有的dex文件優化并生成一個新的dex文件,在這里我選擇的是…/cache/optimizedDirectory/目錄。此外,API文檔對該目錄有嚴格的說明:Do not cache optimized classes on external storage.出于安全考慮,請不要將優化后的dex文件放入外部存儲器中。
- String libraryPath:dex文件所需要的庫文件路徑。這里沒有依賴,使用空字符串代替。
- ClassLoader parent:雙親委派原則中提到的父類加載器。這里我們使用默認的加載器,通過getClassLoader()方法獲得。
在解釋完畢DexClassLoader的構造參數之后,我們開始對剛剛的dex文件進行加載:
DexClassLoader dexClassLoader = new DexClassLoader(apkPath, file.getParent() + "/optimizedDirectory/", "", classLoader);接來下開始load我們剛剛寫入在dex文件中的ClassStudent類:
Class<?> aClass = dexClassLoader.loadClass("ClassStudent");然后我們對其進行初始化,并調用相關的get/set方法對其進行驗證,在這里我傳給ClassStudent對象一個字符串,然后調用它的get方法獲取在方法內合并后的字符串:
Object instance = aClass.newInstance();Method method = aClass.getMethod("setName", String.class);method.invoke(instance, "Sahadev");Method getNameMethod = aClass.getMethod("getName");Object invoke = getNameMethod.invoke(instance););最后我們實現的代碼可能是這樣的:
/*** 加載指定路徑的dex** @param apkPath*/private void loadClass(String apkPath) {ClassLoader classLoader = getClassLoader();File file = new File(apkPath);try {DexClassLoader dexClassLoader = new DexClassLoader(apkPath, file.getParent() + "/optimizedDirectory/", "", classLoader);Class<?> aClass = dexClassLoader.loadClass("ClassStudent");mLog.i(TAG, "ClassStudent = " + aClass);Object instance = aClass.newInstance();Method method = aClass.getMethod("setName", String.class);method.invoke(instance, "Sahadev");Method getNameMethod = aClass.getMethod("getName");Object invoke = getNameMethod.invoke(instance);mLog.i(TAG, "invoke result = " + invoke);} catch (Exception e) {e.printStackTrace();}}最后附上我們的運行截圖:
如果在實現過程中遇到問題的,請在下方留言。
總結
以上是生活随笔為你收集整理的一步步手动实现热修复(一)-dex文件的生成与加载的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中文词语概念上下位图谱项目
- 下一篇: JAVA AIO