編輯:關於Android編程
在中國找到錢不難,但你的一個點子不意味著是一個創業。你談一個再好的想法,比如我今天談一個創意說,新浪為什麼不收購GOOGLE呢?這個創意很好。新浪一收購GOOGLE,是不是新浪就變成老大了?你從哪兒弄來錢?怎麼去整合GOOGLE呢;
之前寫過有關於Android 插件方向的文章,解析了一下Android的插件原理與運行方式。很多小伙伴都問我,為什麼不把我制作的插件放到Github上,讓大家共享一下。
我只能說,大哥啊,這個插件是我在公司研發的時候制作的,商業機密,不能開源啊。
剛好,最近逛github的時候,看到了奇虎360手機助手團隊的一個Android插件開源項目。今天,我們就具體的分析一下它的原理與實現邏輯。讓大家更清楚的了解,一個Android插件的構造。
這個框架是奇虎360手機助手團隊,最近在github上開源出來的Android插件框架。這種精神是很值得鼓勵的。
github地址為:https://github.com/Qihoo360/DroidPlugin
好,一下純屬引入了官方的說明:
說明
DroidPlugin是360手機助手在Android系統上實現了一種新的插件機制:
它可以在無需安裝、修改的情況下運行APK文件,此機制對改進大型APP的架構,實現多團隊協作開發具有一定的好處。
特點
支持Androd 2.3以上系統
插件APK完全不需做任何修改,可以獨立安裝運行、也可以做插件運行。要以插件模式運行某個APK,你無需重新編譯、無需知道其源碼。
插件的四大組件完全不需要在Host程序中注冊,支持Service、Activity、BroadcastReceiver、ContentProvider四大組件
插件之間、Host程序與插件之間會互相認為對方已經”安裝”在系統上了。
API低侵入性:極少的API。HOST程序只是需要一行代碼即可集成Droid Plugin
超強隔離:插件之間、插件與Host之間完全的代碼級別的隔離:不能互相調用對方的代碼。通訊只能使用Android系統級別的通訊方法。
支持所有系統API 資源完全隔離:插件之間、與Host之間實現了資源完全隔離,不會出現資源竄用的情況。
實現了進程管理,插件的空進程會被及時回收,占用內存低。
插件的靜態廣播會被當作動態處理,如果插件沒有運行(即沒有插件進程運行),其靜態廣播也永遠不回被觸發。
那麼這個插件框架,和我們之前所說的插件框架,有什麼特點?它的實現方式是什麼樣的?這裡我們從源碼來分析一下。
之前,我們也討論過,一個Android的apk插件是個什麼形式。主要是三點,我總結如下:
- DexLoader動態的加載dex文件進入vm
- AndroidManifest.xml中預注冊一些將要使用的四大組件(方法很low,其實有更好的)
- 針對四大組件,分別進行抽象代理,proxy
- Apk安裝還需要單獨處理native的,so文件。
- 宿主需要處理好Resource,保證R文件的正確使用。
360這個Android插件系統是否和我們之前討論的類似呢?這裡我們從IPluginManagerImpl.java文件中,我們能夠看到其真正的安裝apk插件的方法——installPackage。具體方法詳解如下所示:
/**
* @param filepath 插件Apk的路徑
* @param flags Flag
*
*/
@Override
public int installPackage(String filepath, int flags) throws RemoteException {
//install plugin
String apkfile = null;
try {
// 驗證合法性
PackageManager pm = mContext.getPackageManager();
PackageInfo info = pm.getPackageArchiveInfo(filepath, 0);
if (info == null) {
return PackageManagerCompat.INSTALL_FAILED_INVALID_APK;
}
apkfile = PluginDirHelper.getPluginApkFile(mContext, info.packageName);
if ((flags & PackageManagerCompat.INSTALL_REPLACE_EXISTING) != 0) {
// 新安裝的插件
// 停止,刪除插件的緩存
forceStopPackage(info.packageName);
if (mPluginCache.containsKey(info.packageName)) {
deleteApplicationCacheFiles(info.packageName, null);
}
new File(apkfile).delete();
Utils.copyFile(filepath, apkfile);
PluginPackageParser parser = new PluginPackageParser(mContext, new File(apkfile));
parser.collectCertificates(0);
PackageInfo pkgInfo = parser.getPackageInfo(PackageManager.GET_PERMISSIONS | PackageManager.GET_SIGNATURES);
// 驗證申請的permission,宿主中是否存在,不存在不讓安裝。
if (pkgInfo != null && pkgInfo.requestedPermissions != null && pkgInfo.requestedPermissions.length > 0) {
for (String requestedPermission : pkgInfo.requestedPermissions) {
boolean b = false;
try {
b = pm.getPermissionInfo(requestedPermission, 0) != null;
} catch (NameNotFoundException e) {
}
if (!mHostRequestedPermission.contains(requestedPermission) && b) {
Log.e(TAG, "No Permission %s", requestedPermission);
new File(apkfile).delete();
return PluginManager.INSTALL_FAILED_NO_REQUESTEDPERMISSION;
}
}
}
// 保存簽名
saveSignatures(pkgInfo);
// if (pkgInfo.reqFeatures != null && pkgInfo.reqFeatures.length > 0) {
// for (FeatureInfo reqFeature : pkgInfo.reqFeatures) {
// Log.e(TAG, "reqFeature name=%s,flags=%s,glesVersion=%s", reqFeature.name, reqFeature.flags, reqFeature.getGlEsVersion());
// }
// }
// 拷貝插件中的native庫文件。
copyNativeLibs(mContext, apkfile, parser.getApplicationInfo(0));
// opt Dex文件,並使用DexClassLoader動態的載入類代碼。
dexOpt(mContext, apkfile, parser);
mPluginCache.put(parser.getPackageName(), parser);
// 回調函數,通知插件安裝
mActivityManagerService.onPkgInstalled(mPluginCache, parser, parser.getPackageName());
sendInstalledBroadcast(info.packageName);
return PackageManagerCompat.INSTALL_SUCCEEDED;
} else {
// 以下是插件,覆蓋安裝的過程。即更新的過程。與新安裝類似。不做解釋了。
if (mPluginCache.containsKey(info.packageName)) {
return PackageManagerCompat.INSTALL_FAILED_ALREADY_EXISTS;
} else {
forceStopPackage(info.packageName);
new File(apkfile).delete();
Utils.copyFile(filepath, apkfile);
PluginPackageParser parser = new PluginPackageParser(mContext, new File(apkfile));
parser.collectCertificates(0);
PackageInfo pkgInfo = parser.getPackageInfo(PackageManager.GET_PERMISSIONS | PackageManager.GET_SIGNATURES);
if (pkgInfo != null && pkgInfo.requestedPermissions != null && pkgInfo.requestedPermissions.length > 0) {
for (String requestedPermission : pkgInfo.requestedPermissions) {
boolean b = false;
try {
b = pm.getPermissionInfo(requestedPermission, 0) != null;
} catch (NameNotFoundException e) {
}
if (!mHostRequestedPermission.contains(requestedPermission) && b) {
Log.e(TAG, "No Permission %s", requestedPermission);
new File(apkfile).delete();
return PluginManager.INSTALL_FAILED_NO_REQUESTEDPERMISSION;
}
}
}
saveSignatures(pkgInfo);
// if (pkgInfo.reqFeatures != null && pkgInfo.reqFeatures.length > 0) {
// for (FeatureInfo reqFeature : pkgInfo.reqFeatures) {
// Log.e(TAG, "reqFeature name=%s,flags=%s,glesVersion=%s", reqFeature.name, reqFeature.flags, reqFeature.getGlEsVersion());
// }
// }
copyNativeLibs(mContext, apkfile, parser.getApplicationInfo(0));
dexOpt(mContext, apkfile, parser);
mPluginCache.put(parser.getPackageName(), parser);
mActivityManagerService.onPkgInstalled(mPluginCache, parser, parser.getPackageName());
sendInstalledBroadcast(info.packageName);
return PackageManagerCompat.INSTALL_SUCCEEDED;
}
}
} catch (Exception e) {
if (apkfile != null) {
new File(apkfile).delete();
}
handleException(e);
return PackageManagerCompat.INSTALL_FAILED_INTERNAL_ERROR;
}
}
以下是dexOpt方法的具體內容,簡單的貼一下,不做說明了。
private void dexOpt(Context hostContext, String apkfile, PluginPackageParser parser) throws Exception {
String packageName = parser.getPackageName();
String optimizedDirectory = PluginDirHelper.getPluginDalvikCacheDir(hostContext, packageName);
String libraryPath = PluginDirHelper.getPluginNativeLibraryDir(hostContext, packageName);
ClassLoader classloader = new PluginClassLoader(apkfile, optimizedDirectory, libraryPath, ClassLoader.getSystemClassLoader());
// DexFile dexFile = DexFile.loadDex(apkfile, PluginDirHelper.getPluginDalvikCacheFile(mContext, parser.getPackageName()), 0);
// Log.e(TAG, "dexFile=%s,1=%s,2=%s", dexFile, DexFile.isDexOptNeeded(apkfile), DexFile.isDexOptNeeded(PluginDirHelper.getPluginDalvikCacheFile(mContext, parser.getPackageName())));
}
vcq9u7nKx9W8v9Oho9XiwO/L+cu1tcTVvL/Tvs3Kx9a4o6zU2kFuZHJvaWRNYW5pZmVzdC54bWzW0NSk16Ky4dLUz8K/ycTcu+HTw7W9tcTQxc+ioaM8L3A+DQo8YmxvY2txdW90ZT4NCgk8cD5wcy7V4rj2t73KvbK7ysfM2LHwusOho8jnufu087zSttTOqsqyw7TSqtSk16Ky4dXiuPbOyszisrvKx7rcwcu94qOsu/LV37K7wcu94s6qyrLDtLK7usOho7/J0tS/tNK7z8LO0rXEsqm/zaOsztK74bPW0Pi4/NDCoaM8YSBocmVmPQ=="http://blog.csdn.net/yzzst/article/details/45582315" target="_blank">http://blog.csdn.net/yzzst/article/details/45582315
OK,下面是我們從DroidPlugin的Test項目中,截取出來的AndroidManifest.xml代碼,我們看看什麼叫占坑,怎麼占坑。代碼如下所示:
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MyActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<service android:name=".Service1" />
<service android:name=".Service2" />
<service
android:name=".Service3"
android:process=":Process2" />
<service
android:name=".Service4"
android:process=":Process2" />
<provider
android:name=".MyContentProvider1"
android:authorities="com.example.ApiTest.MyContentProvider1" />
<provider
android:name=".MyContentProvider2"
android:authorities="com.example.ApiTest.MyContentProvider2"
android:process=":Process2" />
<receiver android:name=".StaticBroadcastReceiver" >
<intent-filter>
<action android:name="com.example.ApiTest.StaticBroadcastReceiver" />
<activity
android:name=".StandardActivity"
android:label="@string/title_activity_standard" />
<activity
android:name=".SingleTopActivity"
android:label="@string/title_activity_single_top"
android:launchMode="singleTop" />
<activity
android:name=".SingleTaskActivity"
android:label="@string/title_activity_single_task"
android:launchMode="singleTask" />
<activity
android:name=".SingleInstanceActivity"
android:label="@string/title_activity_single_instance"
android:launchMode="singleInstance" />
<activity
android:name=".ActivityTestActivity"
android:label="@string/title_activity_activity_test" >