前言html
在目前的軟硬件環境下,Native App與Web App在用戶體驗上有着明顯的優點,但在實際項目中有些會由於業務的頻繁變動而頻繁的升級客戶端,形成較差的用戶體驗,而這也偏偏是Web App的優點。本文對網上Android動態加載jar的資料進行梳理和實踐在這裏與你們一塊兒分享,試圖改善頻繁升級這一弊病。
java
聲明android
歡迎轉載,但請保留文章原始出處:)
windows
博客園:http://www.cnblogs.com安全
農民伯伯: http://over140.cnblogs.com 網絡
Android中文翻譯組:http://androidbox.sinaapp.com/app
正文框架
1、 基本概念和注意點
ide
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、 準備
本文主要參考"4、參考文章"中第一篇文章,補充細節和實踐過程。
2.1 下載開源項目
http://code.google.com/p/goodev-demo
將項目導入工程,工程報錯的話應該是少了gen文件夾,手動添加便可。注意這個例子是從網上下載優化好的jar(已經優化成dex而後再打包成的jar)到本地文件系統,而後再從本地文件系統加載並調用的。本文則直接改爲從SD卡加載。
3、實踐
3.1 編寫接口和實現
3.1.1 接口IDynamic
package com.dynamic;
public interface IDynamic {
public String helloWorld();
}
3.1.2 實現類DynamicTest
package com.dynamic;
public class DynamicTest implements IDynamic {
@Override
public String helloWorld() {
return "Hello World!";
}
}
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
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mToastButton = (Button) findViewById(R.id.toast_button);
// Before the secondary dex file can be processed by the DexClassLoader,
// it has to be first copied from asset resource to a storage location.
// final File dexInternalStoragePath = new File(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);
// // Perform the file copying in an AsyncTask.
// // 從網絡下載須要的dex文件
// (new PrepareDexTask()).execute(dexInternalStoragePath);
// } else {
// mToastButton.setEnabled(true);
// }
mToastButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
// Internal storage where the DexClassLoader writes the optimized dex file to.
//final File optimizedDexOutputPath = getDir("outdex", Context.MODE_PRIVATE);
final File optimizedDexOutputPath = new File(Environment.getExternalStorageDirectory().toString()
+ File.separator + "test.jar");
// Initialize the class loader with the secondary dex file.
// DexClassLoader cl = new DexClassLoader(dexInternalStoragePath.getAbsolutePath(),
// optimizedDexOutputPath.getAbsolutePath(),
// null,
// getClassLoader());
DexClassLoader cl = new DexClassLoader(optimizedDexOutputPath.getAbsolutePath(),
Environment.getExternalStorageDirectory().toString(), null, getClassLoader());
Class libProviderClazz = null;
try {
// Load the library class from the class loader.
// 載入從網絡上下載的類
// libProviderClazz = cl.loadClass("com.example.dex.lib.LibraryProvider");
libProviderClazz = cl.loadClass("com.dynamic.DynamicTest");
// Cast the return object to the library interface so that the
// caller can directly invoke methods in the interface.
// Alternatively, the caller can invoke methods through reflection,
// which is more verbose and slow.
//LibraryInterface lib = (LibraryInterface) libProviderClazz.newInstance();
IDynamic lib = (IDynamic)libProviderClazz.newInstance();
// Display the toast!
//lib.showAwesomeToast(view.getContext(), "hello 世界!");
Toast.makeText(MainActivity.this, lib.helloWorld(), Toast.LENGTH_SHORT).show();
} catch (Exception exception) {
// Handle exception gracefully here.
exception.printStackTrace();
}
}
});
}
3.4 執行結果
4、參考文章
5、補充
你們能夠看看DexClassLoader的API文檔,裏面不提倡從SD卡加載,不安全。此外,我也正在組織翻譯組儘快把這個命名空間下的幾個類都翻譯出來,以供你們參考。
工程下載:這裏,Dex文件下載:這裏。你們能夠直接把Dex文件拷貝到SD卡,而後運行例子。
6、後期維護
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.jar dynamic.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.