最近在研究DroidPlugin源碼及其實現(xiàn)機制葱淳,讀這種大神級的代碼甚是費腦。我基本是逐行閱讀代碼谦铃,為求甚解耘成,花了不少時間和心思。這里驹闰,我也做個筆記供大家參考瘪菌,這樣就可以少走彎路。
Run it
為了解其工作DroidPlugin的工作原理嘹朗,我們第一步應(yīng)該是把Demo運行起來师妙,其項目地址是:DroidPlugin。作者的Demo比較復雜骡显,畢竟他要測試到所有的功能疆栏。所以這里,建議各位自己重寫一個簡單的Demo惫谤,然后運行起來壁顶。這樣起碼你知道如何使用DroidPlugin了。
在Demo中溜歪,與我們打交道的類是PluginManager若专,用該類中以下方法就可以完成最基本的插件調(diào)用。
//判斷插件服務(wù)是否處于連接狀態(tài)
public boolean isConnected() {
return mHostContext != null && mPluginManager != null;
}
//將插件的ServiceConnection添加到緩存中
public void addServiceConnection(ServiceConnection sc) {
sServiceConnection.add(new WeakReference<ServiceConnection>(sc));
}
//將插件的ServiceConnection從緩存中移除
public void removeServiceConnection(ServiceConnection sc) {
Iterator<WeakReference<ServiceConnection>> iterator = sServiceConnection.iterator();
while (iterator.hasNext()) {
WeakReference<ServiceConnection> wsc = iterator.next();
if (wsc.get() == sc) {
iterator.remove();
}
}
}
//安裝插件
public int installPackage(String filepath, int flags) throws RemoteException {
try {
if (mPluginManager != null) {
int result = mPluginManager.installPackage(filepath, flags);
Log.w(TAG, String.format("%s install result %d", filepath, result));
return result;
} else {
Log.w(TAG, "Plugin Package Manager Service not be connect");
}
} catch (RemoteException e) {
throw e;
} catch (Exception e) {
Log.e(TAG, "forceStopPackage", e);
}
return -1;
}
//刪除插件
public void deletePackage(String packageName, int flags) throws RemoteException {
try {
if (mPluginManager != null) {
mPluginManager.deletePackage(packageName, flags);
} else {
Log.w(TAG, "Plugin Package Manager Service not be connect");
}
} catch (RemoteException e) {
throw e;
} catch (Exception e) {
Log.e(TAG, "deletePackage", e);
}
}
Analyse it
首先來看看PluginManager相關(guān)的類圖蝴猪。
從類圖中我們可以看到调衰,整個插件管理器是通過Binder機制來完成運作的膊爪。絕大部分插件管理相關(guān)的方法都在IPluginManagerImpl類中實現(xiàn)。
獲取插件Apk的基本信息
Apk包信息用PackageInfo來表示嚎莉,通過PackageInfo我們可以獲取包名米酬、版本號、版本代碼以及四大組件相關(guān)信息等趋箩。通過以下類圖赃额,我們可以知道包信息相關(guān)的內(nèi)容。
我們知道叫确,要獲取一個Apk的包相關(guān)信息跳芳,可以通過PackageManager來獲取,但是這僅限于已經(jīng)安裝的apk竹勉,對于一個未安裝的apk該如何獲取飞盆。
在PackageManager中有一個名為getPackageArchiveInfo的方法,代碼如下次乓。
/**
* Retrieve overall information about an application package defined
* in a package archive file
*
* @param archiveFilePath The path to the archive file
* @param flags Additional option flags. Use any combination of
* @return Returns the information about the package. Returns
* null if the package could not be successfully parsed.
*
* @see #GET_ACTIVITIES
* @see #GET_GIDS
* @see #GET_CONFIGURATIONS
* @see #GET_INSTRUMENTATION
* @see #GET_PERMISSIONS
* @see #GET_PROVIDERS
* @see #GET_RECEIVERS
* @see #GET_SERVICES
* @see #GET_SIGNATURES
*
*/
public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags) {
final PackageParser parser = new PackageParser();
final File apkFile = new File(archiveFilePath);
try {
PackageParser.Package pkg = parser.parseMonolithicPackage(apkFile, 0);
if ((flags & GET_SIGNATURES) != 0) {
parser.collectCertificates(pkg, 0);
parser.collectManifestDigest(pkg);
}
PackageUserState state = new PackageUserState();
return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state);
} catch (PackageParserException e) {
return null;
}
}
通過getPackageArchiveInfo方法吓歇,我們可以讀取未安裝插件的AndroidManifest.xml文件來獲取packageName、versionName檬输、versionCode照瘾、ActivityInfo、ServiceInfo丧慈、InstrumentationInfo等信息析命。
插件安裝
插件的安裝是通過IPluginManagerImpl的installPackage方法來完成的。
public int installPackage(String filepath, int flags) throws RemoteException {
String apkfile = null;
try {
PackageManager pm = mContext.getPackageManager();
/**
* 調(diào)用getPackageArchiveInfo方法獲取插件包的信息
* */
PackageInfo info = pm.getPackageArchiveInfo(filepath, 0);
if (info == null) {
return PackageManagerCompat.INSTALL_FAILED_INVALID_APK;
}
/**
* 獲取插件將要放置的路徑逃默,/data/data/com.HOST.PACKAGE/Plugin/PLUGIN.PKG/apk/apk-1.apk下
* */
apkfile = PluginDirHelper.getPluginApkFile(mContext, info.packageName);
/**
* 這里是是判斷是否允許替換相同的插件包鹃愤,這種寫法應(yīng)該是模仿系統(tǒng)的寫法。
* 可以通過PackageManager的getPackageArchiveInfo方法源碼來看完域。
* 通過標志位與系統(tǒng)預(yù)設(shè)類變量進行與運算
* */
if ((flags & PackageManagerCompat.INSTALL_REPLACE_EXISTING) != 0) {
/**
* 強制停止插件進程软吐,所有的插件都是運行在獨立的進程
* */
forceStopPackage(info.packageName);
/**
* 刪除先前插件的緩存,ApplicationInfo的dataDir路徑下的所有file
* */
if (mPluginCache.containsKey(info.packageName)) {
deleteApplicationCacheFiles(info.packageName, null);
}
/**
* 刪除原有插件apk
* */
new File(apkfile).delete();
/**
* 將插件apk復制到apkfile路徑
* */
Utils.copyFile(filepath, apkfile);
/**
* PluginPackageParser可以理解為插件的PackageManager吟税,作者基本上把所有組件相關(guān)的信息都解析出來了凹耙,牛逼!
* 所以作者寫了PluginPackageParser這個類來獲取四大組件肠仪、簽名肖抱、權(quán)限等相關(guān)信息。
* */
PluginPackageParser parser = new PluginPackageParser(mContext, new File(apkfile));
/**
* 獲取證書信息
* */
parser.collectCertificates(0);
/**
* 獲取插件的權(quán)限信息和簽名信息
* */
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;
/**
* 如果插件申明的權(quán)限异旧,宿主沒有的話意述,則安裝插件失敗
* **/
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;
}
}
}
/**
* 保存簽名信息到DISK
* */
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());
// }
// }
/**
* 把插件的lib下so文件拷貝到/data/data/.../lib 下
* */
copyNativeLibs(mContext, apkfile, parser.getApplicationInfo(0));
//優(yōu)化dex
dexOpt(mContext, apkfile, parser);
//緩存已經(jīng)解析出來插件的PluginPackageParser信息(類似PackageInfo)
mPluginCache.put(parser.getPackageName(), parser);
//回調(diào)方法,通知已經(jīng)完成
mActivityManagerService.onPkgInstalled(mPluginCache, parser, parser.getPackageName());
//發(fā)送安裝完成的廣播
sendInstalledBroadcast(info.packageName);
return PackageManagerCompat.INSTALL_SUCCEEDED;
}
//以下分析與上述通覆蓋安裝同理
else {
//代碼省略........
}
}
通過以上分析,我們可以知道荤崇,安裝插件主要做了解析插件apk的PackageInfo拌屏,獲取證書信息,讀取so文件术荤,加載dex等倚喂。前面我們有提到過使用getPackageArchiveInfo可以獲取插件的PackageInfo,但是在installPackage方法中有PluginPackageParser類實例創(chuàng)建的過程喜每,用PluginPackageParser來完成包相關(guān)信息的解析务唐。
PluginPackageParser主要是通過反射來獲取PackageInfo雳攘,而且還要做版本適配带兜,很復雜。那為什么作者不直接getPackageArchiveInfo方法獲取的PackageInfo吨灭?
上面有介紹到PackageInfo類相關(guān)的類圖刚照,其實整個PackageInfo所包含的信息是相當多的。我們通過getPackageArchiveInfo獲取插件的PackageInfo僅僅是讀取AndroidManifest.xml文件上的信息喧兄,有些信息是無法獲取无畔,比如ApplicationInfo中的processName、publicSourceDir吠冤、sourceDir浑彰,PackageInfo中的gids等,這些信息都是需要安裝或運行后才能確定的拯辙。
下面郭变,首先來看看PluginPackageParser相關(guān)的類圖
整個解析過程,看似很復雜涯保,但是只要掌握了核心思想就很好理解了诉濒。
PluginPackageParser分析
我們繼續(xù)分析上面提到過的getPackageArchiveInfo方法。
public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags) {
//PackageParser用于解析在disk上的apk安裝包
final PackageParser parser = new PackageParser();
final File apkFile = new File(archiveFilePath);
try {
//獲取Package對象
PackageParser.Package pkg = parser.parseMonolithicPackage(apkFile, 0);
if ((flags & GET_SIGNATURES) != 0) {
//獲取證書信息
parser.collectCertificates(pkg, 0);
parser.collectManifestDigest(pkg);
}
//每個用戶關(guān)于package的狀態(tài)信息夕春,例如package是否安裝未荒,是否隱藏,是否停止等
PackageUserState state = new PackageUserState();
//調(diào)用generatePackageInfo方法返回PackageInfo
return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state);
} catch (PackageParserException e) {
return null;
}
}
/**
*從以上代碼分析中及志,我們可以看到PackageParser是完成包解析的核心步驟片排,接著分析
*PackageParser類中的generatePackageInfo方法,因為generatePackageInfo方法在PackageParser中重載了速侈,
*我們直接看該方法最終實現(xiàn)
**/
public static PackageInfo generatePackageInfo(PackageParser.Package p,
int gids[], int flags, long firstInstallTime, long lastUpdateTime,
Set<String> grantedPermissions, PackageUserState state, int userId) {
if (!checkUseInstalledOrHidden(flags, state)) {
return null;
}
//創(chuàng)建PackageInfo率寡,將Package中的信息賦給PackageInfo
PackageInfo pi = new PackageInfo();
pi.packageName = p.packageName;
pi.splitNames = p.splitNames;
pi.versionCode = p.mVersionCode;
pi.baseRevisionCode = p.baseRevisionCode;
pi.splitRevisionCodes = p.splitRevisionCodes;
pi.versionName = p.mVersionName;
pi.sharedUserId = p.mSharedUserId;
pi.sharedUserLabel = p.mSharedUserLabel;
pi.applicationInfo = generateApplicationInfo(p, flags, state, userId);
pi.installLocation = p.installLocation;
pi.coreApp = p.coreApp;
//......省略中間代碼,
//生成ActivityInfo是通過調(diào)用generateActivityInfo方法锌畸,其他組件或包相關(guān)信息在PackageParser都有對應(yīng)的生產(chǎn)方法勇劣。
if ((flags&PackageManager.GET_ACTIVITIES) != 0) {
int N = p.activities.size();
if (N > 0) {
if ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
pi.activities = new ActivityInfo[N];
} else {
int num = 0;
for (int i=0; i<N; i++) {
if (p.activities.get(i).info.enabled) num++;
}
pi.activities = new ActivityInfo[num];
}
for (int i=0, j=0; i<N; i++) {
final Activity activity = p.activities.get(i);
if (activity.info.enabled
|| (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
pi.activities[j++] = generateActivityInfo(p.activities.get(i), flags,
state, userId);
}
}
}
}
//省略部分代碼.......
}
return pi;
}
通過以上簡要分析,我們可以知道在PackageParser類中有很多可以獲取包信息的方法,比如獲取PackageInfo的generatePackageInfo比默,獲取ServiceInfo的generateServiceInfo等幻捏,這些方法的名字絕大部分都是以generate開頭的。
DroidPlugin作者通過反射的方法來獲取PackageInfo命咐、ActivityInfo等信息篡九,因為PackageParser是hide類,所以每個系統(tǒng)版本獲取包相關(guān)信息的實現(xiàn)可能不一樣醋奠,所以作者做了版本兼容榛臼,這需要讀完各個系統(tǒng)版本PackageParser類源碼,辛苦作者了窜司!
作者自己也定義了一個PackageParser類沛善,用于完成與系統(tǒng)PackageParser類類似的功能。在PluginPackageParser的構(gòu)造方法中通過調(diào)用自定義PackageParser類的newPluginParser方法來獲取包相關(guān)信息塞祈。
public PluginPackageParser(Context hostContext, File pluginFile) throws Exception {
mHostContext = hostContext;
mPluginFile = pluginFile;
//通過反射系統(tǒng)PackageParser相關(guān)方法來完成包相關(guān)信息解析
mParser = PackageParser.newPluginParser(hostContext);
//解析插件包
mParser.parsePackage(pluginFile, 0);
mPackageName = mParser.getPackageName();
mHostPackageInfo = mHostContext.getPackageManager().getPackageInfo(mHostContext.getPackageName(), 0);
//以下操作是將解析到的ActivityInfo進行緩存操作金刁。
//下面代碼出現(xiàn)ComponentName,我想作者是為了模仿系統(tǒng)PackageManager類中相關(guān)的api
//例如PackageManager中獲取ActivityInfo的方法议薪,getActivityInfo(ComponentName,int)
List datas = mParser.getActivities();
for (Object data : datas) {
ComponentName componentName = new ComponentName(mPackageName, mParser.readNameFromComponent(data));
synchronized (mActivityObjCache) {
mActivityObjCache.put(componentName, data);
}
synchronized (mActivityInfoCache) {
ActivityInfo value = mParser.generateActivityInfo(data, 0);
//這個方法很重要尤蛮,前面有提到過未安裝的apk某些屬性是無法獲取到的,
//該方法完成了ApplicationInfo中部分字段的賦值斯议,比如processName产捞、dataDir等。
//類似的還有fixPackageInfo方法哼御。
fixApplicationInfo(value.applicationInfo);
if (TextUtils.isEmpty(value.processName)) {
value.processName = value.packageName;
}
mActivityInfoCache.put(componentName, value);
}
List<IntentFilter> filters = mParser.readIntentFilterFromComponent(data);
synchronized (mActivityIntentFilterCache) {
mActivityIntentFilterCache.remove(componentName);
mActivityIntentFilterCache.put(componentName, new ArrayList<IntentFilter>(filters));
}
}
//......省略代碼
}
至此坯临,parser package的過程大致講完,如果遺漏的地方艇搀,歡迎指出尿扯。建議大家多讀下DroidPlugin關(guān)于包解析的源碼,作者某些思路相當精彩焰雕。
最后
不知道衷笋,大家有沒有一個疑問,其實我們可以通過getPackageArchiveInfo方法來獲取PackageInfo矩屁,然后通過類似fixApplicationInfo來完成包解析辟宗。但是作者并沒有這樣做,而是全部通過反射來完成吝秕。
我覺得應(yīng)該有以下考量泊脐。
1.全部使用反射獲取包信息,邏輯更清楚烁峭,而且更保障容客。
2.有些信息getPackageArchiveInfo獲取不到秕铛,例如PermissionGroupInfo、PermissionInfo缩挑、IntentFilterInfo等但两。
3.作者將PluginParserManager完成類似PackageManager功能,比如getActivityInfo(ComponentName,int)方法供置,必須通過反射獲取到PackageParser.Activity類實例谨湘。
如有疑問,歡迎交流芥丧!非常感謝紧阔!