目的:用最簡(jiǎn)單的方法調(diào)用起指定apk(未安裝過(guò))中的Activity
困難:
apk沒(méi)有安裝,就無(wú)法使用startActivity調(diào)起apk中指定的Activity,不管是顯式調(diào)用還是隱式調(diào)用,都不行。
解決思路:
既然明著來(lái)不行,那只有劍走偏鋒了……

看上圖,本來(lái)我是想點(diǎn)擊MainActivity的中的按鈕,調(diào)起PluginDemo.apk中的PluginDemoActivity的。但上面也說(shuō)過(guò)了,這條路走不通。那換個(gè)思路,當(dāng)我點(diǎn)擊MainActivity的中的按鈕時(shí),我調(diào)起一個(gè)本應(yīng)用內(nèi)的Activity,也就是上圖的PluginActivity,讓PluginActivity顯示與PluginDemoActivity相同的界面(不知道是哪家大神想出的妙招)。
這個(gè)想法很奇特,先看PluginActivity的onCreate()方法:
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
如果此時(shí),PluginActivity擁有PluginDemo.apk的資源對(duì)象,且setContentView方法設(shè)置的是PluginDemoActivity對(duì)應(yīng)的布局資源id,那么上面的想法即可實(shí)施。
涉及到的問(wèn)題:如何讀取另一個(gè)apk中的資源文件?
這就要靠強(qiáng)大的AssetManager了!
AssetManager 提供了對(duì)應(yīng)用程序原始資源的訪問(wèn);這個(gè)類提供了一個(gè)低級(jí)別的API,允許你通過(guò)一個(gè)簡(jiǎn)單的字節(jié)流來(lái)打開(kāi)和讀取已經(jīng)和應(yīng)用程序綁定了的原始文件。
Android應(yīng)用程序在運(yùn)行過(guò)程中,是通過(guò)AssetManager來(lái)讀取打包在apk文件里的資源文件的。
做一個(gè)小實(shí)驗(yàn):
建一個(gè)新的android工程:PluginDemo,包名:com.test.plugindemo,在主Layout中添加一個(gè)TextView即可,如下圖:

編譯工程,build一個(gè)apk,名字改為PluginDemo.apk,拷貝到手機(jī)的Environment.getExternalStorageDirectory()目錄下待用。
再新建一個(gè)工程:Container,包名:com.test.container,
除了主Activity再創(chuàng)建一個(gè)PluginActivity的Activity,在主Layout中添加一個(gè)Button,用于調(diào)起新建的Activity。三張圖看明白:



上PluginActivity的代碼:
protected AssetManager mAssetManager;
protected Resources mResources;
protected Theme mTheme;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
String dir = Environment.getExternalStorageDirectory().toString();
String apkPath = dir + "/PluginDemo.apk";
//讀取apk資源
loadResources(apkPath);
setContentView(R.layout.activity_main);
}
protected void loadResources(String apkPath)
{
try
{
//AssetManager的構(gòu)造函數(shù)沒(méi)有對(duì)api公開(kāi),不能使用new創(chuàng)建
AssetManager assetManager = AssetManager.class.newInstance();
//addAssetPath把資源目錄里的資源都加載到AssetManager對(duì)象中,它是一個(gè)隱藏方法,只能用反射的方式來(lái)調(diào)用
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, apkPath);
mAssetManager = assetManager;
}
catch (Exception e)
{
e.printStackTrace();
}
Resources superRes = super.getResources();
superRes.getDisplayMetrics();
superRes.getConfiguration();
mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
mTheme = mResources.newTheme();
mTheme.setTo(super.getTheme());
}
@Override
public AssetManager getAssets()
{
return mAssetManager == null ? super.getAssets() : mAssetManager;
}
@Override
public Resources getResources()
{
return mResources == null ? super.getResources() : mResources;
}
@Override
public Theme getTheme()
{
return mTheme == null ? super.getTheme() : mTheme;
}
成功獲取資源mResources對(duì)象后,理論上就能運(yùn)行了。測(cè)試運(yùn)行Container成功!

ps:這只是一個(gè)實(shí)驗(yàn),測(cè)試了對(duì)插件資源的讀取,android插件化要做的事情還很多很多,但是朕累了……
補(bǔ)充說(shuō)明(06/05):
Container工程——PluginActivity的setContentView(R.layout.activity_main);中的R.layout.activity_main這個(gè)id是PluginDemo中activity_main的id;只是名字恰好和容器Activity的相同罷了。
為了表達(dá)清楚,可以在PluginDemo工程中再添加一個(gè)Activity,叫TestActivity,對(duì)應(yīng)的layout是activity_test.xml,查看R.java

找到activity_test對(duì)應(yīng)的id(我機(jī)器上是0x7f04001a),然后修改:Container工程——PluginActivity的setContentView(0x7f04001a);
重新放置apk,編譯、運(yùn)行Container,即可顯示新創(chuàng)建的Activity!