蔡杰,郭兵
(四川大學(xué)計算機學(xué)院,成都610065)
Android動態(tài)加載方案的研究與實現(xiàn)
蔡杰,郭兵
(四川大學(xué)計算機學(xué)院,成都610065)
目前隨著智能手機的性能逐步提升,移動應(yīng)用越來越大,也越來越復(fù)雜。Android應(yīng)用的apk包的體積也變得很大。這樣在實際的項目中,會因為業(yè)務(wù)頻繁變更而頻繁地升級客戶端,帶給用戶較差的體驗。隨著產(chǎn)品組織架構(gòu)的調(diào)整,各個業(yè)務(wù)模塊分別歸屬不同團隊開發(fā),這樣開發(fā)溝通成本以及發(fā)布成本都大大增加。對于上述的問題,Android動態(tài)加載就被提出來。使用動態(tài)加載,這樣可以先發(fā)布很小的apk應(yīng)用,然后按需動態(tài)加載功能模塊。
Android;動態(tài)加載;按需
Android應(yīng)用的更新越來越頻繁,體積越來越大。為了滿足各種不同需求,例如快速修復(fù)bug,在線更新應(yīng)用程序,以及拆分業(yè)務(wù)等,需要一種技術(shù)來解決這些問題,這種技術(shù)就是動態(tài)加載。Android中動態(tài)加載技術(shù)有兩種:一種是動態(tài)加載so庫,代碼運行在Native上;另外一種是利用ClassLoader動態(tài)加載jar/dex/apk[1]文件,運行在Android虛擬機上。
目前網(wǎng)絡(luò)上涉及到的關(guān)鍵詞匯有插件化、熱部署、熱修復(fù)等,這些技術(shù)的根源都可以說是動態(tài)加載,通過ClassLoader動態(tài)加載外部jar/dex/apk。而動態(tài)加載帶來的好處也是顯而易見的,它使得應(yīng)用程序能很好地適應(yīng)并行開發(fā),并能解耦各個模塊,還能加快編譯速度,提高并行開發(fā)效率。本文就將介紹動態(tài)加載實現(xiàn)的原理,指出Android動態(tài)加載需要解決的問題,并給出解決方案。
我們知道Java程序運行在JVM虛擬機上面的,虛擬機通過ClassLoader[2]加載jar文件,并根據(jù)jar包里面的Class創(chuàng)建實例對象并工作。所以Java程序可以通過動態(tài)調(diào)用jar文件達到動態(tài)加載的目的。對于Java程序來說,編寫程序就是編寫類,運行程序我們需要使用編譯得到的class文件,而將這些class文件加載到內(nèi)存中就需要類加載器,而在Android系統(tǒng)中我們使用的是Dalvik/ART虛擬機[3],Dalvik/ART虛擬機工作原理和Java的JVM虛擬機差不多,在運行程序時首先將對應(yīng)的類加載到內(nèi)存中。因此,可以利用這一點,在程序運行時手動加載Class,從而達到動態(tài)加載的目的。既然完成加載工作就需要使用類加載器,下面我們就介紹關(guān)于類加載器[4]的相關(guān)知識。
(1)類加載器ClassLoader
ClassLoader類加載器,用來加載Java類到Java虛擬機中的一種加載器。JVM本身包含一個Bootstrap ClassLoader,它負責(zé)加載核心Java Class。另外JVM還提供兩個ClassLoader,它們由BootstrapClassLoader加載。其中ExtensionClassLoader負責(zé)加載擴展的Java Class,ApplicationClassLoader負責(zé)加載應(yīng)用程序自身的類。
在Android應(yīng)用中,整個App不僅只有一個類加載器,可以自己創(chuàng)建ClassLoader實例來加載Class,創(chuàng)建一個ClassLoader實例的時候,需要使用一個現(xiàn)有的ClassLoader實例作為新創(chuàng)建的實例的Parent。這樣整個應(yīng)用里所有的ClassLoader實例都會被一棵樹關(guān)聯(lián)起來,這也是ClassLoader的雙親代理模型的特點。下面是Android中雙親代理模型的關(guān)鍵代碼:
從源碼中我們可以看出,類加載采用如下步驟:
①當(dāng)前ClassLoader首先從自己已經(jīng)加載的類中查詢此類是否被加載,如果已經(jīng)被加載則直接返回原來已經(jīng)加載的類。
②沒有找到被加載的類的時候,就委托父類加載器去加載,父加載器采用同樣的策略,首先查看自己是否加載,然后委托自己的父類去加載,一直到Bootstrp-ClassLoader。
③當(dāng)所有父類加載器都沒有加載的時候,再由當(dāng)前的類加載器加載,并放入自己的緩存中。
這樣做有個明顯的特點,如果一個類被位于樹根的ClassLoader加載過,在之后的過程中,這個類永遠不會被重新加載。首先是共享功能,一些Framework層級的類一旦被頂層的ClassLoader加載過就緩存在內(nèi)存里面,以后任何地方用到都不需要重新加載。除此之外還有隔離功能,不同繼承路線上的ClassLoader加載的類肯定不是同一個類,這樣的限制避免了用戶自己的代碼冒充核心類庫的類訪問核心類庫包可見成員的情況。這也好理解,一些系統(tǒng)層級的類會在系統(tǒng)初始化的時候被加載,例如java.lang.String,如果在一個應(yīng)用里面能夠簡單地用自定義的String類把這個系統(tǒng)的String類給替換掉,那將會有嚴重的安全問題。
(2)DexClassLoader和PathClassLoader
在Android中,ClassLoader是一個抽象類,系統(tǒng)提供了另外兩個類加載器DexClassLoader和PathClass-Loader來加載類。它們的不同之處在于:DexClass-Loader可以加載jar/apk/dex,可以從SD卡中加載未安裝的apk,而PathClassLoader只能加載系統(tǒng)中已經(jīng)安裝過的apk。
其實DexClassLoader和PathClassLoader只是簡單的對BaseDexClassLoader做了一下封裝,它們都是繼承自BaseDexClassLoader,它們只是在構(gòu)造函數(shù)中的參數(shù)有些不同。這個不同的參數(shù)就決定了它們一個可以加載外部的dex,而另外一個只能加載內(nèi)部的dex。
在Java中實現(xiàn)動態(tài)加載時,我們主要是通過類加載器,也就是ClassLoader實現(xiàn)類的動態(tài)加載,但是在實現(xiàn)Android的動態(tài)加載時,我們還需要解決下面兩個問題[5]。
(1)資源訪問
Res資源是Android開發(fā)中經(jīng)常用到的,而Android是把這些資源用對應(yīng)的R.id注冊好,運行時通過這些ID從Resource實例中獲取對應(yīng)的資源。我們知道,宿主程序調(diào)起未安裝的插件apk,一個很大的問題就是資源如何訪問,具體來說就是插件中凡是以R開頭的資源都不能訪問了。這是因為宿主程序中并沒有插件的資源,所以通過R來加載插件的資源是行不通的,如果是運行時動態(tài)加載進來的新類,程序會拋出異常:無法找到某某id所對應(yīng)的資源。因為新類的資源ID根本和現(xiàn)有的Resource實例中保存的資源ID對不上。在Android中,所有資源會在Java源碼層面生成對應(yīng)的常量ID,這些ID會記錄到R.java文件中,參與到之后的代碼編譯階段中。在R.java文件中,Android資源在編譯過程中會生成所有資源的ID,作為常量統(tǒng)一存放在R類中供其他代碼引用。在R類中生成的每一個int型四字節(jié)資源ID,實際上都由三個字段組成。第一字節(jié)代表了Package,第二字節(jié)為分類,三四字節(jié)為類內(nèi)ID。我們后面需要根據(jù)這個特點來解決一部分資源訪問問題。
(2)組件的管理
Android中許多組件類(如Activity、Service等)是需要在Manifest文件里面注冊后才能工作的(系統(tǒng)會檢查該組件有沒有注冊),通過ClassLoader加載并實例化的Activity實例只是一個普通的Java對象,能調(diào)用對象的方法,但是它沒有生命周期,而我們在開發(fā)過程中系統(tǒng)調(diào)用Activity的生命周期方法,而且Activity等系統(tǒng)組件是需要Android的上下文環(huán)境的(Context等資源),沒有這些東西Activity根本無法工作。所以我們對于組件管理要解決的兩個問題,一是使組件具有生命周期,二是使組件具有上下文環(huán)境(可以使用R資源)。
從上面的分析可知,我們實現(xiàn)一個動態(tài)加載方案主要是解決上面兩個問題,資源的訪問以及組件的管理。
(1)我們需要對aapt[6]工具進行改造
上面已經(jīng)提到過,在Android中,所有資源會在Java源碼層面生成對應(yīng)的常量ID,這些ID會記錄到R. java文件中,參與到之后的代碼編譯階段中。在R.java文件中,Android資源在編譯過程中會生成所有資源的ID,作為常量統(tǒng)一存放在R類中供其他代碼引用。在R類中生成的每一個int型四字節(jié)資源ID,實際上都由三個字段組成。第一字節(jié)代表了Package,第二字節(jié)為分類,三四字節(jié)為類內(nèi)ID。Android的編譯依賴一個強大的命令行工具:aapt,我們可以修改aapt的部分參數(shù),為每個插件工程指定獨特的PackageID,然后對各個插件的資源進行編譯,生成R文件,然后與宿主項目的R文件進行ID的合并。因為最后需要對所有資源ID進行合并,所以所有的資源名稱均不能相同。
(2)運行時資源的加載
對于上面提到的第二個問題,以加載Activity為例,一般我們可以通過代理的方式,然后創(chuàng)建代理Activity,然后使用代理Activity的方式來管理生命周期。其實也可以通過系統(tǒng)來創(chuàng)建Activity,這樣Activity的生命周期可以像通常開發(fā)那樣交給系統(tǒng)處理,這里我們使用的方式就是交由系統(tǒng)來處理,當(dāng)然由系統(tǒng)創(chuàng)建的話,我們需要把插件中的Activity在宿主中進行注冊,另外我們需要做一些額外的處理。這里之所以可以交由系統(tǒng)處理,是因為我們已經(jīng)改造過aapt工具了,可以順利在R文件中訪問到需要的資源id。在Android中使用資源的時候,都是通過AssetManager類和Resources類來訪問的。獲取它們的方法位于Context類中。具體實現(xiàn)在ContextImpl類中。ContextImpl類中初始化Resources對象后,它的子類Activity和Service等組件就都可以通過下面兩個方法[7]獲取資源。
至于讀取資源,在之前改造aapt時,已經(jīng)在資源ID第一個字節(jié)標(biāo)記了資源所屬的Package,AssetManager有一個隱藏方法addAssetPath,可以為AssertManager添加資源路徑。通過反射調(diào)用這個隱藏方法,然后把插件apk位置告訴AssetManager類,它就會根據(jù)apk內(nèi)的resources.arsc和已編譯資源來完成資源的加載工作。
插件資源加載完成之后,我們還要創(chuàng)建Activity,Service等組件。在Android系統(tǒng)中,Activity、Service等系統(tǒng)組件,都會經(jīng)由android.app.ActivityThread類[8]在主線程中執(zhí)行。ActivityThread類有一個成員叫mInstrumentation,它會負責(zé)創(chuàng)建Activity等操作,我們就是在這個類里面完成所需資源的注入。mInstrumentation是Instrumentation類的一個實例,它是用來接管所有Activity、Service等組件的創(chuàng)建(當(dāng)然也就包含了它們使用到的Resources類)。通過修改mInstrumentation為我們自己創(chuàng)建的Instrumentation,在每次創(chuàng)建Activity的時候順手把它的mResources類換為我們自定義的Resources類,同時也重寫ContextImpl用于返回我們自定義的資源對象。
本文分析了動態(tài)加載的原理,介紹了Java中的類加載器的工作原理以及Android中重新實現(xiàn)的兩個類加載器,并介紹了Android動態(tài)加載機制的相關(guān)原理。通過修改aapt工具重新編譯資源,解決資源的訪問問題。讓系統(tǒng)創(chuàng)建相關(guān)組件,并在合適的時候注入資源,解決組件管理相關(guān)的問題。使用此技術(shù)可以解決Android應(yīng)用快速修復(fù)bug,在線更新應(yīng)用程序,以及拆分業(yè)務(wù)方面的需求。提高開發(fā)人員的開發(fā)效率,以及帶給用戶更好的體驗。
[1]張國印,吳艷霞.Android Dalvik虛擬機結(jié)構(gòu)及機制剖析[M].北京:清華大學(xué)出版社,2014.
[2]周志明.深入理解Java虛擬機:JVM高級特性與最佳實踐[M].北京:機械工業(yè)出版社,2013.
[3]伊鵬翔.Dalvik虛擬機結(jié)構(gòu)與性能的研究[D].吉林大學(xué)2011.
[4]Android動態(tài)加載基礎(chǔ)ClassLoader工作機制.https://segmentfault.com/a/1190000004062880.
[5]任玉剛.Android開發(fā)藝術(shù)探索[M].北京:電子工業(yè)出版社,2015.
[6]攜程Android App插件化和動態(tài)加載實踐.http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/1105/3658.html.
[7]常煜,鄧飛.Android動態(tài)加載技術(shù)[J].電腦知識與技術(shù),2016.
[8]羅升陽.Android系統(tǒng)源代碼情景分析[M].北京:電子工業(yè)出版社,2016.
Sorting Algorithm Without Changing the Data Position&Dynamic Demonstration
CAI Jie,GUO Bing
(College of Computer Science,Sichuan University,Chengdu 610065)
Now with the performance of smartphones gradually improved,mobile applications become bigger and bigger,also more and more complex.The Android apk package volume becomes larger.In the actual project,it may bring a poor user experience because of changing or updating client side frequently.On the other hand,with the adjustment of product structure,each business module respectively belong to different team,the development communication cost and launch cost are greatly increased.For those problems,proposes the Android dynamic loading.Using dynamic loading,we can release a small core application version,then dynamic load the function module on demand.
Android;Dynamic Load;On-Demand
1007-1423(2017)01-0042-04
10.3969/j.issn.1007-1423.2017.01.011
蔡杰(1992-),男,湖北天門人,碩士,研究方向為嵌入式、軟件工程
2016-11-03
2016-12-26
郭兵(1970-),男,山東人,教授,博士生導(dǎo)師,研究方向為嵌入式實時系統(tǒng)、綠色計算、個人大數(shù)據(jù)、軟件工程