本文主要内容
- pms简要介绍
- pms构造函数
上一篇文章中阐述了apk的安装过程,本文对pms的源码简要分析下
pms简要介绍
pms即PackageManagerService,它主要负责应用安装、卸载、更新等,在应用开机阶段还需要负责扫描已安装的应用,记录应用相关信息,比如应用中的activity、receiver等等,应用的odex优化,甚至为开发者提供接口,以便开发者查询应用名称,主icon等等各种信息
pms运行在system进程当中,在SystemServer类中启动。
mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
用户获取的PackageManager,经过android的封装,得到的是binder的proxy。而PackageManager是一个抽象类,它的子类是ApplicationPackageManager,而ApplicationPackageManager获得了pms binder的proxy对象,所以可以与pms进行IPC通信。
pms构造函数
在pms的main方法中,其实就是调用pms的构造方法而已
public static final PackageManagerService main(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
PackageManagerService m = new PackageManagerService(context, installer,
factoryTest, onlyCore);
ServiceManager.addService("package", m);
return m;
}
pms在构造方法中,主要扫描系统中几个特定文件夹下的apk,从而建立合适的数据结构来管理Package信息,四大组件信息,权限信息等(PKMS主要解析apk文件的AndroidManifest.xml文件
先看第1阶段,处理 packages.xml 等文件。
mSettings = new Settings(context);
mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false),
mSdkVersion, mOnlyCore);
Settings类很有意思,此处的Settings类与数据库读取的那个Settings无关,它主要用于保存各应用相关的信息。
Settings(Context context, File dataDir) {
mSystemDir = new File(dataDir, "system");
mSystemDir.mkdirs();
FileUtils.setPermissions(mSystemDir.toString(),
FileUtils.S_IRWXU|FileUtils.S_IRWXG
|FileUtils.S_IROTH|FileUtils.S_IXOTH,
-1, -1);
mSettingsFilename = new File(mSystemDir, "packages.xml");
mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");
mPackageListFilename = new File(mSystemDir, "packages.list");
FileUtils.setPermissions(mPackageListFilename, 0660, SYSTEM_UID, PACKAGE_INFO_GID);
// Deprecated: Needed for migration
mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");
mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
}
从它的构造方法中可以看出,它与packages.xml等文件密切相关。实质上,每个应用都会有一个uid,Settings类将保存各应用的uid及其它参数,在开机阶段也会去读取packages.xml等文件
回到pms的构造方法,查看addSharedUserLPw方法:
SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags) {
// 。。。。
s = new SharedUserSetting(name, pkgFlags);
s.userId = uid;
if (addUserIdLPw(uid, s, name)) {
mSharedUsers.put(name, s);
return s;
}
return null;
}
private boolean addUserIdLPw(int uid, Object obj, Object name) {
//根据应用uid,如果是非系统应用,则保存在mUserIds中,如果是系统应用则保存在mOtherUserIds中
if (uid >= Process.FIRST_APPLICATION_UID) {
int N = mUserIds.size();
final int index = uid - Process.FIRST_APPLICATION_UID;
while (index >= N) {
mUserIds.add(null);
N++;
}
mUserIds.set(index, obj);
} else {
mOtherUserIds.put(uid, obj);
}
return true;
}
在pms的构造方法中,后续还会调用 mSettings.readLPw 方法,在这个方法中将读取 packages.xml 等文件
boolean readLPw(PackageManagerService service, List<UserInfo> users, int sdkVersion,
boolean onlyCore) {
FileInputStream str = null;
if (mBackupSettingsFilename.exists()) {
str = new FileInputStream(mBackupSettingsFilename);
if (mSettingsFilename.exists()) {
//如果备份的文件存在,则读取备份文件并删除正常的文件
mSettingsFilename.delete();
}
}
try {
if (str == null) {
if (!mSettingsFilename.exists()) {
//如果既没有备份文件也没有正常文件,则异常
return false;
}
str = new FileInputStream(mSettingsFilename);
}
XmlPullParser parser = Xml.newPullParser();
parser.setInput(str, null);
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {
;
}
int outerDepth = parser.getDepth();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (tagName.equals("package")) {
readPackageLPw(parser);
}
//。。。
}
如果大家去看 /data/sysem/packages.xml 文件时,会发现有时候还有各种备份文件,文件名中包含backup等,这其实是在写文件时加的保险措施,在写文件之前,先将文件备份,如果写入失败,不删除备份文件,在下次开机的时候直接读取备份文件即可。如果写入成功,则删除备份文件。这种文件操作思路,值得借鉴,一般的写文件都用这种方式,包括硬盘缓存等
Settings类中包含的数据结构关系如下图所示:
完成这一步骤后,开始应用优化
byte dexoptRequired = DexFile.isDexOptNeededInternal(lib, null,
dexCodeInstructionSet, false);
if (dexoptRequired != DexFile.UP_TO_DATE) {
alreadyDexOpted.add(lib);
// The list of "shared libraries" we have at this point is
if (dexoptRequired == DexFile.DEXOPT_NEEDED) {
mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet);
} else {
mInstaller.patchoat(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet);
}
判断当前应用是否需要进行odex优化,如果需要则调用mInstaller完成。mInstaller通过socket调用 installerd 进程完成优化,关于installerd 可以见本人上一篇博文
这一步骤完成后,则是最重要的工作了,扫描应用安装的文件夹:
File vendorOverlayDir = new File(VENDOR_OVERLAY_DIR);
scanDirLI(vendorOverlayDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags | SCAN_TRUSTED_OVERLAY, 0);
我们来简要看看scanDirLI方法
private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
final File[] files = dir.listFiles();
for (File file : files) {
final boolean isPackage = (isApkFile(file) || file.isDirectory())
&& !PackageInstallerService.isStageName(file.getName());
try {
scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
scanFlags, currentTime, null);
} catch (PackageManagerException e) {
}
}
}
可以看到,scanDirLI方法遍历对应的文件夹,执行scanPackageLI方法。
在scanPackageLI中,系统收集比较应用签名等,完全性校验完成后,再度扫描应用文件。
final PackageParser.Package pkg;
try {
//解析androidMenifest文件
pkg = pp.parsePackage(scanFile, parseFlags);
} catch (PackageParserException e) {
throw PackageManagerException.from(e);
}
collectCertificatesLI(pp, ps, pkg, scanFile, parseFlags);
if (compareSignatures(ps.signatures.mSignatures, pkg.mSignatures)
PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanFlags
| SCAN_UPDATE_SIGNATURE, currentTime, user);
在parsePackage方法中,将解析AndroidMenifest.xml文件,解析出包名、所需要的权限,申明的四大组件等等,将结果封装在Package对象中。
关于四大组件的扫描,请参见 PackageParser.parseBaseApplication 方法,此方法解析xml文件并保存相关信息,太长了
经过上述几个步骤,pms的构造方法核心内容就介绍完毕了,阅读源码真的不要被过长的代码吓住了,要耐下心来,同时要非常注意源码中的英文注释,结合注释一起看,事半功倍。
虽然pms还有非常多的内容,但限于篇幅,本文也只讲了这么多。对于源码,相信每个程序员都是又爱又恨,关键是需要体会源码的设计精神,为什么要这么做,这些东西都是需要时间的打磨,当我们清楚pms或其它服务的源码时,细细体会,终有一天我们能大有收获。