Android動態載入——jar/dex
前言
在目前的軟硬體環境下,Native App與Web App在使用者體驗上有著明顯的優勢,但在實際專案中有些會因為業務的頻繁變更而頻繁的升級客戶端,造成較差的使用者體驗,而這也恰恰是Web App的優勢。本文對網上Android動態載入jar的資料進行梳理和實踐在這裡與大家一起分享,試圖改善頻繁升級這一弊病。
宣告
歡迎轉載,但請保留文章原始出處:)
部落格園:http://www.cnblogs.com
農民伯伯: http://over140.cnblogs.com
Android中文翻譯組:http://androidbox.sinaapp.com/
正文
一、 基本概念和注意點
1.1 首先需要了解一點:在Android中可以動態載入,但無法像Java中那樣方便動態載入jar
原因:Android的虛擬機器(Dalvik VM)是不認識Java打出jar的byte code,需要通過dx工具來優化轉換成Dalvik byte code才行。這一點在咱們Android專案打包的apk中可以看出:引入其他Jar的內容都被打包進了classes.dex。
所以這條路不通,請大家注意。
1.2 當前哪些API可用於動態載入
1.2.1 DexClassLoader
這個可以載入jar/apk/dex,也可以從SD卡中載入,也是本文的重點。
1.2.3 PathClassLoader
只能載入已經安裝到Android系統中的apk檔案。
二、 準備
本文主要參考"四、參考文章"中第一篇文章,補充細節和實踐過程。
2.1 下載開源專案
http://code.google.com/p/goodev-demo
將專案匯入工程,工程報錯的話應該是少了gen資料夾,手動新增即可。注意這個例子是從網上下載優化好的jar(已經優化成dex然後再打包成的jar)到本地檔案系統,然後再從本地檔案系統載入並呼叫的。本文則直接改成從SD卡載入。
三、實踐
3.1 編寫介面和實現
3.1.1 介面IDynamic
packagecom.dynamic;
publicinterfaceIDynamic{
publicStringhelloWorld();
}
3.1.2 實現類DynamicTest
packagecom.dynamic;
publicclassDynamicTestimplementsIDynamic{
@Override
publicStringhelloWorld(){
return"HelloWorld!";
}
}
3.2 打包並轉成dex
3.2.1 選中工程,常規流程匯出即可,如圖:
注意:在實踐中發現,自己新建一個Java工程然後匯出jar是無法使用的,這一點大家可以根據文章一來了解相關原因,也是本文的重點之一。這裡打包匯出為dynamic.jar
(後期修復:打包請不要把介面檔案打進來,參見文章末尾後續維護!)
3.2.2 將打包好的jar拷貝到SDK安裝目錄android-sdk-windows\platform-tools下,DOS進入這個目錄,執行命名:
dx--dex--output=test.jar dynamic.jar
3.3 修改呼叫例子
修改MainActivity,如下:
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mToastButton=(Button)findViewById(R.id.toast_button);
//BeforethesecondarydexfilecanbeprocessedbytheDexClassLoader,
//ithastobefirstcopiedfromassetresourcetoastoragelocation.
//finalFiledexInternalStoragePath=newFile(getDir("dex",Context.MODE_PRIVATE),SECONDARY_DEX_NAME);
//if(!dexInternalStoragePath.exists()){
//mProgressDialog=ProgressDialog.show(this,
//getResources().getString(R.string.diag_title),
//getResources().getString(R.string.diag_message),true,false);
////PerformthefilecopyinginanAsyncTask.
////從網路下載需要的dex檔案
//(newPrepareDexTask()).execute(dexInternalStoragePath);
//}else{
//mToastButton.setEnabled(true);
//}
mToastButton.setOnClickListener(newView.OnClickListener(){
publicvoidonClick(Viewview){
//InternalstoragewheretheDexClassLoaderwritestheoptimizeddexfileto.
//finalFileoptimizedDexOutputPath=getDir("outdex",Context.MODE_PRIVATE);
finalFileoptimizedDexOutputPath=newFile(Environment.getExternalStorageDirectory().toString()
+File.separator+"test.jar");
//Initializetheclassloaderwiththesecondarydexfile.
//DexClassLoadercl=newDexClassLoader(dexInternalStoragePath.getAbsolutePath(),
//optimizedDexOutputPath.getAbsolutePath(),
//null,
//getClassLoader());
DexClassLoadercl=newDexClassLoader(optimizedDexOutputPath.getAbsolutePath(),
Environment.getExternalStorageDirectory().toString(),null,getClassLoader());
ClasslibProviderClazz=null;
try{
//Loadthelibraryclassfromtheclassloader.
//載入從網路上下載的類
//libProviderClazz=cl.loadClass("com.example.dex.lib.LibraryProvider");
libProviderClazz=cl.loadClass("com.dynamic.DynamicTest");
//Castthereturnobjecttothelibraryinterfacesothatthe
//callercandirectlyinvokemethodsintheinterface.
//Alternatively,thecallercaninvokemethodsthroughreflection,
//whichismoreverboseandslow.
//LibraryInterfacelib=(LibraryInterface)libProviderClazz.newInstance();
IDynamiclib=(IDynamic)libProviderClazz.newInstance();
//Displaythetoast!
//lib.showAwesomeToast(view.getContext(),"hello世界!");
Toast.makeText(MainActivity.this,lib.helloWorld(),Toast.LENGTH_SHORT).show();
}catch(Exceptionexception){
//Handleexceptiongracefullyhere.
exception.printStackTrace();
}
}
});
}
3.4 執行結果
四、參考文章
五、補充
大家可以看看DexClassLoader的API文件,裡面不提倡從SD卡載入,不安全。此外,我也正在組織翻譯組儘快把這個名稱空間下的幾個類都翻譯出來,以供大家參考。
工程下載:這裡,Dex檔案下載:這裡。大家可以直接把Dex檔案拷貝到SD卡,然後執行例子。
六、後期維護
6.1 2011-12-1 修復本文錯誤
感謝網友ppp250和liuzhaocn的反饋,基本按照評論2來修改:
6.1.1 不需要在本工程裡面匯出jar,自己新建一個Java工程然後匯出來也行。
6.1.2 匯出jar時不能帶介面檔案,否則會報以下錯:
java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
6.1.3 將jar優化時應該重新成jar(jar->dex->jar),如果如下命令:
dx--dex--output=test.jardynamic.jar
以下轉自雲在千峰部落格:
普通的Android程式Dalvik虛擬機器都是從一個預設的地方載入程式需要的類檔案(dex檔案),而Dalvik虛擬機器還提供了從其他地方載入類的能力(比如從裝置的內部儲存空間以及網際網路).
這種自定義類載入機制可以使用於一些場景:
dex檔案只能包含最多64K的函式引用,對於大型的程式如果超過了該數字,就可以通過把程式打包為多個dex檔案來實現,在程式執行的時候在載入這些需要的類
一些開發框架可以通過執行時自定義類載入機制來設計出更加可擴充套件的框架
通過該方式實現更加強壯的程式註冊機制,防止被人破解
Android有個示例專案演示瞭如何使用, 專案地址
http://code.google.com/p/android-custom-class-loading-sample/
要使用該示例,不能使用Eclipse外掛ADT來打包必需通過該專案提供的Ant指令碼, 另外該Ant指令碼需要Android SDK 12版本, 可以通過Android SDK Manager來下載或者到如下地址下載,然後解壓到對應的目錄中
https://dl-ssl.google.com/android/repository/tools_r12-windows.zip
https://dl-ssl.google.com/android/repository/platform-tools_r06-windows.zip
在示例中有3個類檔案:
com.example.dex.MainActivity: UI介面,在這個類中動態載入需要的類
com.example.dex.LibraryInterface: 動態載入類的介面定義
com.example.dex.lib.LibraryProvider: 動態載入類的實現,該類在打包的時候會打包到另外一個dex檔案中
在打包的時候需要修改專案目錄下的local.properties檔案,把sdk.dir的值修改為對應的android SDK目錄.例如:sdk.dir=E:\\google\\android-sdk-windows
另外不要忘記在default.properties中指定需要的android平臺: 例如 target=android-9
然後就可以執行android install來build並且安裝到模擬器或者手機上了.
載入自定義類的過程
獲取需要載入的自定義類的dex檔案,可以是裝置本地的檔案或者網際網路上的檔案
把獲取到的自定義類dex檔案儲存到程式的內部儲存空間中:new File(getDir(“dex”, Context.MODE_PRIVATE),SECONDARY_DEX_NAME);
通過DexClassLoader類載入器來解析優化前面的dex檔案
通過DexClassLoader的loadClass函式來載入類
通過獲得到的類的newInstance函式來生成需要的物件
開始使用獲取到的動態類物件~\(≧▽≦)/~啦啦啦
Android提供的示例專案中,通過Ant打包後把com.example.dex.lib.LibraryProvider類放入了程式的assets資料夾中,然後從這裡讀取需要動態載入的類. 為了演示從網際網路載入類和使用Eclipse ADT外掛來build該示例,我們對該專案做了簡單修改,修改後的示例專案地址:
http://code.google.com/p/goodev-demo中 的android-custom-class-loading-goodev-demo
在該示例中刪除了com.example.dex.lib.LibraryProvider類,我們把該類打包為dex檔案並且放入到了網際網路上下載地址: http://goodev.sinaapp.com/and/secondary_dex.jar
在程式執行的時候先從該地址下載需要的類檔案,然後解析.
詳細情況請參考專案中的程式碼註釋
修改後的專案可以通過Eclipse ADT來build.
Read more:http://blog.chengyunfeng.com/?p=87#ixzz2x8vY4oTM
轉載於:https://my.oschina.net/tingzi/blog/213332