Android动态加载入门 简单加载模式
基本信息
-
作者:kaedea
-
項目:android-dynamical-loading
初步了解Android動態(tài)加載
Java程序中,JVM虛擬機(jī)是通過類加載器ClassLoader加載.jar文件里面的類的。Android也類似,不過Android用的是Dalvik/ART虛擬機(jī),不是JVM,也不能直接加載.jar文件,而是加載dex文件。
先要通過Android SDK提供的DX工具把.jar文件優(yōu)化成.dex文件,然后Android的虛擬機(jī)才能加載。注意,有的Android應(yīng)用能直接加載.jar文件,那是因為這個.jar文件已經(jīng)經(jīng)過優(yōu)化,只不過后綴名沒改(其實已經(jīng)是.dex文件)。
如果對ClassLoader的工作機(jī)制有興趣,具體過程請參考?Android 動態(tài)加載基礎(chǔ) ClassLoader工作機(jī)制,這里不再贅述。
如何獲取能夠加載的.dex文件
首先我們可以通過JDK的編譯命令javac把Java代碼編譯成.class文件,再使用jar命令把.class文件封裝成.jar文件,這與編譯普通Java程序的時候完全一樣。
之后再用Android SDK的DX工具把.jar文件優(yōu)化成.dex文件(在“android-sdk\build-tools\具體版本\”路徑下)
dx --dex --output=target.dex origin.jar // target.dex就是我們要的了
此外,我們可以現(xiàn)把代碼編譯成APK文件,再把APK里面的.dex文件解壓出來,或者直接把APK文件當(dāng)成.dex使用(只是APK里面的靜態(tài)資源文件我們暫時還用不到)。至此我們發(fā)現(xiàn),無論加載.jar,還是.apk,其實都和加載.dex是等價的,Android能加載.jar和.apk,是因為它們都包含有.dex,直接加載.apk文件時,ClassLoader也會自動把.apk里的.dex解壓出來。
加載并調(diào)用.dex里面的方法
與JVM不同,Android的虛擬機(jī)不能用ClassCload直接加載.dex,而是要用DexClassLoader或者PathClassLoader,他們都是ClassLoader的子類,這兩者的區(qū)別是
DexClassLoader:可以加載jar/apk/dex,可以從SD卡中加載未安裝的apk;
PathClassLoader:要傳入系統(tǒng)中apk的存放Path,所以只能加載已經(jīng)安裝的apk文件;
使用前,先看看DexClassLoader的構(gòu)造方法
public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {super((String)null, (File)null, (String)null, (ClassLoader)null);throw new RuntimeException("Stub!");}注意,我們之前提到的,DexClassLoader并不能直接加載外部存儲的.dex文件,而是要先拷貝到內(nèi)部存儲里。這里的dexPath就是.dex的外部存儲路徑,而optimizedDirectory則是內(nèi)部路徑,libraryPath用null即可,parent則是要傳入當(dāng)前應(yīng)用的ClassLoader,這與ClassLoader的“雙親代理模式”有關(guān)。
實例使用DexClassLoader的代碼
File optimizedDexOutputPath = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "test_dexloader.jar");// 外部路徑 File dexOutputDir = this.getDir("dex", 0);// 無法直接從外部路徑加載.dex文件,需要指定APP內(nèi)部路徑作為緩存目錄(.dex文件會被解壓到此目錄) DexClassLoader dexClassLoader = new DexClassLoader(optimizedDexOutputPath.getAbsolutePath(),dexOutputDir.getAbsolutePath(), null, getClassLoader());到這里,我們已經(jīng)成功把.dex文件給加載進(jìn)來了,接下來就是如何調(diào)用.dex里面的代碼,主要有兩種方式。
使用反射的方式
使用DexClassLoader加載進(jìn)來的類,我們本地并沒有這些類的源碼,所以無法直接調(diào)用,不過可以通過反射的方法調(diào)用,簡單粗暴。
DexClassLoader dexClassLoader = new DexClassLoader(optimizedDexOutputPath.getAbsolutePath(), dexOutputDir.getAbsolutePath(), null, getClassLoader());Class libProviderClazz = null;try {libProviderClazz = dexClassLoader.loadClass("me.kaede.dexclassloader.MyLoader");// 遍歷類里所有方法Method[] methods = libProviderClazz.getDeclaredMethods();for (int i = 0; i < methods.length; i++) {Log.e(TAG, methods[i].toString());}Method start = libProviderClazz.getDeclaredMethod("func");// 獲取方法start.setAccessible(true);// 把方法設(shè)為public,讓外部可以調(diào)用String string = (String) start.invoke(libProviderClazz.newInstance());// 調(diào)用方法并獲取返回值Toast.makeText(this, string, Toast.LENGTH_LONG).show();} catch (Exception exception) {// Handle exception gracefully here.exception.printStackTrace();}使用接口的方式
畢竟.dex文件也是我們自己維護(hù)的,所以可以把方法抽象成公共接口,把這些接口也復(fù)制到主項目里面去,就可以通過這些接口調(diào)用動態(tài)加載得到的實例的方法了。
pulic interface IFunc{public String func(); }// 調(diào)用 IFunc ifunc = (IFunc)libProviderClazz; String string = ifunc.func(); Toast.makeText(this, string, Toast.LENGTH_LONG).show();到這里,我們已經(jīng)成功從外部路徑動態(tài)加載一個.dex文件,并執(zhí)行里面的代碼邏輯了。通過從服務(wù)器下載最新的.dex文件并替換本地的舊文件,就能初步實現(xiàn)“APP的動態(tài)升級了”。
如何動態(tài)更改XML布局
雖然已經(jīng)能動態(tài)更改代碼邏輯了,但是UI界面要怎么更改啊?Android開發(fā)中大部分的情況下,UI界面都是通過XML布局實現(xiàn)的,放在res目錄下,可是.dex庫里面并沒有這些靜態(tài)資源啊,所以無法改變XML布局。(這里即使直接動態(tài)加載APK文件,但是通過DexClassLoader只能加載新的APK其中的.dex文件,并無法加載其中的res資源文件,所以如果在動態(tài)加載的.dex中直接使用新的APK的res資源的話會拋出異常。)
大家都知道,所有的XML布局在運行的時候都要通過LayoutInflator渲染成View的實例,這個實例與我們使用純Java代碼創(chuàng)建的View實例幾乎是等價的,而且后者可能效率還更高,所有的XML布局實現(xiàn)的UI界面都有等價的純代碼的創(chuàng)建方案。由此伸展開來,res目錄下所有XML資源都有等價的純代碼的實現(xiàn)方式,比如XML動畫、XML Drawable等。
所以,如果想要動態(tài)更改應(yīng)用的UI界面的話,可以通過用純代碼創(chuàng)建布局的形式來解決。此外,還可以模仿LayoutInflator的工作方式,自己寫一套布局解析器來解析XML文件,這樣就能在完全不依賴res資源的情況下創(chuàng)建UI界面了,當(dāng)然這樣的工作量不少,而且,完全避開res資源的話,所有的分辨率、國際化等自適應(yīng)問題都要自己在應(yīng)用層寫代碼維護(hù)了,顯然脫離res資源框架不是一個很明智的做法,但是這種做法確實可行,在我們之前的實際生產(chǎn)中的項目中也穩(wěn)定使用著,這里出于責(zé)任問題就不方便公開細(xì)節(jié)了。
(說實在,這種方案非常繁瑣,不好維護(hù),一方面,這是產(chǎn)品一句“技術(shù)可行就做唄”而產(chǎn)生的解決方案;另一方面,但是動態(tài)加載技術(shù)還很不成熟,也沒有什么實際投入到生產(chǎn)的項目,所以采取了非常保守的開發(fā)方式)。
使用Fragment代替Activity
Activity需要在Manifest里注冊,然后一標(biāo)準(zhǔn)的Intent啟動才會具有生命周期,很明顯,如果想要動態(tài)加載的.dex里的Activity沒有注冊的話,是無法啟動的。
有一種簡單粗暴的做法就是可以把.dex里所有需要用到的Activity都事先注冊到原項目里,不過這樣一來如果.dex里的Activity有變化,原項目就必須跟著升級。
另外一種方案是使用Fragment,Fragment自帶生命周期,不需要在Manifest里注冊,所以可以在.dex里使用Fragment來代替Activity,代價就是Fragment之間的切換會繁瑣許多。
ART模式的兼容性問題
當(dāng)初我們開始設(shè)計動態(tài)加載方案的時候,還沒有ART模式。隨著Kitkat的發(fā)布以及ART模式的出現(xiàn),我們開始擔(dān)心“用DexClassLoader加載.dex文件”的方案會不會在ART模式上面存在兼容性問題。
其實,ART模式相比原來的Dalvik,會在安裝APK的時候,使用Android系統(tǒng)自帶的dex2oat工具把APK里面的.dex文件轉(zhuǎn)化成OAT文件,OAT文件是一種Android私有ELF文件格式,它不僅包含有從DEX文件翻譯而來的本地機(jī)器指令,還包含有原來的DEX文件內(nèi)容。這使得我們無需重新編譯原有的APK就可以讓它正常地在ART里面運行,也就是我們不需要改變原來的APK編程接口。ART模式的系統(tǒng)里,同樣存在DexClassLoader類,包名路徑也沒變,只不過它的具體實現(xiàn)與原來的有所不同,但是接口是一致的。
package dalvik.system;import dalvik.system.BaseDexClassLoader; import java.io.File;public class DexClassLoader extends BaseDexClassLoader {public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {super((String)null, (File)null, (String)null, (ClassLoader)null);throw new RuntimeException("Stub!");} }也就是說,ART模式在加載.dex文件的方法上,對Dalvik做了向下兼容,所以使用DexClassLoader加載進(jìn)來的.dex文件同樣也會被轉(zhuǎn)化成OAT文件再被執(zhí)行,“以DexClassLoader為核心的動態(tài)加載方案”在ART模式上可以穩(wěn)定運行。
關(guān)于ART模式以及OAT文件的詳細(xì)分析,請參考官方的ART and Dalvik,以及老羅的Android ART運行時無縫替換Dalvik虛擬機(jī)的過程分析。
存在的問題與改進(jìn)方案
以上大致就是“Android動態(tài)性加載初級階段”的解決方案,雖然現(xiàn)在已經(jīng)能投入到具體的生產(chǎn)中去,但是還有一些問題無法忽略。
無法使用res目錄下的資源,特別是使用XML布局,以及無法通過res資源到達(dá)自適應(yīng)
無法動態(tài)加載新的Activity等組件,因為這些組件需要在Manifest中注冊,動態(tài)加載無法更改當(dāng)前APK的Manifest
以上問題可以通過反射調(diào)用Framework層代碼以及代理Activity的方式解決,可以把這種的動態(tài)加載框架成為“代理模式”。
參考日志
http://44289533.iteye.com/blog/1954453
http://blog.csdn.net/bboyfeiyu/article/details/11710497
http://www.cnblogs.com/over140/archive/2011/11/23/2259367.html
總結(jié)
以上是生活随笔為你收集整理的Android动态加载入门 简单加载模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ClassLoader工作机制
- 下一篇: Android动态加载进阶 代理Acti