最近在研究DroidPlugin源码及其实现机制,读这种大神级的代码甚是费脑。我基本是逐行阅读代码,为求甚解,花了不少时间和心思。这里,我也做个笔记供大家参考,这样就可以少走弯路。
Run it
为了解其工作DroidPlugin的工作原理,我们第一步应该是把Demo运行起来,其项目地址是:DroidPlugin。作者的Demo比较复杂,毕竟他要测试到所有的功能。所以这里,建议各位自己重写一个简单的Demo,然后运行起来。这样起码你知道如何使用DroidPlugin了。
在Demo中,与我们打交道的类是PluginManager,用该类中以下方法就可以完成最基本的插件调用。
//判断插件服务是否处于连接状态
public boolean isConnected() {
return mHostContext != null && mPluginManager != null;
}
//将插件的ServiceConnection添加到缓存中
public void addServiceConnection(ServiceConnection sc) {
sServiceConnection.add(new WeakReference(sc));
}
//将插件的ServiceConnection从缓存中移除
public void removeServiceConnection(ServiceConnection sc) {
Iterator> iterator = sServiceConnection.iterator();
while (iterator.hasNext()) {
WeakReference 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相关的类图。
从类图中我们可以看到,整个插件管理器是通过Binder机制来完成运作的。绝大部分插件管理相关的方法都在IPluginManagerImpl类中实现。
获取插件Apk的基本信息
Apk包信息用PackageInfo来表示,通过PackageInfo我们可以获取包名、版本号、版本代码以及四大组件相关信息等。通过以下类图,我们可以知道包信息相关的内容。
我们知道,要获取一个Apk的包相关信息,可以通过PackageManager来获取,但是这仅限于已经安装的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();
/**
* 调用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);
/**
* 这里是是判断是否允许替换相同的插件包,这种写法应该是模仿系统的写法。
* 可以通过PackageManager的getPackageArchiveInfo方法源码来看。
* 通过标志位与系统预设类变量进行与运算
* */
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,作者基本上把所有组件相关的信息都解析出来了,牛逼!
* 所以作者写了PluginPackageParser这个类来获取四大组件、签名、权限等相关信息。
* */
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;
}
}
}
/**
* 保存签名信息到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));
//优化dex
dexOpt(mContext, apkfile, parser);
//缓存已经解析出来插件的PluginPackageParser信息(类似PackageInfo)
mPluginCache.put(parser.getPackageName(), parser);
//回调方法,通知已经完成
mActivityManagerService.onPkgInstalled(mPluginCache, parser, parser.getPackageName());
//发送安装完成的广播
sendInstalledBroadcast(info.packageName);
return PackageManagerCompat.INSTALL_SUCCEEDED;
}
//以下分析与上述通覆盖安装同理
else {
//代码省略........
}
}
通过以上分析,我们可以知道,安装插件主要做了解析插件apk的PackageInfo,获取证书信息,读取so文件,加载dex等。前面我们有提到过使用getPackageArchiveInfo可以获取插件的PackageInfo,但是在installPackage方法中有PluginPackageParser类实例创建的过程,用PluginPackageParser来完成包相关信息的解析。
PluginPackageParser主要是通过反射来获取PackageInfo,而且还要做版本适配,很复杂。那为什么作者不直接getPackageArchiveInfo方法获取的PackageInfo?
上面有介绍到PackageInfo类相关的类图,其实整个PackageInfo所包含的信息是相当多的。我们通过getPackageArchiveInfo获取插件的PackageInfo仅仅是读取AndroidManifest.xml文件上的信息,有些信息是无法获取,比如ApplicationInfo中的processName、publicSourceDir、sourceDir,PackageInfo中的gids等,这些信息都是需要安装或运行后才能确定的。
下面,首先来看看PluginPackageParser相关的类图
整个解析过程,看似很复杂,但是只要掌握了核心思想就很好理解了。
PluginPackageParser分析
我们继续分析上面提到过的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);
}
//每个用户关于package的状态信息,例如package是否安装,是否隐藏,是否停止等
PackageUserState state = new PackageUserState();
//调用generatePackageInfo方法返回PackageInfo
return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state);
} catch (PackageParserException e) {
return null;
}
}
/**
*从以上代码分析中,我们可以看到PackageParser是完成包解析的核心步骤,接着分析
*PackageParser类中的generatePackageInfo方法,因为generatePackageInfo方法在PackageParser中重载了,
*我们直接看该方法最终实现
**/
public static PackageInfo generatePackageInfo(PackageParser.Package p,
int gids[], int flags, long firstInstallTime, long lastUpdateTime,
Set grantedPermissions, PackageUserState state, int userId) {
if (!checkUseInstalledOrHidden(flags, state)) {
return null;
}
//创建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是通过调用generateActivityInfo方法,其他组件或包相关信息在PackageParser都有对应的生产方法。
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
通过以上简要分析,我们可以知道在PackageParser类中有很多可以获取包信息的方法,比如获取PackageInfo的generatePackageInfo,获取ServiceInfo的generateServiceInfo等,这些方法的名字绝大部分都是以generate开头的。
DroidPlugin作者通过反射的方法来获取PackageInfo、ActivityInfo等信息,因为PackageParser是hide类,所以每个系统版本获取包相关信息的实现可能不一样,所以作者做了版本兼容,这需要读完各个系统版本PackageParser类源码,辛苦作者了!
作者自己也定义了一个PackageParser类,用于完成与系统PackageParser类类似的功能。在PluginPackageParser的构造方法中通过调用自定义PackageParser类的newPluginParser方法来获取包相关信息。
public PluginPackageParser(Context hostContext, File pluginFile) throws Exception {
mHostContext = hostContext;
mPluginFile = pluginFile;
//通过反射系统PackageParser相关方法来完成包相关信息解析
mParser = PackageParser.newPluginParser(hostContext);
//解析插件包
mParser.parsePackage(pluginFile, 0);
mPackageName = mParser.getPackageName();
mHostPackageInfo = mHostContext.getPackageManager().getPackageInfo(mHostContext.getPackageName(), 0);
//以下操作是将解析到的ActivityInfo进行缓存操作。
//下面代码出现ComponentName,我想作者是为了模仿系统PackageManager类中相关的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 filters = mParser.readIntentFilterFromComponent(data);
synchronized (mActivityIntentFilterCache) {
mActivityIntentFilterCache.remove(componentName);
mActivityIntentFilterCache.put(componentName, new ArrayList(filters));
}
}
//......省略代码
}
至此,parser package的过程大致讲完,如果遗漏的地方,欢迎指出。建议大家多读下DroidPlugin关于包解析的源码,作者某些思路相当精彩。
最后
不知道,大家有没有一个疑问,其实我们可以通过getPackageArchiveInfo方法来获取PackageInfo,然后通过类似fixApplicationInfo来完成包解析。但是作者并没有这样做,而是全部通过反射来完成。
我觉得应该有以下考量。
1.全部使用反射获取包信息,逻辑更清楚,而且更保障。
2.有些信息getPackageArchiveInfo获取不到,例如PermissionGroupInfo、PermissionInfo、IntentFilterInfo等。
3.作者将PluginParserManager完成类似PackageManager功能,比如getActivityInfo(ComponentName,int)方法,必须通过反射获取到PackageParser.Activity类实例。
如有疑问,欢迎交流!非常感谢!