前言
Android系统在启动过程中,会扫描特定目录,完成对apk的安装.PMS在这个过程中主要完成俩件事情: 1.解析apk的配置文件AndroidManifest.xml,获得其安装信息. 2.为apk分配其Linux用户id和Linux用户组id,以便其在系统中获得合适的运行权限.
APK的Linux用户id只可能有1个,但是可以拥有若干个Linux用户组id,以便于在系统中获得更多的资源访问权限,例如,读取联系人信息,使用摄像头,拨打电话等等权限.PMS在安装一个APK时,如果发现它申请了一个特定的用户组id,就会为其分配一个相应的用户组id.
相关代码路径
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
frameworks/base/core/java/android/content/pm/PackageParser.java
frameworks/base/services/core/java/com/android/server/pm/Settings.java
- PackageManagerService.java是用于管理应用程序的服务
- PackageParser.java主要包含解析apk的代码
- Settings.java用于存储应用程序的信息
安装过程分析
从PMS的静态main方法说起:
1.PackageManagerService.main
public static PackageManagerService main(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
// Self-check for initial settings.
PackageManagerServiceCompilerMapping.checkProperties();
PackageManagerService m = new PackageManagerService(context, installer,
factoryTest, onlyCore);
m.enableSystemUserPackages();
ServiceManager.addService("package", m);
return m;
}
PackageManagerService m = new PackageManagerService()启动了管理服务,ServiceManager.addService注册该服务到Service Manager,这样其他组件就可以通过Service Manager获得其访问接口.
2.PackageManagerService构造方法
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
- PMS类中有个成员变量mSettings,用于管理应用程序安装信息,例如应用程序的Linux的用户id和用户组id,以及manifest解析得到的应用程序信息.
- 由于Android系统每次重新启动,都会重新安装一遍系统的应用程序,像Linux的用户id这类的信息每次启动都需要保持一致,这就是上一条mSettings的作用.
- mSettings.readLPw方法用于恢复上一次应用程序的安装信息.
- 接着会调用scanDirTracedLI方法,会走到scanDirLI方法里,扫描安装apk,主要扫描的5个目录:/system/framework,/system/app,/vendor/app,/data/app和/data/app-private.
- 然后updatePermissionsLPw为申请了特定安装资源访问权限的应用程序分配相应的Linux用户组ID.
- 接着调用writeLPr将前面所获得的应用程序安装信息保存在本地的配置文件.
2.1 Settings.readLPw
Settings(File dataDir, Object lock) {
mSystemDir = new File(dataDir, "system");
mSystemDir.mkdirs();
.....
mSettingsFilename = new File(mSystemDir, "packages.xml");
mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");
mPackageListFilename = new File(mSystemDir, "packages.list");
}
boolean readLPw(@NonNull List<UserInfo> users) {
FileInputStream str = null;
if (mBackupSettingsFilename.exists()) {
try {
str = new FileInputStream(mBackupSettingsFilename);
mReadMessages.append("Reading from backup settings file\n");
PackageManagerService.reportSettingsProblem(Log.INFO,
"Need to read from backup settings file");
if (mSettingsFilename.exists()) {
// If both the backup and settings file exist, we
// ignore the settings since it might have been
// corrupted.
Slog.w(PackageManagerService.TAG, "Cleaning up settings file "
+ mSettingsFilename);
mSettingsFilename.delete();
}
} catch (java.io.IOException e) {
// We'll try for the normal settings file.
}
}
......
try {
if (str == null) {
if (!mSettingsFilename.exists()) {
mReadMessages.append("No settings file found\n");
PackageManagerService.reportSettingsProblem(Log.INFO,
"No settings file; creating initial state");
// It's enough to just touch version details to create them
// with default values
findOrCreateVersion(StorageManager.UUID_PRIVATE_INTERNAL);
findOrCreateVersion(StorageManager.UUID_PRIMARY_PHYSICAL);
return false;
}
str = new FileInputStream(mSettingsFilename);
}
XmlPullParser parser = Xml.newPullParser();
parser.setInput(str, StandardCharsets.UTF_8.name());
int type;
......
int outerDepth = parser.getDepth();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
String tagName = parser.getName();
if (tagName.equals("package")) {
readPackageLPw(parser);
} else if (tagName.equals("shared-user")) {
readSharedUserLPw(parser);
}
......
str.close();
......
}
- Settings类的成员变量mSettingsFilename和mBackupSettingsFilename分别指向系统中的文件/data/system/packages.xml和/data/system/packages-backup.xml.这两个文件用于保存上次的应用程序安装信息,其中backup是备份.
- if (mBackupSettingsFilename.exists())判断备份文件是否存在,如果存在,那么就会以它的内容作为上一次应用程序的安装信息.如果不存在则用/data/system/packages.xml作为上次应用程序的安装信息.
- 接着调用XmlPullParser解析xml文件,解析标签为package的信息,用到方法readPackageLPw,解析标签为shared-user的标签获得用于描述应用的共享Linux用户id,用到的方法是readSharedUserLPw.
2.2 Settings.readPackageLPw
private void readPackageLPw(XmlPullParser parser) throws XmlPullParserException, IOException {
name = parser.getAttributeValue(null, ATTR_NAME);
realName = parser.getAttributeValue(null, "realName");
idStr = parser.getAttributeValue(null, "userId");
uidError = parser.getAttributeValue(null, "uidError");
sharedIdStr = parser.getAttributeValue(null, "sharedUserId");
......
if (name == null) {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Error in package manager settings: <package> has no name at "
+ parser.getPositionDescription());
} else if (codePathStr == null) {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Error in package manager settings: <package> has no codePath at "
+ parser.getPositionDescription());
} else if (userId > 0) {
packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr),
new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiString,
secondaryCpuAbiString, cpuAbiOverrideString, userId, versionCode, pkgFlags,
pkgPrivateFlags, parentPackageName, null);
if (PackageManagerService.DEBUG_SETTINGS)
Log.i(PackageManagerService.TAG, "Reading package " + name + ": userId="
+ userId + " pkg=" + packageSetting);
if (packageSetting == null) {
PackageManagerService.reportSettingsProblem(Log.ERROR, "Failure adding uid "
+ userId + " while parsing settings at "
+ parser.getPositionDescription());
} else {
packageSetting.setTimeStamp(timeStamp);
packageSetting.firstInstallTime = firstInstallTime;
packageSetting.lastUpdateTime = lastUpdateTime;
}
} else if (sharedIdStr != null) {
userId = sharedIdStr != null ? Integer.parseInt(sharedIdStr) : 0;
if (userId > 0) {
packageSetting = new PendingPackage(name.intern(), realName, new File(
codePathStr), new File(resourcePathStr), legacyNativeLibraryPathStr,
primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString,
userId, versionCode, pkgFlags, pkgPrivateFlags, parentPackageName,
null);
packageSetting.setTimeStamp(timeStamp);
packageSetting.firstInstallTime = firstInstallTime;
packageSetting.lastUpdateTime = lastUpdateTime;
mPendingPackages.add((PendingPackage) packageSetting);
if (PackageManagerService.DEBUG_SETTINGS)
Log.i(PackageManagerService.TAG, "Reading package " + name
+ ": sharedUserId=" + userId + " pkg=" + packageSetting);
} else {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Error in package manager settings: package " + name
+ " has bad sharedId " + sharedIdStr + " at "
+ parser.getPositionDescription());
}
} else {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Error in package manager settings: package " + name + " has bad userId "
+ idStr + " at " + parser.getPositionDescription());
}
} catch (NumberFormatException e) {
...... }
......
}
- 从标签”package”的xml元素解析属性name,userId和sharedUserId,其中,属性name代表上一次安装该应用程序的包名,而userId和sharedUserId代表其独立Linux用户ID和共享Linux用户ID.
- if (userId > 0),检查是否应用程序上次安装被分配到一个独立的Linux用户id,如果是则调用addPackageLPw方法将其保存下来.
- else if (sharedIdStr != null),如果sharedIdStr不等于null,那说明并没有给应用程序分配独立的Linux用户id,而是让它与其他的应用程序共享一个Linux用户ID.PMS会创建一个PendingPackage对象,用于描述一个Linux用户id不确定的应用程序.者需要等到readSharedUserLPw解析共享userid完成后,才能确认该应用程序上一次使用的共享userid是否还有效.
2.3 Settings.addPackageLPw
下面看addPackageLPw方法如何保存上一次安装应用程序的Linux用户ID
PackageSetting addPackageLPw(String name, String realName, File codePath, File resourcePath,
String legacyNativeLibraryPathString, String primaryCpuAbiString,
String secondaryCpuAbiString, String cpuAbiOverrideString, int uid, int vc, int
pkgFlags, int pkgPrivateFlags, String parentPackageName,
List<String> childPackageNames) {
PackageSetting p = mPackages.get(name);
if (p != null) {
if (p.appId == uid) {
return p;
}
PackageManagerService.reportSettingsProblem(Log.ERROR,
"Adding duplicate package, keeping first: " + name);
return null;
}
p = new PackageSetting(name, realName, codePath, resourcePath,
legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString,
cpuAbiOverrideString, vc, pkgFlags, pkgPrivateFlags, parentPackageName,
childPackageNames);
p.appId = uid;
if (addUserIdLPw(uid, p, name)) {
mPackages.put(name, p);
return p;
}
return null;
}
- 在PMS中,每一个应用程序的安装信息都是使用一个PackageSetting对象来描述的.这些PackageSetting对象被保存在Settings类的成员变量mPackages所代表的一个ArrayMap中.
final ArrayMap<String, PackageSetting> mPackages = new ArrayMap<>();
String类型的Key值是应用程序的包名name.
- if (p != null) 首先在Settings类的成员变量mPackages中检查是否存在一个与键值name所对应的PackageSetting对象.如果存在,if (p.appId == uid),判断PackageSetting对象的成员变量userId是否等于参数uid的值,如果相等,那么就说明PMS已经为应用程序分配过一个Linux用户ID,直接返回p.
- 如果上述条件不存在,会创建一个PackageSetting对象p,用来描述Package名称等于name的应用程序的安装信息,p = new PackageSetting(…).之后将这个对象p的成员变量userId值设置为参数uid,最后调用addUserIdLPw方法在系统中保留值为uid的Linux用户ID.如果保留成功,那么会将对象p保存到Settings类的成员变量mPackages中.
2.4 Settings.addUserIdLPw
private boolean addUserIdLPw(int uid, Object obj, Object name) {
if (uid > Process.LAST_APPLICATION_UID) {
return false;
}
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++;
}
if (mUserIds.get(index) != null) {
PackageManagerService.reportSettingsProblem(Log.ERROR,
"Adding duplicate user id: " + uid
+ " name=" + name);
return false;
}
mUserIds.set(index, obj);
} else {
if (mOtherUserIds.get(uid) != null) {
PackageManagerService.reportSettingsProblem(Log.ERROR,
"Adding duplicate shared id: " + uid
+ " name=" + name);
return false;
}
mOtherUserIds.put(uid, obj);
}
return true;
}
- Android系统中,uid大于FIRST_APPLICATION_UID(10000),且小于LAST_APPLICATION_UID(19999)的Linux用户ID是保留给应用程序使用的,而小于10000的ID用于保留给特权用户使用.
- Settings类的成员变量mUserIds是一个类型为Arraylist的列表,用来维护那些已经分配给应用程序使用的Linux用户ID.如果保存在这个列表中的第index个位置的object对象不等于null,那么说明(FIRST_APPLICATION_UID+index)所对应的Linux用户ID已经被分配了.
- Settings类中的另一个成员变量mOtherUserIds是一个类型为SparseArray的稀疏矩阵,用于保存uid小于FIRST_APPLICATION_UID(10000)的应用程序.
3.1 Settings.readSharedUserLPw
private void readSharedUserLPw(XmlPullParser parser) throws XmlPullParserException,IOException {
String name = null;
String idStr = null;
int pkgFlags = 0;
int pkgPrivateFlags = 0;
SharedUserSetting su = null;
try {
name = parser.getAttributeValue(null, ATTR_NAME);
idStr = parser.getAttributeValue(null, "userId");
int userId = idStr != null ? Integer.parseInt(idStr) : 0;
if ("true".equals(parser.getAttributeValue(null, "system"))) {
pkgFlags |= ApplicationInfo.FLAG_SYSTEM;
}
if (name == null) {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Error in package manager settings: <shared-user> has no name at "
+ parser.getPositionDescription());
} else if (userId == 0) {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Error in package manager settings: shared-user " + name
+ " has bad userId " + idStr + " at "
+ parser.getPositionDescription());
} else {
if ((su = addSharedUserLPw(name.intern(), userId, pkgFlags, pkgPrivateFlags))
== null) {
PackageManagerService
.reportSettingsProblem(Log.ERROR, "Occurred while parsing settings at "
+ parser.getPositionDescription());
}
}
} catch (NumberFormatException e) {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Error in package manager settings: package " + name + " has bad userId "
+ idStr + " at " + parser.getPositionDescription());
}
if (su != null) {
int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
String tagName = parser.getName();
if (tagName.equals("sigs")) {
su.signatures.readXml(parser, mPastSignatures);
} else if (tagName.equals("perms")) {
readInstallPermissionsLPr(parser, su.getPermissionsState());
} else {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Unknown element under <shared-user>: " + parser.getName());
XmlUtils.skipCurrentTag(parser);
}
}
} else {
XmlUtils.skipCurrentTag(parser);
}
}
- 标签值等于”shared-user”的xml元素用于描述上一次安装应用程序时所分配的一个共享Linux一年用户的.其中属性name和userId描述一个共享Linux用户的名称和ID值,而属性system用来描述这个共享Linux用户ID是分配给一个系统类型的应用程序使用的,还是分配给一个用户类型的应用程序使用的.
- addSharedUserLPw(name.intern(), userId, pkgFlags, pkgPrivateFlags)方法为名称等于name的共享Linux用户保留一个值为userId的Linux用户ID.
3.2 Settings.addSharedUserLPw
SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
SharedUserSetting s = mSharedUsers.get(name);
if (s != null) {
if (s.userId == uid) {
return s;
}
PackageManagerService.reportSettingsProblem(Log.ERROR,
"Adding duplicate shared user, keeping first: " + name);
return null;
}
s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);
s.userId = uid;
if (addUserIdLPw(uid, s, name)) {
mSharedUsers.put(name, s);
return s;
}
return null;
}
- 同2.2类似的,每一个共享Linux用户使用一个SharedUserSetting对象来描述.这些SharedUserSetting对象被保存在Settings类的成员变量mSharedUsers所描述的一个ArrayMap中,并且是以它们所描述的共享Linux用户的名称为关键字.
- if (addUserIdLPw(uid, s, name)),如果uid保留成功,那么将SharedUserSetting对象s保存到mSharedUsers,这就表示PMS已经为名称等于name的共享Linux对象分配过Linux用户ID了.