本文首要内容
- 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或其它服务的源码时,细细领会,终有一天咱们能大有收成。