一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

一步步手動(dòng)實(shí)現(xiàn)熱修復(fù)

 個(gè)人文檔awpyia 2017-05-10

今日科技快訊

昨日,樂視超級(jí)汽車戰(zhàn)略合作伙伴法拉第未來(Faraday Future,簡稱FF)在美國拉斯維加斯發(fā)布旗下首款量產(chǎn)車FF 91。根據(jù)現(xiàn)場介紹,F(xiàn)F 91續(xù)航超800公里,0-60 英里/小時(shí)加速時(shí)間僅為2.39 秒,加速性能超越 Ferrari 488 GTB、TESLA Model S P100D 在內(nèi)的頂級(jí)跑車和同級(jí)別豪華車。此外,發(fā)布會(huì)演示環(huán)節(jié)還通過樂視超級(jí)手機(jī),無需人工復(fù)雜操作即可作為車輛鑰匙及車主識(shí)別功能,為用戶提供個(gè)性化需求及強(qiáng)大的交互體驗(yàn)。

作者簡介

本篇來自 SAHADEV 的投稿,詳細(xì)介紹了如何實(shí)現(xiàn)熱修復(fù),作者原文中有很多熱修復(fù)相關(guān)博文推薦,由于篇幅原因,我這里沒有貼出,感興趣的朋友可以點(diǎn)擊最后 閱讀原文 查看。

SAHADEV 的博客地址:

http://blog.csdn.net/sahadev_

前言

熱修復(fù)技術(shù)自從QQ空間團(tuán)隊(duì)搞出來之后便漸漸趨于成熟。這里我們主要介紹如何一步步手動(dòng)實(shí)現(xiàn)基本的熱修復(fù)功能,無需使用第三方框架。

本文示例所用到的任何資源都已開源,項(xiàng)目中包含工程中所用到代碼、示例圖片、說明文檔。項(xiàng)目地址為:

https://code.csdn.net/u011064099/sahadevhotfix/tree/master

dex文件的生成與加載

我們在這部分主要做的流程有:

1. 編寫基本的Java文件并編譯為 .class 文件。

2. 將 .class 文件轉(zhuǎn)為 .dex 文件。

3. 將轉(zhuǎn)好的 dex文件 放入創(chuàng)建好的Android工程內(nèi)并在啟動(dòng)時(shí)將其寫入本地。

4. 加載解壓后的 .dex 文件中的類,并調(diào)用其方法進(jìn)行測試。

Note: 在閱讀本節(jié)之前最好先了解一下 類加載器的雙親委派原則DexClassLoader的使用以及反射的知識(shí)點(diǎn)。

編寫基本的Java文件并編譯為.class文件

首先我們在一個(gè)工程目錄下開始創(chuàng)建并編寫我們的Java文件,你可能會(huì)選擇各種IDE來做這件事,但我在這里勸你不要這么做,因?yàn)橛锌釉诘饶?。等把基本流程搞清楚可以再選擇更進(jìn)階的方法。這里我們可以選擇文本編輯器比如EditPlus來對(duì)Java文件進(jìn)行編輯。

新建一個(gè)Java文件,并命名為:ClassStudent.java,并在java文件內(nèi)鍵入以下代碼:

public class ClassStudent {
   private String name;
   
   public ClassStudent() {}
   
   public void setName(String name) {
       this.name = name;    }
       
   public String getName(){
       return this.name + '.Mr';      }}

Note: 這里要注意,不要對(duì)類添加包名,因?yàn)樵诤笃趯?duì)class文件處理時(shí)會(huì)遇到問題,具體問題會(huì)稍后說明。上面的 getName 方法在返回時(shí)對(duì) this.name 屬性添加了一段字符串,這里請注意,后面會(huì)用到。

在文件創(chuàng)建好之后,對(duì)Java文件進(jìn)行編譯:

將.class文件轉(zhuǎn)為.dex文件

好,現(xiàn)在我們使用class文件生成對(duì)應(yīng)的dex文件。生成dex文件所需要的工具為dx,dx工具位于sdk的 build-tools 文件夾內(nèi),如下圖所示:

Tips: 為了方便使用,建議將dx的路徑添加到環(huán)境變量中。如果對(duì)dx工具不熟悉的,可以在終端中輸入 dx –help 以獲取幫助。

dx工具的基本用法是:

dx --dex [--output=file>] [file>.class | file>.{zip,jar,apk} | directory>]

Tips: 剛開始自己摸索的時(shí)候,就沒有仔細(xì)看命令,導(dǎo)致后面兩個(gè)參數(shù)的順序顛倒了,搞出了一些讓人疑惑難解的問題,最后又不得不去找dx工具的源碼調(diào)試,最后才發(fā)現(xiàn)自己的問題在哪。如果有對(duì)dx工具感興趣的,可以對(duì)dx的包進(jìn)行反編譯或者獲取dx的相關(guān)源代碼進(jìn)行了解。dx.lib文件位于dx.bat的下級(jí)目錄lib文件夾中,可以使用JD-GUI工具對(duì)其進(jìn)行查看或?qū)С觥H绻枰@取源代碼的,請使用以下命令進(jìn)行克隆:

Git clone https://android./platform/dalvik

我們使用以下命令生成dex文件:

dx --dex --output=user.dex ClassStudent.class

這里我為了防止出錯(cuò),提前在當(dāng)前目錄下新建好了 user.dex 文件。上述命令依賴編譯.class文件的JDK版本,如果使用的是JDK8編譯的class會(huì)提示以下問題:

PARSE ERROR:unsupported class file version 52.0...while parsing ClassStudent.class1 error; aborting

這里的52.0意味著class文件不被支持,需要使用JDK8以下的版本進(jìn)行編譯,但是dx所需的環(huán)境還是需要為JDK8的,這里我編譯class文件使用的是JDK7,請注意。

上面我們提到了為什么先不要在ClassStudent中使用包名,因?yàn)樵趫?zhí)行dx的時(shí)候會(huì)報(bào)以下異常,這是因?yàn)橐韵碌诙?xiàng)條件沒有通過,該代碼位于 com.Android.dx.cf.direct.DirectClassFile 文件內(nèi):


運(yùn)行截圖如下所示:

好了,到此為止我們的目錄應(yīng)該如下:

寫入dex到本地磁盤

接下來將生成好的user.dex文件放入Android工程的res\raw文件夾下:

在系統(tǒng)啟動(dòng)時(shí)將其寫入到磁盤,這里不再貼出具體的寫入代碼,項(xiàng)目的MainActivity中包含了此部分代碼。

加載dex中的類并測試

在寫入完畢之后使用DexClassLoader對(duì)其進(jìn)行加載。DexClassLoader 的構(gòu)造方法需要4個(gè)參數(shù),這里對(duì)這4個(gè)參數(shù)進(jìn)行簡要說明:

  • String dexPath:dex文件的絕對(duì)路徑。在這里我將其放入了應(yīng)用的cache文件夾下。

  • String optimizedDirectory:優(yōu)化后的dex文件存放路徑。DexClassLoader在構(gòu)造完畢之后會(huì)對(duì)原有的dex文件優(yōu)化并生成一個(gè)新的dex文件,在這里我選擇的是 …/cache/optimizedDirectory/ 目錄。此外,API文檔對(duì)該目錄有嚴(yán)格的說明:Do not cache optimized classes on external storage.出于安全考慮,請不要將優(yōu)化后的dex文件放入外部存儲(chǔ)器中。

  • String libraryPath:dex文件所需要的庫文件路徑。這里沒有依賴,使用空字符串代替。

  • ClassLoader parent:雙親委派原則中提到的父類加載器。這里我們使用默認(rèn)的加載器,通過getClassLoader()方法獲得。

在解釋完畢 DexClassLoader 的構(gòu)造參數(shù)之后,我們開始對(duì)剛剛的dex文件進(jìn)行加載:


接來下開始load我們剛剛寫入在dex文件中的 ClassStudent 類:

Class aClass = dexClassLoader.loadClass('ClassStudent');

然后我們對(duì)其進(jìn)行初始化,并調(diào)用相關(guān)的 get/set 方法對(duì)其進(jìn)行驗(yàn)證,在這里我傳給 ClassStudent 對(duì)象一個(gè)字符串,然后調(diào)用它的get方法獲取在方法內(nèi)合并后的字符串:

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

最后我們實(shí)現(xiàn)的代碼可能是這樣的:


最后附上我們的運(yùn)行截圖:

類的加載機(jī)制簡要介紹

本節(jié)內(nèi)容是為了給下節(jié)內(nèi)容做知識(shí)鋪墊,所以如果要需要了解熱修復(fù)技術(shù),本節(jié)內(nèi)容的知識(shí)點(diǎn)必不可少。

一個(gè)類在被加載到內(nèi)存之前要經(jīng)過加載、驗(yàn)證、準(zhǔn)備等過程。經(jīng)過這些過程之后,虛擬機(jī)才會(huì)從方法區(qū)將代表類的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換為內(nèi)存中的Class。

我們這節(jié)內(nèi)容的重點(diǎn)在于一個(gè)類是如何被加載的,所以我們從類的加載入口開始。

類的加載是由虛擬機(jī)觸發(fā)的,類的加載入口位于 ClassLoader loadClassInternal() 方法:


這段方法還有段注釋說明:這個(gè)方法由虛擬機(jī)調(diào)用用來加載一個(gè)類。我們看到這個(gè)類的內(nèi)部最后調(diào)用了 loadClass() 方法。那我們進(jìn)入 loadClass() 方法看看:

public Class loadClass(String name) throws ClassNotFoundException {
   return loadClass(name, false);}

loadClass() 方法方法內(nèi)部調(diào)用了 loadClass() 的重載方法:


loadClass() 方法大概做了以下工作:

  • 首先查找該類是否已經(jīng)被加載.

  • 如果該ClassLoader有父加載器,那么調(diào)用父加載器的loadClass()方法.

  • 如果沒有父加載器,則調(diào)用 findBootstrapClassOrNull() 方法進(jìn)行加載,該方法會(huì)使用引導(dǎo)類加載器進(jìn)行加載。普通類是不會(huì)被該加載器加載到的,所以這里一般返回null.

  • 如果前面的步驟都沒找到,那調(diào)用自身的findClass()方法進(jìn)行查找。

好,ClassLoader 的 findClass() 方法是個(gè)空方法,所以這個(gè)過程一般是由子加載器實(shí)現(xiàn)的。Java的加載器這么設(shè)計(jì)是有一定的淵源的,感興趣的讀者可以自行查找書籍了解。

protected Class findClass(String name) throws ClassNotFoundException {
   throw new ClassNotFoundException(name);}

在Android中,ClassLoader 的直接子類是 BaseDexClassLoader,我們看一下 BaseDexClassLoader 的 findClass() 實(shí)現(xiàn):


Tips: 有需要虛擬機(jī)以及類加載器全套代碼的,請使用以下命令克隆:

Git clone https://android./platform/dalvik-snapshot

相關(guān)代碼位于項(xiàng)目的 ics-mr1 分支上。

看到這里我們可以知道,Android中類的查找是通過這個(gè) pathList 進(jìn)行查找的,而 pathList 又是個(gè)什么鬼呢?

在 BaseDexClassLoader 中聲明了以下變量:

/** structured lists of path elements */
private final DexPathList pathList;

所以我們可以看看 DexPathList findClass() 方法做了什么:


這里通過遍歷 dexElements 中的 Element對(duì)象 進(jìn)行查找,最終走的是 DexFile 的 loadClassBinaryName() 方法:


到此為止,我們就將類的加載過程梳理完了。

Class文件的替換

本節(jié)主要依賴文章:

http://blog.csdn.net/vurtne_ye/article/details/39666381

中的未實(shí)現(xiàn)代碼實(shí)現(xiàn),實(shí)現(xiàn)思路也源自該文章,在閱讀本文之前可以先行了解。

這一節(jié)我們主要實(shí)現(xiàn)的流程有:

  • 在工程內(nèi)創(chuàng)建相同的ClassStudent類,但在調(diào)用 getName() 方法返回字符串時(shí)會(huì)稍有區(qū)別,用于結(jié)果驗(yàn)證

  • 使用 DexClassLoader 加載外部的 user.dex

  • 將 DexClassLoader 中的 dexElements 放在 PathClassLoader 的 dexElements 之前

  • 驗(yàn)證替換結(jié)果

創(chuàng)建工程內(nèi)的ClassStudent

上面演示了如何加載外部的Class,為了起到熱修復(fù)效果,那么我們需要在工程內(nèi)有一個(gè)被替換的類,替換的ClassStudent類內(nèi)容如下:


外部的ClassStudent類的內(nèi)容如下:


這兩個(gè)類除了在 getName() 方法返回之處有差別之外,其它地方一模一樣,不過這足可以讓我們說明情況。

我們這里要實(shí)現(xiàn)的目的: 我們默認(rèn)調(diào)用getName()方法返回的是“xxxx.Miss”,如果熱修復(fù)成功,那么再使用該方法的話,返回的則會(huì)是“xxxx.Mr”

對(duì)含有包名的類再次編譯

因?yàn)榈谝还?jié)中專門聲明了不可以對(duì)類聲明包名,但是這樣在Android工程中無法引用到該類,所以把不能聲明包名的問題解決了一下。

不能聲明包名的主要原因是在編譯Java文件時(shí),沒有正確的使用命令。對(duì)含有包名的Java文件應(yīng)當(dāng)使用以下命令:

javac -d ./ ClassStudent.java

經(jīng)過上面命令編譯后的.class文件便可以順利通過dx工具的轉(zhuǎn)換。

我們還是按照第一節(jié)的步驟將轉(zhuǎn)換后的user.dex文件放入工程中并寫入本地磁盤,以便稍后使用。

替換工程內(nèi)的類文件

在開始之前還是再回顧一下實(shí)現(xiàn)思路(詳見上一大章節(jié)):類在使用之前必須要經(jīng)過加載器的加載才能夠使用,在加載類時(shí)會(huì)調(diào)用自身的 findClass() 方法進(jìn)行查找。然而在Android中類的查找使用的是 BaseDexClassLoader,BaseDexClassLoader 對(duì) findClass() 方法進(jìn)行了重寫。

我們可以得知類的查找是通過遍歷 dexElements 來進(jìn)行查找的。所以為了實(shí)現(xiàn)替換效果,我們需要將 DexClassLoader 中的 Element對(duì)象 放到 dexElements數(shù)組第0個(gè)位置,這樣才能在 BaseDexClassLoader 查找類時(shí)先找到 DexClassLoader 所用的 user.dex 中的類。

類的加載是從上而下加載的,所以就算是 DexClassLoader 加載了外部的類,但是在系統(tǒng)使用類的時(shí)候還是會(huì)先在 ClassLoader 中查找,如果找不到則會(huì)在 BaseDexClassLoader 中查找,如果再找不到,就會(huì)進(jìn)入 PathClassLoader 中查找,最后才會(huì)使用 DexClassLoader 進(jìn)行查找,所以按照這個(gè)流程外部類是無法正常發(fā)揮作用的。所以我們的目的就是在查找工程內(nèi)的類之前,先讓加載器去外部的dex中查找。

好了,再次梳理了思路之后,我們接下來對(duì)思路進(jìn)行實(shí)踐。

下面的方法是我們主要的注入方法:


這段代碼的核心在于將 DexClassLoader 中的 dexElements PathClassLoader 中的 dexElements 進(jìn)行合并,然后將合并后的 dexElements 替換 原先的dexElements。最后我們在使用 ClassStudent類 的時(shí)候便可以直接使用外部的ClassStudent,而不會(huì)再加載默認(rèn)的ClassStudent類。

首先我們通過 classLoader 獲取各自的 pathList 對(duì)象:


在使用以上反射的時(shí)候要注意,pathList屬性 屬于 基類BaseDexClassLoader。所以如果直接獲取 DexClassLoader 或者 PathClassLoader的pathList 屬性的話,會(huì)得到null。

其次是獲取 pathList 對(duì)應(yīng)的 dexElements,這里要注意 dexElements 是個(gè) 數(shù)組對(duì)象


接下來我們將兩個(gè)數(shù)組對(duì)象合并成為一個(gè):


上面這段代碼我們根據(jù)數(shù)組對(duì)象的類型創(chuàng)建了一個(gè)新的大小為2的新數(shù)組,并將兩個(gè)數(shù)組的第一個(gè)元素取出,將代表 外部dex 的 dexElement 放在了第0個(gè)位置。這樣便可以確保在查找類時(shí)優(yōu)先從外部的dex中查找。

最后將原先的dexElements覆蓋


驗(yàn)證替換結(jié)果

好,我們做完以上的工作之后,寫一段代碼來進(jìn)行驗(yàn)證:


如果我們沒有替換成功的話,那么這里默認(rèn)使用的是內(nèi)部的ClassStudent,getName()返回的會(huì)是Lavon.Miss。

如果我們替換成功的話,那么這里默認(rèn)使用的是外部的ClassStudent,getName()返回的則會(huì)是Lavon.Mr

我們實(shí)際運(yùn)行看下效果:

這說明我們已經(jīng)完成了基本的熱修復(fù)。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請遵守用戶 評(píng)論公約

    類似文章 更多

    91久久精品国产一区蜜臀| 99久久国产亚洲综合精品| 丰满人妻一二区二区三区av | 久久99青青精品免费观看| 国产女性精品一区二区三区| 观看日韩精品在线视频| 黄片在线免费看日韩欧美| 国产亚州欧美一区二区| 日本精品理论在线观看| 欧美日韩国产精品第五页| 大香蕉大香蕉手机在线视频| 国内女人精品一区二区三区| 欧美乱码精品一区二区三| 91后入中出内射在线| 国产免费观看一区二区| 欧美一级片日韩一级片| 色一情一乱一区二区三区码| 91麻豆精品欧美视频| 中文字幕一区久久综合| 亚洲高清亚洲欧美一区二区| 成人精品日韩专区在线观看| 精品人妻一区二区三区四区久久| 国产超薄黑色肉色丝袜| 亚洲欧美日韩色图七区| 亚洲伦片免费偷拍一区| 99久久精品免费看国产高清| 国产又色又爽又黄又大| 绝望的校花花间淫事2| 日本不卡在线视频中文国产| 极品熟女一区二区三区| 欧美黑人在线一区二区| 国产精品涩涩成人一区二区三区| 日本在线视频播放91| 亚洲国产精品久久网午夜| 国产成人精品视频一二区| 国产精品视频久久一区| 国产精品亚洲综合色区韩国| 精品精品国产自在久久高清| 麻豆91成人国产在线观看| 国产精品偷拍视频一区| 麻豆在线观看一区二区|