Android 中的包指的是Apk、jar和so文件等等,它们被加载到内存中,由一个包转变成可执行的代码,这就需要一个机制来进行包的加载、解析、管理等操作。管理这些包,是PackageManagerService 的核心,他负责对系统中所有的包进行管理,并与其他服务进行通信。
Android APK的安装过程,实际上就是对包进行解析,并将解析的包信息保存到settings和(内存)PMS 中各个组件列表的过程。
PMS 在启动过程中会对系统中的应用程序包进行解析并加载到内存,所以我们首先关注一下PMS的启动流程
PackageManagerService启动流程
启动PMS:
startBootstrapServices():
private void startBootstrapServices() {
mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
}
startOtherServices()
private void startOtherServices() {
if (!mOnlyCore) {
try {
//将调用performDexOpt:Performs dexopt on the set of packages
mPackageManagerService.updatePackagesIfNeeded();
}
try {
//执行Fstrim,执行磁盘维护操作,未看到详细的资料
//可能类似于TRIM技术,将标记为删除的文件,彻底从硬盘上移除
//而不是等到写入时再移除,目的是提高写入时效率
mPackageManagerService.performFstrimIfNeeded();
}
mPackageManagerService.systemReady();
}
}
整个system_server进程启动过程,涉及PMS服务的主要几个动作如下:
- ()
- ()
- ()
里面主要是构造PMS,我们直接看PMS的构造函数,里面做了大量的工作,我们化繁为简,重点关注几个点即可:
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
...
//Settings是Android的全局管理者,用于协助PMS保存所有的安装包信息
mSettings = new Settings(mPackages);
mSettings.addSharedUserLPw("", Process.SYSTEM_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
...
mInstaller = installer;
//用于dex优化
mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
"*dexopt*")
// 创建各种目录
File dataDir = Environment.getDataDirectory();
mAppInstallDir = new File(dataDir, "app");
mAppLib32InstallDir = new File(dataDir, "app-lib");
...
// 解析和"
mRestoredSettings = mSettings.readLPw(sUserManager.getUsers(false));
//dex 优化
mInstaller.dexopt(path, Process.SYSTEM_UID, dexCodeInstructionSet,
dexoptNeeded, DEXOPT_PUBLIC /*dexFlags*/);
//收集和解析系统APKS
scanDirLI(vendorOverlayDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags | SCAN_TRUSTED_OVERLAY, 0);
//信息写回文件
mSettings.writeLPr();
}
PMS Settings 和scanDirLI 扫描系统apks 是本文的研究重点:
Package Settings
Settings是Android的全局管理者,用于协助PMS保存所有的安装包信息
frameworks/base/services/core/java/com/android/server/pm/
先看看起构造函数
Settings(File dataDir, PermissionSettings permission, Object lock) {
mLock = lock;
mPermissions = permission;
mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);
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, "");
mBackupSettingsFilename = new File(mSystemDir, "");
mPackageListFilename = new File(mSystemDir, "");
FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID);
final File kernelDir = new File("/config/sdcardfs");
mKernelMappingFilename = kernelDir.exists() ? kernelDir : null;
// Deprecated: Needed for migration
mStoppedPackagesFilename = new File(mSystemDir, "");
mBackupStoppedPackagesFilename = new File(mSystemDir, "");
}
:
PKMS 扫描完目标文件夹后会创建该文件。当系统进行程序安装、卸载和更新等操作时,均会更新该文件。该文件保存了系统中与 package 相关的一些信息。
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<packages>
<version ... />
<version ... />
<permissions>
<item name="xxxS" package="xxx" protection="xx" />
... ...
</permissions>
<package xxx>
...
</package>
...
<shared-user xxx>
...
</shared-user>
...
<keyset-settings version="1">
...
</keyset-settings>
</packages>
文件中主要的信息分为下面几个部分:
permission: 里面包含了系统中所有定义的权限的信息
package:里面包含了系统中所有安装的app的详细信息
shared-user:里面包含了所有系统定义的shareuser的信息
keyset-settings:里面包含了已安装app签名的public key信息
:
描述系统中存在的所有非系统自带的 APK 的信息。当这些程序有变动时,PKMS 就会更新该文件。
:
从系统自带的设置程序中进入应用程序页面,然后在选择强制停止(ForceStop)某个应用时,系统会将该应用的相关信息记录到此文件中。也就是该文件保存系统中被用户强制停止的 Package 的信息
readLPw()函数,从/data/system/或文件中获得packages、permissions相关信息,添加到相关内存列表中。文件记录了系统的permisssions以及每个APK的name、codePath、flags、version等信息这些信息主要通过APK的解析获取,解析完APK后将更新信息写入这个文件,下次开机直接从里面读取相关信息添加到内存相关结构中。当有APK升级、安装或删除时会更新这个文件。
writeLPr函数,将解析出的每个APK的信息()保存到和文件。记录了如下数据:pkgName, userId, debugFlag, dataPath(包的数据路径)。
Package scanDirLI
private void scanDirLI(File dir, final 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());
if (!isPackage) {
// Ignore entries which are not packages
continue;
}
try {
//处理目录下每一个package文件
scanPackageTracedLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
scanFlags, currentTime, null);
} catch (PackageManagerException e) {
.........
}
}
}
scanPackageTracedLI函数最终会调用到scanPackageLI函数:
rivate PackageParser.Package scanPackageLI(FilescanFile, int parseFlags,
int scanMode, long currentTime) {
mLastScanError = PackageManager.INSTALL_SUCCEEDED;
StringscanPath = scanFile.getPath();
parseFlags |= mDefParseFlags;//默认的扫描标志,正常情况下为0
//创建一个 PackageParser 对象
PackageParser pp = new PackageParser(scanPath);
pp.setSeparateProcesses(mSeparateProcesses);// mSeparateProcesses 为空
pp.setOnlyCoreApps(mOnlyCore);// mOnlyCore 为 false
/*
调用 PackageParser 的 parsePackage 函数解析APK文件。注意,这里把代表屏幕
信息的 mMetrics 对象也传了进去
*/
finalPackageParser.Package pkg = pp.parsePackage(scanFile,
scanPath, mMetrics, parseFlags);
//...
PackageSetting ps = null;
PackageSetting updatedPkg;
//...
/*
这里略去一大段代码,主要是关于 Package 升级方面的工作。
*/
//收集签名信息,这部分内容涉及 signature。
if (!collectCertificatesLI(pp, ps, pkg, scanFile, parseFlags))
return null;
//判断是否需要设置 PARSE_FORWARD_LOCK 标志,这个标志针对资源文件和 Class 文件
//不在同一个目录的情况。目前只有 /vendor/app 目录下的扫描会使用该标志。这里不讨论
//这种情况。
if (ps != null && !ps.codePath.equals(ps.resourcePath))
parseFlags |= PackageParser.PARSE_FORWARD_LOCK;
String codePath = null;
String resPath = null;
if ((parseFlags & PackageParser.PARSE_FORWARD_LOCK) != 0) {
//...//这里不考虑 PARSE_FORWARD_LOCK的情况。
} else {
resPath = pkg.mScanPath;
}
codePath = pkg.mScanPath;//mScanPath 指向该 APK 文件所在位置
//设置文件路径信息,codePath 和 resPath 都指向 APK 文件所在位置
setApplicationInfoPaths(pkg, codePath, resPath);
//调用第二个 scanPackageLI 函数
return scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_SIGNATURE,
currentTime);
}
在函数最后会调用另一个同名的scanPackageLI,我们后面再分析,先看看PackageParser的内容
PackageParser 主要负责 APK 文件的解析,即解析 APK 文件中的 代码如下:
public Package parsePackage(File sourceFile, String destCodePath,
DisplayMetrics metrics, int flags) {
mParseError = PackageManager.INSTALL_SUCCEEDED;
mArchiveSourcePath = sourceFile.getPath();
//...//检查是否为 APK 文件
XmlResourceParser parser = null;
AssetManager assmgr = null;
Resources res = null;
boolean assetError = true;
try {
assmgr = new AssetManager();
int cookie = assmgr.addAssetPath(mArchiveSourcePath);
if (cookie != 0) {
res = new Resources(assmgr, metrics, null);
assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT);
/*
获得一个 XML 资源解析对象,该对象解析的是 APK 中的 文件。
以后再讨论 AssetManager、Resource 及相关的知识
*/
parser = assmgr.openXmlResourceParser(cookie,
ANDROID_MANIFEST_FILENAME);
assetError = false;
} //...//出错处理
String[] errorText = new String[1];
Package pkg = null;
Exception errorException = null;
try {
//调用另外一个 parsePackage 函数
pkg = parsePackage(res, parser, flags, errorText);
}//...
//...//错误处理
parser.close();
assmgr.close();
//保存文件路径,都指向 APK 文件所在的路径
pkg.mPath = destCodePath;
pkg.mScanPath = mArchiveSourcePath;
pkg.mSignatures = null;
return pkg;
}
}
实际上调用了另一个同名的 parsePackage 函数
private Package parsePackage(Resources res, XmlResourceParser parser, int flags, String[] outError)
throws XmlPullParserException, IOException {
AttributeSet attrs = parser;
mParseInstrumentationArgs = null;
mParseActivityArgs = null;
mParseServiceArgs = null;
mParseProviderArgs = null;
//得到 Package 的名字,其实就是得到 中 package 属性的值,
//每个 APK 都必须定义该属性
String pkgName = parsePackageName(parser, attrs, flags, outError);
//...
int type;
//...
//以 pkgName 名字为参数,创建一个 Package 对象。后面的工作就是解析 XML 并填充
//该 Package 信息
final Package pkg = new Package(pkgName);
boolean foundApp = false;
//...//下面开始解析该文件中的标签,由于这段代码功能简单,所以这里仅列举相关函数
while (如果解析未完成) {
//...
StringtagName = parser.getName(); //得到标签名
if (tagName.equals("application")) {
//...//解析 application 标签
parseApplication(pkg, res, parser, attrs, flags, outError);
} else if (tagName.equals("permission-group")) {
//...//解析 permission-group 标签
parsePermissionGroup(pkg, res, parser, attrs, outError);
} else if (tagName.equals("permission")) {
//...//解析 permission 标签
parsePermission(pkg, res, parser, attrs, outError);
} else if (tagName.equals("uses-permission")) {
//从 XML 文件中获取 uses-permission 标签的属性
sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestUsesPermission);
//取出属性值,也就是对应的权限使用声明
String name = sa.getNonResourceString(com.android.internal.
R.styleable.AndroidManifestUsesPermission_name);
//添加到 Package 的 requestedPermissions 数组
if (name != null && !pkg.requestedPermissions.contains(name)) {
pkg.requestedPermissions.add(name.intern());
}
} else if (tagName.equals("uses-configuration")) {
/*
该标签用于指明本 package 对硬件的一些设置参数,目前主要针对输入设备(触摸屏、键盘
等)。游戏类的应用可能对此有特殊要求。
*/
ConfigurationInfocPref = new ConfigurationInfo();
//...//解析该标签所支持的各种属性
pkg.configPreferences.add(cPref);//保存到 Package 的 configPreferences 数组
}
//...//对其他标签解析和处理
}
}
在 PackageParser 扫描完一个 APK 后,此时系统已经根据该 APK 中 ,创建了一个完整的 Package 对象,下一步就是将该 Package 加入到系统中。此时调用的函数就是另外一个 scanPackageLI
private PackageParser.Package scanPackageLI(
PackageParser.Package pkg, int parseFlags, int scanMode, long currentTime) {
FilescanFile = new File(pkg.mScanPath);
//...
mScanningPath = scanFile;
//设置 package 对象中 applicationInfo 的 flags 标签,用于标示该 Package 为系统
//Package
if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) != 0) {
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
}
//下面这句 if 判断极为重要,见下面的解释
if (pkg.packageName.equals("android")) {
synchronized (mPackages) {
if (mAndroidApplication != null) {
//...
mPlatformPackage = pkg;
pkg.mVersionCode = mSdkVersion;
mAndroidApplication = pkg.applicationInfo;
mResolveActivity.applicationInfo = mAndroidApplication;
mResolveActivity.name = ResolverActivity.class.getName();
mResolveActivity.packageName = mAndroidApplication.packageName;
mResolveActivity.processName = mAndroidApplication.processName;
mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
mResolveActivity.theme = com.android.internal.R.style.Theme_Holo_Dialog_Alert;
mResolveActivity.exported = true;
mResolveActivity.enabled = true;
//mResoveInfo 的 activityInfo 成员指向 mResolveActivity
mResolveInfo.activityInfo = mResolveActivity;
mResolveInfo.priority = 0;
mResolveInfo.preferredOrder = 0;
mResolveInfo.match = 0;
mResolveComponentName = new ComponentName(
mAndroidApplication.packageName, mResolveActivity.name);
}
}
}
}