文章首發(fā):Android應(yīng)用程序插件化研究之AssetManager|大利貓
最近在研究Android應(yīng)用的插件化開發(fā),看了好幾個相關(guān)的開源項目憋槐。插件化都是在解決以下幾個問題:
- 如何把插件apk中的代碼和資源加載到當(dāng)前虛擬機(jī)淑趾。
- 如何把插件apk中的四大組件注冊到進(jìn)程中。
- 如何防止插件apk中的資源和宿主apk中的資源引用沖突近范。
就這幾個問題评矩,我開始研究插件化開發(fā)實現(xiàn)的相關(guān)技術(shù)阱飘。在上篇文章中我講了如何把插件apk中的class加載到當(dāng)前進(jìn)的問題虱颗,本篇文章主要講第一點的第二點:如何加載另一個apk中的資源到當(dāng)前應(yīng)用中忘渔。
AssetManager介紹
當(dāng)我們在組件中獲取資源時使用getResource獲得Resource對象畦粮,通過這個對象我們可以訪問相關(guān)資源乖阵,比如文本、圖片拉背、顏色等默终。通過跟蹤源碼發(fā)現(xiàn),其實 getResource 方法是Context的一個抽象方法两疚, getResource的實現(xiàn)是在ContextImp中實現(xiàn)的诱渤。獲取的Resource對象是應(yīng)用的全局變量谈况,然后繼續(xù)跟蹤源碼,發(fā)現(xiàn) Resource 中有一個AssetManager的全局變量赡茸,在Resource的構(gòu)造函數(shù)中傳入的祝闻,所以最終獲取資源都是通過 AssetManager 獲取的联喘,于是我們把注意力放到AssetManager上。我們要解決下面兩個問題:
- 如何獲取 AssetManager 對象叭喜。
- 如何通過 AssetManager 對象獲取插件中apk的資源堤框。
通過對 AssetManager 的相關(guān)源碼跟蹤纵柿,我們找到答案:
- AssetManager 的構(gòu)造函數(shù)沒有對 api 公開昂儒,不能使用 new 創(chuàng)建渊跋;context .getAssets() 可用獲取當(dāng)前上下文環(huán)境的 AssetManager着倾;利用反射 AssetManager.class.newInstance() 這樣可用獲取對象。
- 如何獲取插件 apk 中的資源蒿囤。我們發(fā)現(xiàn) AssetManager 中有個重要的方法崇决。
/** Add an additional set of assets to the asset manager.
* This can be either a directory or ZIP file.
* Not for use by applications. Returns the cookie of the added asset,
* or 0 on failure.
*@{hide}
*/
public native final int addAssetPath(String path);
我們可以把一個包含資源的文件包添加到assets中恒傻。這就是AssetManager查找資源的第一個路徑。這個方法是一個隱藏方法睁枕,我們可以通過反射調(diào)用外遇。
AssetManager assetManager = AssetManager.class.newInstance() ; // context .getAssets()罐氨?
AssetManager.class.getDeclaredMethod("addAssetPath", String.class).invoke(assetManager, apkPath);
上文提到,我們可以獲取當(dāng)前上下文的 AssetManager,也可以通過反射創(chuàng)建一個 AssetManager玩徊。我們這里本文分析的是后一種。第一種能不能呢泣棋,這個問題留給讀者先去思考潭辈,后續(xù)文章會單獨討論。 詳細(xì)了解可以參考老羅的文章《Android應(yīng)用程序資源管理器(Asset Manager)的創(chuàng)建過程分析》把敢。下面我們來寫一個demo:獲取插件的文本資源和圖片資源。
創(chuàng)建一個插件apk工程module:apkbeloaded
我把插件的包名命名為laodresource.demo.com.loadresourcedemo婶恼。
第一步:
這個工程中我們可以不用寫任何代碼柏副。在drawable目錄下放一張圖片:icon_be_load.png。
在string中定義字符串:<string name="text_beload">I am from apkBeLoaded</string>眷篇。
第二步:
打包生成apk:apkbeloaded-debug.apk荔泳。
拷貝到測試機(jī)文件路徑下:$ adb push <你的路徑>/apkbeloaded-debug.apk sdcard/
創(chuàng)建一個宿主apk工程
在布局文件中定義一個文本控件和一個圖片控件,分別用來顯示插件中獲取的文本和圖片椎椰。
<ImageView android:layout_width="wrap_content"
android:src="@mipmap/ic_launcher"
android:id="@+id/icon"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/text"
android:text="Hello World!"
android:layout_below="@+id/icon"
android:layout_centerInParent="true"
/>
MainActivity.java
onCrease:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ImageView imageView = (ImageView) findViewById(R.id.icon);
TextView textView = (TextView) findViewById(R.id.text);
/**
* 插件apk路徑
*/
String apkPath = Environment.getExternalStorageDirectory()+"/apkbeloaded-debug.apk";
/**
* 插件資源對象
*/
Resources resources = getBundleResource(this,apkPath);
/**
*獲取圖片資源
*/
Drawable drawable = resources.getDrawable(resources.getIdentifier("icon_be_load", "drawable",
"laodresource.demo.com.apkbeloaded"));
/**
* 獲取文本資源
*/
String text = resources.getString(resources.getIdentifier("text_beload","string",
"laodresource.demo.com.apkbeloaded"));
imageView.setImageDrawable(drawable);
textView.setText(text);
}
創(chuàng)建Resource對象:
public Resources getBundleResource(Context context, String apkPath){
AssetManager assetManager = createAssetManager(apkPath);
return new Resources(assetManager,
context.getResources().getDisplayMetrics(),
context.getResources().getConfiguration());
}
創(chuàng)建AssetManager對象:
private AssetManager createAssetManager(String apkPath) {
try {
AssetManager assetManager = AssetManager.class.newInstance();
AssetManager.class.getDeclaredMethod("addAssetPath", String.class).invoke(
assetManager, apkPath);
return assetManager;
} catch (Throwable th) {
th.printStackTrace();
}
return null;
}
到這里慨飘,我們完成了插件研究的第一個問題:加載插件apk的class和資源瓤的。下一篇文章開始研究插件中的組件注冊問題圈膏。
Demo源碼:https://github.com/liuguangli/LoadResourceDemo