#############################################
本文为极度寒冰原创,转载请注明出处 #############################################
1 PackageManager
/*常用命令*/
adb shell dumpsys package (dump出系统中所有的application信息)
adb shell dumpsys package “com.android.contacts” p (dump出系统中特定包名的application信息)
/*常用命令*/
一: PackageManager服务
在Android系统中,和用户关系最密切的service应该是PackageManager了。
一般来说,用户想要在Android Phone上进行自己感兴趣的活动,都少不了apk的支持。
不论是打电话,上网,发短信还是玩一些自己喜欢的游戏,这些内容在android的世界里都是以apk的形式存在的。
所以,apk的安装,卸载是与每个用户息息相关的。
我们接下来会用一些文章去解析PackageManager的工作原理,apk的安装和卸载的过程。
在SystemServer中,经过前面的分析,我们知道了启动Android的系统关键服务的函数首先就是startBootstrapServices.
private void startBootstrapServices() {
// Wait for installd to finish starting up so that it has a chance to
// create critical directories such as /data/user with the appropriate
// permissions. We need this to complete before we initialize other services.
mInstaller = mSystemServiceManager.startService(Installer.class); // Install是pm路径下面的一个单独的类,主要用于通过InstallerConnection建立和installd的链接
// 然后Installd会进行创建一些系统关键目录的作用,所以我们要等待Installd的结束,才可以继续进行其它的创建
.....
// Only run "core" apps if we're encrypting the device.
String cryptState = SystemProperties.get("vold.decrypt");
if (ENCRYPTING_STATE.equals(cryptState)) {
Slog.w(TAG, "Detected encryption in progress - only parsing core apps");
mOnlyCore = true;
} else if (ENCRYPTED_STATE.equals(cryptState)) {
Slog.w(TAG, "Device encrypted - only parsing core apps");
mOnlyCore = true;
}
// Start the package manager.
Slog.i(TAG, "Package Manager");
// mOnlyCore 用于判断是否只扫描系统的目录
mPackageManagerService = PackageManagerService.main(mSystemContext, mInstaller,
mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore); // mFactoryTestMode 用于判断是否为工厂测试,正常情况下mOnlyCore = false
mFirstBoot = mPackageManagerService.isFirstBoot();
mPackageManager = mSystemContext.getPackageManager();
.....
}
在经过systemServer的启动后,我们知道了系统将会通过PackageManagerService的main函数进入到PackageManager的实际工作中。
PackageManagerService的main函数其实很简单,只有一点点的内容。
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;
}
我们知道了PackageManager的main函数其实主要的实现是通过PackageManagerService的构造函数实现的。
那么构造函数的实现也是比较多的,做了很多耗时的工作。
其中,PackageManager的构造函数的主要功能为:
1. 扫描Android系统中几个目标文件夹中的apk,并且建立相应的数据结构去管理Package的信息,四大组件的信息,权限信息等内容
2. 解析物理的apk的文件,生成符合自己需求的数据结构。
所以,学习PackageManager最重要的就是学习保存各种信息的数据结构和他们之间的关系,以及控制的策略
———————————————————————————————————————————————————————————–
二: PackageManager的构造函数之前期准备
/*UID和GID*/
如果你是一个Linux的用户,那么一定会有如下的体验。当你的Linux的系统里面,存在多个用户的时候,你在登录了用户A之后,是没有权限去修改用户B的文件内容的。
再举个极端的例子,用普通用户的权限是没有办法进行Root用户所特有的操作的。
UID: Linux系统用于区别不同的用户,使用不同的用户名是一种方法。但是用户名只是一种让人方便读的字符串,对机器来讲是没有意义的。
为了方便机器的读取,Linux采用了一个32位的整数记录和区分不同的用户。这个用来区分不同用户的数字被称为User ID,简称UID。
Root的UID为0,普通的UID一般是从500开始。Android系统不同,后续会进行分析
GID: 除了用户的概念,Linux的系统还有用户组的概念。同一个用户组的用户之间具有相似的特征。
假如我们把某一个用户加入到root组,那么这个用户就可以浏览root用户组目录的文件。 如果root用户把某个文件的读写执行权限开放,root用户组的所有用户都可以修改该文件。
/*PackageManager 构造函数解析*/
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,
SystemClock.uptimeMillis());
if (mSdkVersion <= 0) { // 在最开始,我们会去检查sdk的version,因为如果检查不到sdk的话,apk就不知道运行在哪个版本上
Slog.w(TAG, "**** ro.build.version.sdk not set!");
}
mContext = context;
mFactoryTest = factoryTest;
mOnlyCore = onlyCore;
mLazyDexOpt = "eng".equals(SystemProperties.get("ro.build.type")); //判断build的类型,一般为eng,user版本,eng的版本一般是不会进行odex的优化的
mMetrics = new DisplayMetrics(); // 初始化一个DisplayMetrics,用于保存屏幕像素的参数
mSettings = new Settings(context); // new了一个Settings的对象,这个settings是pm里面的,主要是保存系统apk的相关设置,互相之间关系等内容 --1
mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED); // --2
mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,
ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.log", LOG_UID,
ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID,
ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID,
ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID,
ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
String separateProcesses = SystemProperties.get("debug.separate_processes");
if (separateProcesses != null && separateProcesses.length() > 0) {
if ("*".equals(separateProcesses)) {
mDefParseFlags = PackageParser.PARSE_IGNORE_PROCESSES;
mSeparateProcesses = null;
Slog.w(TAG, "Running with debug.separate_processes: * (ALL)");
} else {
mDefParseFlags = 0;
mSeparateProcesses = separateProcesses.split(",");
Slog.w(TAG, "Running with debug.separate_processes: "
+ separateProcesses);
}
} else {
mDefParseFlags = " + mDefParseFlags + " mSeparateProcesses " + mSeparateProcesses);
}
mInstaller = installer;
getDefaultDisplayMetrics(context, mMetrics);
SystemConfig systemConfig = SystemConfig.getInstance();
mGlobalGids = systemConfig.getGlobalGids();
mSystemPermissions = systemConfig.getSystemPermissions();
mAvailableFeatures = systemConfig.getAvailableFeatures();
......
}
/* –1 Settings的分析*/
/*
Settings的构造函数如下,在构造函数中主要是创建了/data/system的目录,并且创建了packages.xml, packages-backup.xml, packages.list, packages-stopped.xml等文件。
这些文件又都有一些具体的作用:
packages.xml: 是保存了系统所有的Package信息
packages-backup.xml: 是packages.xml的备份,防止在写packages.xml突然断电
packages.list: 保存了系统中已经安装的apk,以及对应的data/data/下面的对应关系
packages-stopped.xml: 用于记录系统中强制停止运行的Package信息
packages-stopped-backup.xml: 是packages-stopped.xml的备份,防止在写packages-stopped-backup的时候突然断电
*/
Settings(Context context) {
this(context, Environment.getDataDirectory());
}
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");
}
/*在初始化了Settings的文件之后,就进行了addSharedUserLPw的操作*/
/*
mSharedUsers是一个hashmap,HashMap<String, SharedUserSetting>,保存的是string和SharedUserSetting的对应
SharedUserSetting类继承自GrantedPermissions,包含了如下的五个内部变量:
final String name;
int userId;
// flags that are associated with this uid, regardless of any package flags
int uidFlags;
final HashSet<PackageSetting> packages = new HashSet<PackageSetting>();
final PackageSignatures signatures = new PackageSignatures();
PackageSetting 继承自PackageSettingBase,主要有下面的三个成员变量:
int appId;
PackageParser.Package pkg;
SharedUserSetting sharedUser;
*/
SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags) {
SharedUserSetting s = mSharedUsers.get(name); // 从当前的mSharedUsers里面寻找对应名字的字符串是否已经有了对应
if (s != null) { // 如果已经存在的话,就返回对应的UID
if (s.userId == uid) {
return s;
}
// 如果从hashmap里面找到了这个String name,但是两个的uid却不一样的话,那么就会报错。因为一个shared user不可能有两个UID,只会保留第一个
PackageManagerService.reportSettingsProblem(Log.ERROR,
"Adding duplicate shared user, keeping first: " + name);
return null;
}
s = new SharedUserSetting(name, pkgFlags); // 如果从mSharedUsers里面没有找到这个name的话,就会去新建一个
s.userId = uid; // 赋值UID
if (addUserIdLPw(uid, s, name)) { // 将index和sharedUserSettings保存在mUserIds和mOtherUserIds
mSharedUsers.put(name, s); // 讲name和sharedUserSettings保存在mSharedUsers
return s;
}
return null;
}
/*接下来就会进入到addUserIdLPw的函数中*/
/*
UID前面讲了,是Linux的一种安全机制。
在android系统里面,继承并扩展了这种安全机制。
Android的系统,不同的应用程序之间原则上是不可以互相访问数据的。除非运行在同一个进程,或者通过contentProvider或其他的机制。
这个安全机制的主要原因,也就是android为每一个app都分配了一个UID,每个app从Linux的层面上面,都是一个独立的用户。
所以可以解释了这个安全机制的实现原理。
*/
private boolean addUserIdLPw(int uid, Object obj, Object name) {
if (uid > Process.LAST_APPLICATION_UID) { // 系统为app分配的UID为10000 - 19999,如果超出了这个范围就会报错
return false;
}
/*
mUserIds是一个ArrayList,也就是一个动态的数组
private final ArrayList<Object> mUserIds = new ArrayList<Object>();
举个例子,如果是uid=10001的用户,说明是一个app,那么这个时候就会进入到app的相应处理中去
会去读去mUserIds的size.
在第一次读取的时候,N肯定是为0的。但是index肯定不会为0,因为在apk安装的时候就会为其分配一个UID
所以在第一次读取的时候,index一定是>=N的。
这个时候,会add null,其实就是什么也没有做,而只是对N进行++
所以,N的数字,一定是比前面遍历过得apk的uid的index大1的
N只是用来统计当前的ArrayList里面的apk,index的数目
mOtherUserIds的定义如下:是一个SparseArray。可以通过put,append两种方式写入数据
private final SparseArray<Object> mOtherUserIds = new SparseArray<Object>();
*/
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++;
}
/*如果通过index从mUserIds里面读取到了数据,那肯定是错误的。因为一个app只有一个index,且只能被赋值一次*/
if (mUserIds.get(index) != null) {
PackageManagerService.reportSettingsProblem(Log.ERROR,
"Adding duplicate user id: " + uid
+ " name=" + name);
return false;
}
mUserIds.set(index, obj); // 将当前的obj设置到ArrayList对应的index中
} 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;
}
/*
mUserIds是一个ArrayList,也就是一个动态的数组
private final ArrayList<Object> mUserIds = new ArrayList<Object>();
举个例子,如果是uid=10001的用户,说明是一个app,那么这个时候就会进入到app的相应处理中去
会去读去mUserIds的size.
在第一次读取的时候,N肯定是为0的。但是index肯定不会为0,因为在apk安装的时候就会为其分配一个UID
所以在第一次读取的时候,index一定是>=N的。
这个时候,会add null,其实就是什么也没有做,而只是对N进行++
所以,N的数字,一定是比前面遍历过得apk的uid的index大1的
N只是用来统计当前的ArrayList里面的apk,index的数目
mOtherUserIds的定义如下:是一个SparseArray。可以通过put,append两种方式写入数据
private final SparseArray<Object> mOtherUserIds = new SparseArray<Object>();
*/
/*
通过上述的分析,我们可以知道在PackageManager的构造函数中,先将系统的一些关键进程的UID进行了添加
为什么这样做呢?举个例子
在Settings里面,AndroidManifest里面有如下的声明:
<manifest xmlns:android=”http://schemas.android.com/apk/res/android”
package=”com.android.settings”
coreApp=”true”
android:sharedUserId=”android.uid.system”>
关注的焦点为sharedUserId为android.uid.system,设置这个可以使生成的apk能够获取system权限。
可以在任意system权限目录下面进行目录或者文件的创建,以及访问其他apk资源等
进行这个设置,也应该是让后面的app的uid在进行添加的时候,可以找到对应的android.uid.system的权限
然后在实际添加的过程中,调用了addSharedUserLPw函数,在这个函数中,我们会去检查是否当前的UID已经在mSharedUsers中存在,如果存在,就返回已经存在的对象。如果UID不一致,则会报错。
如果不存在的话,就会去new一个SharedUserSetting的对象。
在SharedUserSetting中,主要存在以下的对象。
final String name; –> name
int userId; –> 对应的UID
int uidFlags; –> 对应的FLAG
final HashSet<PackageSetting> packages = new HashSet<PackageSetting>(); –> 一个PackageSetting的对象,
final PackageSignatures signatures = new PackageSignatures(); –> 签名
在new出来SharedUserSetting的对象后,会将传递进来的UID进行赋值给userId,这样的话,结合构造函数,就完成了对
name, userId, uidFlags的赋值
然后执行addUserIdLPw函数,会将当前的SharedUserSetting对象和index进行关联,如果是app的话存入到了mUserIds的动态数组,如果是系统的userId的话,将对应的SharedUserSetting存入到了mOtherUserIds的hashmap中
*/
/*
在构造函数中,下面的code部分也是非常重要的
*/
SystemConfig systemConfig = SystemConfig.getInstance(); // 单例模式,获取SystemConfig的实例
mGlobalGids = systemConfig.getGlobalGids();
mSystemPermissions = systemConfig.getSystemPermissions();
mAvailableFeatures = systemConfig.getAvailableFeatures();
/*
SystemConfig 的构造函数如下,可以看到,主要是去读取了一些系统的权限相关的信息
读取的路径是/ststem/etc/目录
*/
SystemConfig() {
// Read configuration from system
readPermissions(Environment.buildPath(
Environment.getRootDirectory(), "etc", "sysconfig"), false);
// Read configuration from the old permissions dir
readPermissions(Environment.buildPath(
Environment.getRootDirectory(), "etc", "permissions"), false);
// Only read features from OEM config
readPermissions(Environment.buildPath(
Environment.getOemDirectory(), "etc", "sysconfig"), true);
readPermissions(Environment.buildPath(
Environment.getOemDirectory(), "etc", "permissions"), true);
}
/*
那么接下来去看一下readPermissions的实现
*/
void readPermissions(File libraryDir, boolean onlyFeatures) {
// Read permissions from given directory.
if (!libraryDir.exists() || !libraryDir.isDirectory()) { // 如果文件夹不存在,或者不为一个文件夹的时候
if (!onlyFeatures) { // 并且读取onlyFeatures的值为flase的时候
Slog.w(TAG, "No directory " + libraryDir + ", skipping");
}
return;
} // /system/etc/sysconfig 会进入到No directory skipping
if (!libraryDir.canRead()) { // 如果文件夹不可读的话,会返回
Slog.w(TAG, "Directory " + libraryDir + " cannot be read");
return;
}
// Iterate over the files in the directory and scan .xml files
for (File f : libraryDir.listFiles()) { // 遍历目录下面的文件夹
// We'll read platform.xml last
if (f.getPath().endsWith("etc/permissions/platform.xml")) { // 最后再去处理platform.xml
continue;
}
if (!f.getPath().endsWith(".xml")) { // 如果文件的路径不是xml结尾的话, 会继续读取其他的文件
Slog.i(TAG, "Non-xml file " + f + " in " + libraryDir + " directory, ignoring");
continue;
}
if (!f.canRead()) { // 如果文件不可读的话,会返回继续读取其它的文件
Slog.w(TAG, "Permissions library file " + f + " cannot be read");
continue;
}
readPermissionsFromXml(f, onlyFeatures);
}
// Read permissions from .../etc/permissions/platform.xml last so it will take precedence
final File permFile = new File(Environment.getRootDirectory(),
"etc/permissions/platform.xml");
readPermissionsFromXml(permFile, onlyFeatures);
}
然后最终会通过readPermissionsFromXml去进行文件的读取
/*
* 正如前面的文章分析init.rc时,我们知道针对这个文本的解析,都会有一个文本的解析器。
* 主要是对文本中的各个标签进行解析,并且对应成了相应的数据结构
* 解析android的system config的permission也是基于这个思想进行的解析
* 下面就来具体分析一下这个解析的过程和对应的数据结构
*/
private void readPermissionsFromXml(File permFile, boolean onlyFeatures) {
/*
FileReader fr = new FileReader(String fileName);//使用带有指定文件的String参数的构造方法。创建该输入流对象。并关联源文件。
主要方法:
int read(); // 读取单个字符。返回作为整数读取的字符,如果已达到流末尾,则返回 -1。
int read(char []cbuf);//将字符读入数组。返回读取的字符数。如果已经到达尾部,则返回-1。
void close();//关闭此流对象。释放与之关联的所有资源。
XmlPullParser:
常用的有:
XmlPullParser.END_DOCUMENT
XmlPullParser.START_DOCUMENT
XmlPullParser.START_TAG
XmlPullParser.END_TAG
XmlPullParser.TEXT
分别代表着XML文档的结束,开始,标签的开始,标签的结束,内容
XmlPullParser.getEventType() : Returns the type of the current event (START_TAG, END_TAG, TEXT, etc.) 【获取当前事件回调类型】
XmlPullParser.getName():For START_TAG or END_TAG events, the (local) name of the current element is returned when namespaces are enabled.【获取当前节点名字】
XmlPullParser.getAttributeValue(int index):Returns the given attributes value.【根据id获取节点属性值】
XmlPullParser.getAttributeValue(String namespace, String name):Returns the attributes value identified by namespace URI and namespace localName.【根据name获取节点属性值】
XmlPullParser.netxText(): If current event is START_TAG then if next element is TEXT then element content is returned or if next event is END_TAG then empty string is returned, otherwise exception is thrown.【回调节点START_TAG时,通过此方法获取节点内容】
*/
FileReader permReader = null;
try {
permReader = new FileReader(permFile);
} catch (FileNotFoundException e) {
Slog.w(TAG, "Couldn't find or open permissions file " + permFile);
return;
}
try {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(permReader);
int type;
/*
* parser.next() 指向下一个标签
*/
while ((type=parser.next()) != parser.START_TAG
&& type != parser.END_DOCUMENT) {
;
} // 如果下一个标签不是START_TAG的话,会一直寻找到下一个标签
if (type != parser.START_TAG) { // 如果遍历完了,都没有遍历到一个标签的开始的话,会返回error
throw new XmlPullParserException("No start tag found");
}
/*
* parser.getName() 获取标签的标题
*/
if (!parser.getName().equals("permissions") && !parser.getName().equals("config")) {//如果获取到的标签的标题不为permissions,或者解析出来的标签的标题不为config的话,会报错
/*
以android.hardware.nfc.hce.xml为例,该文件中的内容为:
<permissions>
<feature name="android.hardware.nfc.hce" />
</permissions>
*/
throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
", expected 'permissions' or 'config'");
}
/*
具体的标签的属性如下:
group: 安装到系统中的所有 APK 都具备的组 ID。
permission: 可以指定一个权限与几个组 ID 对应。当一个 APK 被授予这个权限时,它也同时属于这几个组。
assign-permission: 把一个权限赋予一个 UID,当进程使用这个 UID 运行时,就具备了这个权限。
library: 为系统添加一些扩展库用的。对应的.jar 文件放在/system/framework/目录下。比如Google Map 相关的库。
feature: 每添加一个硬件,都要增加对应的feature
*/
while (true) { // 如果是permissions和config的话,会进入到这个while循环,用来解析这个大的标签,和配置文件
XmlUtils.nextElement(parser); // 读取permissions的大标签下面的每一个小标签
if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
break;
}
String name = parser.getName();
/*
<permission name="android.permission.MODIFY_NETWORK_ACCOUNTING">
<group gid="net_bw_acct" />
</permission>
*/
if ("group".equals(name) && !onlyFeatures) {
String gidStr = parser.getAttributeValue(null, "gid"); // 如果tag的name是group的话,根据gid去获取当前的属性
if (gidStr != null) {
int gid = android.os.Process.getGidForName(gidStr);
mGlobalGids = appendInt(mGlobalGids, gid); // 将gid保存到mGlobalGids的数组中
} else {
Slog.w(TAG, "<group> without gid at "
+ parser.getPositionDescription());
}
XmlUtils.skipCurrentTag(parser);
continue;
} else if ("permission".equals(name) && !onlyFeatures) { // 如果tag的name是permission的话,根据name去获取当前的属性
String perm = parser.getAttributeValue(null, "name");
if (perm == null) {
Slog.w(TAG, "<permission> without name at "
+ parser.getPositionDescription());
XmlUtils.skipCurrentTag(parser);
continue;
}
perm = perm.intern(); // 返回string池中的perm,此处perm的值并没有改变
readPermission(parser, perm); // 调用readPermission进行perm的解析,该函数的解析我们放到后面进行分析 ----1
/*
<assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="media" />
<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="media" />
<assign-permission name="android.permission.WAKE_LOCK" uid="media" />
<assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="media" />
<assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="media" />
<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="graphics" />
*/
} else if ("assign-permission".equals(name) && !onlyFeatures) { // 如果tag的name是assign-permission的话,会根据name, uid去获取相应的属性
String perm = parser.getAttributeValue(null, "name"); // 获取name
if (perm == null) {
Slog.w(TAG, "<assign-permission> without name at "
+ parser.getPositionDescription());
XmlUtils.skipCurrentTag(parser);
continue;
}
String uidStr = parser.getAttributeValue(null, "uid"); // 获取UID
if (uidStr == null) {
Slog.w(TAG, "<assign-permission> without uid at "
+ parser.getPositionDescription());
XmlUtils.skipCurrentTag(parser);
continue;
}
int uid = Process.getUidForName(uidStr); // 通过uid的string,获取到真正的UID
if (uid < 0) {
Slog.w(TAG, "<assign-permission> with unknown uid \""
+ uidStr + "\" at "
+ parser.getPositionDescription());
XmlUtils.skipCurrentTag(parser);
continue;
}
perm = perm.intern(); // permission的name
HashSet<String> perms = mSystemPermissions.get(uid); // 通过uid去perms的hashset中查询,看是否可以查询的到。
if (perms == null) { // 如果查询不到的话
perms = new HashSet<String>(); // 会新建一个perms
mSystemPermissions.put(uid, perms); //然后把uid和对应的hashset放到mSystemPermissions的SparseArray中
}
perms.add(perm); // 将新的permission放入到perms中
XmlUtils.skipCurrentTag(parser);
// 通过这个操作,我们可以知道目前的数据结构是mSystemPermissions是一个SparseArray,然后是通过uid建立的映射。保存的是当前的UID所拥有的权限
} else if ("library".equals(name) && !onlyFeatures) {
/*
<library name="android.test.runner" file="/system/framework/android.test.runner.jar" />
<library name="javax.obex" file="/system/framework/javax.obex.jar"/>
*/
String lname = parser.getAttributeValue(null, "name"); // 通过name和file去获取对应的属性
String lfile = parser.getAttributeValue(null, "file");
if (lname == null) {
Slog.w(TAG, "<library> without name at "
+ parser.getPositionDescription());
} else if (lfile == null) {
Slog.w(TAG, "<library> without file at "
+ parser.getPositionDescription());
} else {
//Log.i(TAG, "Got library " + lname + " in " + lfile);
mSharedLibraries.put(lname, lfile); // 如果都有值的话,会把name和file都push到mSharedLibraries中
}
XmlUtils.skipCurrentTag(parser);
continue;
} else if ("feature".equals(name)) {
/*
<permissions>
<feature name="android.hardware.usb.accessory" />
<library name="com.android.future.usb.accessory"
file="/system/framework/com.android.future.usb.accessory.jar" />
</permissions>
*/
String fname = parser.getAttributeValue(null, "name"); // 在feature的tag下,会获取feature的name,每增加一个硬件,都要增加对应的feature
if (fname == null) {
Slog.w(TAG, "<feature> without name at "
+ parser.getPositionDescription());
} else {
//Log.i(TAG, "Got feature " + fname);
FeatureInfo fi = new FeatureInfo();
fi.name = fname;
mAvailableFeatures.put(fname, fi); // 将对应的feature name和其对应的FeatureInfo放入到了mAvailableFeatures中
}
XmlUtils.skipCurrentTag(parser);
continue;
} else if ("allow-in-power-save".equals(name)) {
/*
allow-in-power-save, 应该是在省电模式下面允许该package获取网络的权限。
目前的话,只有platform.xml里面一处有这个标签的声明
<!-- These are the standard packages that are white-listed to always have internet
access while in power save mode, even if they aren't in the foreground. -->
<allow-in-power-save package="com.android.providers.downloads" />
*/
String pkgname = parser.getAttributeValue(null, "package");
if (pkgname == null) {
Slog.w(TAG, "<allow-in-power-save> without package at "
+ parser.getPositionDescription());
} else {
mAllowInPowerSave.add(pkgname); // 将该pkgname保存到mAllowInPowerSave中
}
XmlUtils.skipCurrentTag(parser);
continue;
} else if ("fixed-ime-app".equals(name)) { // 目前的permission中,并没有fixed-ime-app这个标签
String pkgname = parser.getAttributeValue(null, "package");
if (pkgname == null) {
Slog.w(TAG, "<fixed-ime-app> without package at "
+ parser.getPositionDescription());
} else {
mFixedImeApps.add(pkgname);
}
XmlUtils.skipCurrentTag(parser);
continue;
} else {
XmlUtils.skipCurrentTag(parser);
continue;
}
}
permReader.close();
} catch (XmlPullParserException e) {
Slog.w(TAG, "Got execption parsing permissions.", e);
} catch (IOException e) {
Slog.w(TAG, "Got execption parsing permissions.", e);
}
}
在前面的解析中,我们留下了一个函数,是在permission的标签的时候,会用readPermission去进行解析
在这个函数中,我们会把permission和对应的gid进行保存。
void readPermission(XmlPullParser parser, String name) // name是permission的name,parser是读取到的数据流
throws IOException, XmlPullParserException {
name = name.intern();
PermissionEntry perm = mPermissions.get(name); // 去mPermissions中查看是否已经有name对应的PermissionEntry
if (perm == null) {
perm = new PermissionEntry(name);
mPermissions.put(name, perm);
} // 如果没有的话,会new一个PermissionEntry
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 ("group".equals(tagName)) {
String gidStr = parser.getAttributeValue(null, "gid");
if (gidStr != null) {
int gid = Process.getGidForName(gidStr);
perm.gids = appendInt(perm.gids, gid);
} else {
Slog.w(TAG, "<group> without gid at "
+ parser.getPositionDescription());
}
}
XmlUtils.skipCurrentTag(parser);
}
}
分析到了现在,我们分析完了systemConfig的构造函数。
在systemConfig的阶段,我们会去读取/system/etc的permission内容,然后进行解析,保存在不同的数据结构中.
我们接着回到PackageManager的构造函数中:
/*
SystemConfig systemConfig = SystemConfig.getInstance();
mGlobalGids = systemConfig.getGlobalGids(); // 系统的etc的xml下面所声明的所有的gid
mSystemPermissions = systemConfig.getSystemPermissions(); // 系统中etc下xml声明的uid和其对应的权限
mAvailableFeatures = systemConfig.getAvailableFeatures(); // 系统中硬件所拥有的feature
*/
接下来我们继续分析PackageManager的构造函数
synchronized (mInstallLock) {
// writer
synchronized (mPackages) {
mHandlerThread = new ServiceThread(TAG,
Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
mHandlerThread.start();
mHandler = new PackageHandler(mHandlerThread.getLooper()); // 建立PackageHandler的消息循环,用于处理apk的安装请求
Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
File dataDir = Environment.getDataDirectory(); // 创建data的文件夹
mAppDataDir = new File(dataDir, "data"); // /data/data
mAppInstallDir = new File(dataDir, "app"); // data/app
mAppLib32InstallDir = new File(dataDir, "app-lib"); // /data/app-lib
mAsecInternalPath = new File(dataDir, "app-asec").getPath(); // /data/app-ases
mUserAppDataDir = new File(dataDir, "user"); // /data/user
mDrmAppPrivateInstallDir = new File(dataDir, "app-private"); // /data/app-private
sUserManager = new UserManagerService(context, this,
mInstallLock, mPackages);
....
}
}
在进行完data的文件夹创建之后,会进入下面的一些操作:
ArrayMap<String, SystemConfig.PermissionEntry> permConfig
= systemConfig.getPermissions(); // return 的是SystemConfig的mPermissions,存放的是permission的name和gid
for (int i=0; i<permConfig.size(); i++) {
SystemConfig.PermissionEntry perm = permConfig.valueAt(i);
BasePermission bp = mSettings.mPermissions.get(perm.name);
if (bp == null) {
bp = new BasePermission(perm.name, "android", BasePermission.TYPE_BUILTIN);
mSettings.mPermissions.put(perm.name, bp);
}
if (perm.gids != null) {
bp.gids = appendInts(bp.gids, perm.gids);
}
}
// 该for循环的作用是将前面从/system/etc/permission里面读取到的permission的name和对应的gid放入到bp中,然后保存在mSettings的mPermissions中
ArrayMap<String, String> libConfig = systemConfig.getSharedLibraries();
for (int i=0; i<libConfig.size(); i++) {
mSharedLibraries.put(libConfig.keyAt(i),
new SharedLibraryEntry(libConfig.valueAt(i), null));
}
// 该for循环的作用是将读取出来的SharedLibrary放入到mSharedLibraries中
mFoundPolicyFile = SELinuxMMAC.readInstallPolicy();
/*
这里首先调用Settings的readLPw函数去解析packages.xml和packages-backup.xml保存的安装列表信息
并把解析的pakcages信息添加到相应的数据结构中
这里我们先假设这是第一次开机,所有packages.xml和packages-backup.xml文件都还不存在。
所以Settings的readLPw函数会直接返回。
packages.xml里面保存了当前系统中保存的所有apk的详细信息
*/
mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false),
mSdkVersion, mOnlyCore);
String customResolverActivity = Resources.getSystem().getString(
R.string.config_customResolverActivity);
if (TextUtils.isEmpty(customResolverActivity)) {
customResolverActivity = null;
} else {
mCustomResolverComponentName = ComponentName.unflattenFromString(
customResolverActivity);
}
接下来会进行下面的处理:
long startTime = SystemClock.uptimeMillis(); // 获取当前系统的时间
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START,
startTime);
// Set flag to monitor and not change apk file paths when
// scanning install directories.
final int scanFlags = SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING; // 设置扫描模式
final HashSet<String> alreadyDexOpted = new HashSet<String>();
/**
* Add everything in the in the boot class path to the
* list of process files because dexopt will have been run
* if necessary during zygote startup.
*/
final String bootClassPath = System.getenv("BOOTCLASSPATH");
final String systemServerClassPath = System.getenv("SYSTEMSERVERCLASSPATH");
if(Debug_chao) {
/*
bootClassPath为
/system/framework/core-libart.jar:
/system/framework/conscrypt.jar:
/system/framework/okhttp.jar:
/system/framework/core-junit.jar:
/system/framework/bouncycastle.jar:
/system/framework/ext.jar:
/system/framework/framework.jar:
/system/framework/telephony-common.jar:
/system/framework/voip-common.jar:
/system/framework/ims-common.jar:
/system/framework/mms-common.jar:
/system/framework/android.policy.jar:
/system/framework/apache-xml.jar
*/
Log.e(TAG,"bootClassPath = " + bootClassPath);
/*
/system/framework/services.jar:
/system/framework/ethernet-service.jar:
/system/framework/wifi-service.jar
*/
Log.e(TAG,"systemServerClassPath = " + systemServerClassPath);
}
if (bootClassPath != null) { // 将bootClassPath的值,加入到alreadyDexOpted的hashset中
/*
BOOTCLASSPATH的赋值为:
PRODUCT_BOOTCLASSPATH := $(subst $(space),:,$(foreach m,$(DEXPREOPT_BOOT_JARS_MODULES),/system/framework/$(m).jar))
DEXPREOPT_BOOT_JARS_MODULES := $(PRODUCT_BOOT_JARS)
PRODUCT_BOOT_JARS := \
core-libart \
conscrypt \
okhttp \
core-junit \
bouncycastle \
ext \
framework \
telephony-common \
voip-common \
ims-common \
mms-common \
android.policy \
apache-xml \
*/
String[] bootClassPathElements = splitString(bootClassPath, ':');
for (String element : bootClassPathElements) {
alreadyDexOpted.add(element);
}
} else {
Slog.w(TAG, "No BOOTCLASSPATH found!");
}
/*
# The order of PRODUCT_SYSTEM_SERVER_JARS matters.
PRODUCT_SYSTEM_SERVER_JARS := \
services \
ethernet-service \
wifi-service
*/
if (systemServerClassPath != null) {
String[] systemServerClassPathElements = splitString(systemServerClassPath, ':');
for (String element : systemServerClassPathElements) {
alreadyDexOpted.add(element);
}
} else {
Slog.w(TAG, "No SYSTEMSERVERCLASSPATH found!");
}
boolean didDexOptLibraryOrTool = false;
final List<String> allInstructionSets = getAllInstructionSets(); ---1
final String[] dexCodeInstructionSets = // 将allInstructionSets转化为数组
getDexCodeInstructionSets(allInstructionSets.toArray(new String[allInstructionSets.size()]));
/**
* Ensure all external libraries have had dexopt run on them.
*/
if (mSharedLibraries.size() > 0) { // 当知道了系统abi的架构后,会进行对sharedLibraries的处理,这边的mSharedLibraries,是jar
// NOTE: For now, we're compiling these system "shared libraries"
// (and framework jars) into all available architectures. It's possible
// to compile them only when we come across an app that uses them (there's
// already logic for that in scanPackageLI) but that adds some complexity.
for (String dexCodeInstructionSet : dexCodeInstructionSets) { // 遍历所支持的平台,一般有arm,x86,arm64等平台
for (SharedLibraryEntry libEntry : mSharedLibraries.values()) {
final String lib = libEntry.path; // 获取lib的path
if (lib == null) {
continue;
}
try {
/*
* 该函数为L上面新增的接口
*
* Returns UP_TO_DATE if the VM believes that the apk/jar file
* is up to date, PATCHOAT_NEEDED if it believes that the file is up
* to date but it must be relocated to match the base address offset,
* and DEXOPT_NEEDED if it believes that it is out of date and should
* be passed through "dexopt" again.
*
* @param fileName the absolute path to the apk/jar file to examine.
* @return DEXOPT_NEEDED if dexopt should be called on the file,
* PATCHOAT_NEEDED if we need to run "patchoat" on it and
* UP_TO_DATE otherwise.
*/
byte dexoptRequired = DexFile.isDexOptNeededInternal(lib, null,
dexCodeInstructionSet,
false);
if (dexoptRequired != DexFile.UP_TO_DATE) { // 如果不是UP_TO_DATE
alreadyDexOpted.add(lib); // 会将lib path加入到alreadyDexOpted
// The list of "shared libraries" we have at this point is
if (dexoptRequired == DexFile.DEXOPT_NEEDED) { // 如果是DEXOPT_NEEDED的话,灰烬行dexopt的优化
mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet);
} else { // 如果不是的话,那就是PATCHOAT_NEEDED,会进行oat的优化
mInstaller.patchoat(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet);
}
didDexOptLibraryOrTool = true; // 只要进行了oat或者opt的优化的话,会将该变量置为true
}
} catch (FileNotFoundException e) {
Slog.w(TAG, "Library not found: " + lib);
} catch (IOException e) {
Slog.w(TAG, "Cannot dexopt " + lib + "; is it an APK or JAR? "
+ e.getMessage());
}
}
}
}
——————————————-
1. getAllInstructionSets函数的实现:
private static List<String> getAllInstructionSets() {
final String[] allAbis = Build.SUPPORTED_ABIS; // 获取当前手机所支持的硬件架构
final List<String> allInstructionSets = new ArrayList<String>(allAbis.length); // new一个List,长度即为获取出来的ABI的个数
for (String abi : allAbis) { // 从所有的支持的架构开始遍历,abi为armeabi,armeabi-v7a....
final String instructionSet = VMRuntime.getInstructionSet(abi); // 从以下的2,3可以看到,是从目前android系统支持的架构map中,去查找,查找出来一般为arm,mips,arm64...
if (!allInstructionSets.contains(instructionSet)) { // 如果查询到的不包含在我们从Build的supported里面的话,把其加入到allInstructionSets里面
allInstructionSets.add(instructionSet); // 第一次的时候,因为allInstructionSets为一个刚new出来的对象,所以肯定是会加入到allInstructionSets里面
}
}
return allInstructionSets;
}
2. VMRuntime getInstructionSet的函数的实现:
public static String getInstructionSet(String abi) {
final String instructionSet = ABI_TO_INSTRUCTION_SET_MAP.get(abi);
if (instructionSet == null) {
throw new IllegalArgumentException("Unsupported ABI: " + abi);
}
return instructionSet;
}
3. ABI_TO_INSTRUCTION_SET_MAP的声明:
private static final Map<String, String> ABI_TO_INSTRUCTION_SET_MAP
= new HashMap<String, String>();
static {
ABI_TO_INSTRUCTION_SET_MAP.put("armeabi", "arm");
ABI_TO_INSTRUCTION_SET_MAP.put("armeabi-v7a", "arm");
ABI_TO_INSTRUCTION_SET_MAP.put("mips", "mips");
ABI_TO_INSTRUCTION_SET_MAP.put("mips64", "mips64");
ABI_TO_INSTRUCTION_SET_MAP.put("x86", "x86");
ABI_TO_INSTRUCTION_SET_MAP.put("x86_64", "x86_64");
ABI_TO_INSTRUCTION_SET_MAP.put("arm64-v8a", "arm64");
}
---------------------------------------------------------------------------------
File frameworkDir = new File(Environment.getRootDirectory(), "framework"); // /system/framework
// Gross hack for now: we know this file doesn't contain any
// code, so don't dexopt it to avoid the resulting log spew.
alreadyDexOpted.add(frameworkDir.getPath() + "/framework-res.apk"); // 将framework-res.apk加入到alreadyDexOpted中
// Gross hack for now: we know this file is only part of
// the boot class path for art, so don't dexopt it to
// avoid the resulting log spew.
alreadyDexOpted.add(frameworkDir.getPath() + "/core-libart.jar");
/**
* And there are a number of commands implemented in Java, which
* we currently need to do the dexopt on so that they can be
* run from a non-root shell.
*/
String[] frameworkFiles = frameworkDir.list(); // 或者/system/framework下的所有文件
if (frameworkFiles != null) {
// TODO: We could compile these only for the most preferred ABI. We should
// first double check that the dex files for these commands are not referenced
// by other system apps.
for (String dexCodeInstructionSet : dexCodeInstructionSets) {
for (int i=0; i<frameworkFiles.length; i++) {
File libPath = new File(frameworkDir, frameworkFiles[i]); // 获取文件的绝对路径
String path = libPath.getPath(); // path是绝对路径
// Skip the file if we already did it.
if (alreadyDexOpted.contains(path)) { // 如果该文件是已经包含在alreadyDexOpted中了,就忽略不去处理
continue;
}
// Skip the file if it is not a type we want to dexopt.
if (!path.endsWith(".apk") && !path.endsWith(".jar")) { // 如果不是以jar和apk结尾的话,就忽略。因为odex优化针对的就是jar和apk
continue;
}
try {
byte dexoptRequired = DexFile.isDexOptNeededInternal(path, null,
dexCodeInstructionSet,
false); // 判断文件是否需要优化
if (dexoptRequired == DexFile.DEXOPT_NEEDED) { // 如果需要dex的话,就会去进行Dex的优化
mInstaller.dexopt(path, Process.SYSTEM_UID, true, dexCodeInstructionSet);
didDexOptLibraryOrTool = true;
} else if (dexoptRequired == DexFile.PATCHOAT_NEEDED) { // 如果是需要oat优化的话,就会去进行oat的优化
mInstaller.patchoat(path, Process.SYSTEM_UID, true, dexCodeInstructionSet);
didDexOptLibraryOrTool = true;
}
} catch (FileNotFoundException e) {
Slog.w(TAG, "Jar not found: " + path);
} catch (IOException e) {
Slog.w(TAG, "Exception reading jar: " + path, e);
}
}
}
}
// 在接下来的处理中,就会遇到packagemanager的一个重要的函数,scanDirLI,我们接下来继续分析。
// (Do this before scanning any apps.)
// For security and version matching reason, only consider
// overlay packages if they reside in VENDOR_OVERLAY_DIR.
/*
* private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay";
*/
File vendorOverlayDir = new File(VENDOR_OVERLAY_DIR);
scanDirLI(vendorOverlayDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags | SCAN_TRUSTED_OVERLAY, 0); // 扫描该系统目录下的所有apk,进行安装 -1
// Find base frameworks (resource packages without code).
scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR
| PackageParser.PARSE_IS_PRIVILEGED,
scanFlags | SCAN_NO_DEX, 0);
// Collected privileged system packages.
final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR
| PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);
// Collect ordinary system packages.
final File systemAppDir = new File(Environment.getRootDirectory(), "app");
scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
// Collect all vendor packages.
File vendorAppDir = new File("/vendor/app");
try {
vendorAppDir = vendorAppDir.getCanonicalFile();
} catch (IOException e) {
// failed to look up canonical path, continue with original one
}
scanDirLI(vendorAppDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
// Collect all OEM packages.
final File oemAppDir = new File(Environment.getOemDirectory(), "app");
scanDirLI(oemAppDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
if (DEBUG_UPGRADE) Log.v(TAG, "Running installd update commands");
mInstaller.moveFiles();
——————————————————————————————————————-
/*
* scanDirLI的实现如下:
*/
private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
final File[] files = dir.listFiles(); // 会从传入的dir中,遍历所有的文件
if (ArrayUtils.isEmpty(files)) { // 如果该文件夹中一个文件都没有的话,会return
Log.d(TAG, "No files in app dir " + dir);
return;
}
if (DEBUG_PACKAGE_SCANNING) {
Log.d(TAG, "Scanning app dir " + dir + " scanFlags=" + scanFlags
+ " flags=0x" + Integer.toHexString(parseFlags));
}
for (File file : files) { // 如果有文件存在的话,就会进行遍历
final boolean isPackage = (isApkFile(file) || file.isDirectory())
&& !PackageInstallerService.isStageName(file.getName()); // 判断一个文件是否是一个apk的文件(以apk结尾),或者是一个文件并且文件夹满足isStageName的条件
if (!isPackage) {
// Ignore entries which are not packages
continue;
}
try {
scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
scanFlags, currentTime, null); // 会利用scanPackageLI进行接下来的解析
} catch (PackageManagerException e) {
Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage());
// Delete invalid userdata apps
if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
e.error == PackageManager.INSTALL_FAILED_INVALID_APK) { // 只有非系统的apk扫描失败的时候,才会删除该apk。
logCriticalInfo(Log.WARN, "Deleting invalid package at " + file);
if (file.isDirectory()) {
FileUtils.deleteContents(file);
}
file.delete();
}
}
}
}
———————————————————————————————————————
public static boolean isStageName(String name) {
final boolean isFile = name.startsWith("vmdl") && name.endsWith(".tmp");
final boolean isContainer = name.startsWith("smdl") && name.endsWith(".tmp");
final boolean isLegacyContainer = name.startsWith("smdl2tmp");
return isFile || isContainer || isLegacyContainer;
}
———————————————————————————————————————
/*
* Scan a package and return the newly parsed package.
* Returns null in case of errors and the error code is stored in mLastScanError
* 调用scanPackageLI函数扫描一个特定的文件,返回值是PackageParser的内部类package。
* 该类的实例代表一个apk文件,所以它就是和apk文件对应的数据结构
*/
private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
long currentTime, UserHandle user) throws PackageManagerException {
if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile);
parseFlags |= mDefParseFlags;
PackageParser pp = new PackageParser(); // new 了一个PackageParser的对象
pp.setSeparateProcesses(mSeparateProcesses);
pp.setOnlyCoreApps(mOnlyCore);
pp.setDisplayMetrics(mMetrics);
if ((scanFlags & SCAN_TRUSTED_OVERLAY) != 0) {
parseFlags |= PackageParser.PARSE_TRUSTED_OVERLAY;
}
/*
* 各个参数的值如下:
mSeparateProcesses = null,因为前面初始化值的时候separateProcesses为null
mOnlyCore = false 正常情况下mOnlyCore为flase
mMetrics 为前面获取到的手机的分辨率
mDefParseFlags 的值为0
parseFlags的值会随着scanFile的变化而变化
*/
final PackageParser.Package pkg;
try {
pkg = pp.parsePackage(scanFile, parseFlags);
} catch (PackageParserException e) {
throw PackageManagerException.from(e);
}
PackageSetting ps = null;
PackageSetting updatedPkg;
// reader
synchronized (mPackages) {
// Look to see if we already know about this package.
String oldName = mSettings.mRenamedPackages.get(pkg.packageName);
if (pkg.mOriginalPackages != null && pkg.mOriginalPackages.contains(oldName)) {
// This package has been renamed to its original name. Let's
// use that.
ps = mSettings.peekPackageLPr(oldName);
}
// If there was no original package, see one for the real package name.
if (ps == null) {
ps = mSettings.peekPackageLPr(pkg.packageName);
}
// Check to see if this package could be hiding/updating a system
// package. Must look for it either under the original or real
// package name depending on our state.
updatedPkg = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName);
if (DEBUG_INSTALL && updatedPkg != null) Slog.d(TAG, "updatedPkg = " + updatedPkg);
}
boolean updatedPkgBetter = false;
// First check if this is a system package that may involve an update
if (updatedPkg != null && (parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {
if (ps != null && !ps.codePath.equals(scanFile)) {
// The path has changed from what was last scanned... check the
// version of the new path against what we have stored to determine
// what to do.
if (DEBUG_INSTALL) Slog.d(TAG, "Path changing from " + ps.codePath);
if (pkg.mVersionCode < ps.versionCode) {
// The system package has been updated and the code path does not match
// Ignore entry. Skip it.
logCriticalInfo(Log.INFO, "Package " + ps.name + " at " + scanFile
+ " ignored: updated version " + ps.versionCode
+ " better than this " + pkg.mVersionCode);
if (!updatedPkg.codePath.equals(scanFile)) {
Slog.w(PackageManagerService.TAG, "Code path for hidden system pkg : "
+ ps.name + " changing from " + updatedPkg.codePathString
+ " to " + scanFile);
updatedPkg.codePath = scanFile;
updatedPkg.codePathString = scanFile.toString();
// This is the point at which we know that the system-disk APK
// for this package has moved during a reboot (e.g. due to an OTA),
// so we need to reevaluate it for privilege policy.
if (locationIsPrivileged(scanFile)) {
updatedPkg.pkgFlags |= ApplicationInfo.FLAG_PRIVILEGED;
}
}
updatedPkg.pkg = pkg;
throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE, null);
} else {
// The current app on the system partition is better than
// what we have updated to on the data partition; switch
// back to the system partition version.
// At this point, its safely assumed that package installation for
// apps in system partition will go through. If not there won't be a working
// version of the app
// writer
synchronized (mPackages) {
// Just remove the loaded entries from package lists.
mPackages.remove(ps.name);
}
logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + scanFile
+ " reverting from " + ps.codePathString
+ ": new version " + pkg.mVersionCode
+ " better than installed " + ps.versionCode);
InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps),
ps.codePathString, ps.resourcePathString, ps.legacyNativeLibraryPathString,
getAppDexInstructionSets(ps));
synchronized (mInstallLock) {
args.cleanUpResourcesLI();
}
synchronized (mPackages) {
mSettings.enableSystemPackageLPw(ps.name);
}
updatedPkgBetter = true;
}
}
}
if (updatedPkg != null) {
// An updated system app will not have the PARSE_IS_SYSTEM flag set
// initially
parseFlags |= PackageParser.PARSE_IS_SYSTEM;
// An updated privileged app will not have the PARSE_IS_PRIVILEGED
// flag set initially
if ((updatedPkg.pkgFlags & ApplicationInfo.FLAG_PRIVILEGED) != 0) {
parseFlags |= PackageParser.PARSE_IS_PRIVILEGED;
}
}
// Verify certificates against what was last scanned
collectCertificatesLI(pp, ps, pkg, scanFile, parseFlags);
/*
* A new system app appeared, but we already had a non-system one of the
* same name installed earlier.
*/
boolean shouldHideSystemApp = false;
if (updatedPkg == null && ps != null
&& (parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0 && !isSystemApp(ps)) {
/*
* Check to make sure the signatures match first. If they don't,
* wipe the installed application and its data.
*/
if (compareSignatures(ps.signatures.mSignatures, pkg.mSignatures)
!= PackageManager.SIGNATURE_MATCH) {
logCriticalInfo(Log.WARN, "Package " + ps.name + " appeared on system, but"
+ " signatures don't match existing userdata copy; removing");
deletePackageLI(pkg.packageName, null, true, null, null, 0, null, false);
ps = null;
} else {
/*
* If the newly-added system app is an older version than the
* already installed version, hide it. It will be scanned later
* and re-added like an update.
*/
if (pkg.mVersionCode < ps.versionCode) {
shouldHideSystemApp = true;
logCriticalInfo(Log.INFO, "Package " + ps.name + " appeared at " + scanFile
+ " but new version " + pkg.mVersionCode + " better than installed "
+ ps.versionCode + "; hiding system");
} else {
/*
* The newly found system app is a newer version that the
* one previously installed. Simply remove the
* already-installed application and replace it with our own
* while keeping the application data.
*/
logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + scanFile
+ " reverting from " + ps.codePathString + ": new version "
+ pkg.mVersionCode + " better than installed " + ps.versionCode);
InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps),
ps.codePathString, ps.resourcePathString, ps.legacyNativeLibraryPathString,
getAppDexInstructionSets(ps));
synchronized (mInstallLock) {
args.cleanUpResourcesLI();
}
}
}
}
// The apk is forward locked (not public) if its code and resources
// are kept in different files. (except for app in either system or
// vendor path).
// TODO grab this value from PackageSettings
if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
if (ps != null && !ps.codePath.equals(ps.resourcePath)) {
parseFlags |= PackageParser.PARSE_FORWARD_LOCK;
}
}
// TODO: extend to support forward-locked splits
String resourcePath = null;
String baseResourcePath = null;
if ((parseFlags & PackageParser.PARSE_FORWARD_LOCK) != 0 && !updatedPkgBetter) {
if (ps != null && ps.resourcePathString != null) {
resourcePath = ps.resourcePathString;
baseResourcePath = ps.resourcePathString;
} else {
// Should not happen at all. Just log an error.
Slog.e(TAG, "Resource path not set for pkg : " + pkg.packageName);
}
} else {
resourcePath = pkg.codePath;
baseResourcePath = pkg.baseCodePath;
}
// Set application objects path explicitly.
pkg.applicationInfo.setCodePath(pkg.codePath);
pkg.applicationInfo.setBaseCodePath(pkg.baseCodePath);
pkg.applicationInfo.setSplitCodePaths(pkg.splitCodePaths);
pkg.applicationInfo.setResourcePath(resourcePath);
pkg.applicationInfo.setBaseResourcePath(baseResourcePath);
pkg.applicationInfo.setSplitResourcePaths(pkg.splitCodePaths);
// Note that we invoke the following method only if we are about to unpack an application
PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanFlags
| SCAN_UPDATE_SIGNATURE, currentTime, user);
/*
* If the system app should be overridden by a previously installed
* data, hide the system app now and let the /data/app scan pick it up
* again.
*/
if (shouldHideSystemApp) {
synchronized (mPackages) {
/*
* We have to grant systems permissions before we hide, because
* grantPermissions will assume the package update is trying to
* expand its permissions.
*/
grantPermissionsLPw(pkg, true, pkg.packageName);
mSettings.disableSystemPackageLPw(pkg.packageName);
}
}
return scannedPkg;
}
—————————————————————————————————————
在parsePackage的时候,会进行如下处理,如果是文件夹的话,会调用parseClusterPackage
如果不是文件夹的话,会进行parseMonolithicPackage的操作。
public Package parsePackage(File packageFile, int flags) throws PackageParserException {
if (packageFile.isDirectory()) {
return parseClusterPackage(packageFile, flags);
} else {
return parseMonolithicPackage(packageFile, flags);
}
}
—————————————————————————————————————
对于普通apk来说的话,是会进行parseMonolithicPackage的操作。然后我们来看一下这个函数的实现。
@Deprecated
public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
if (mOnlyCoreApps) { // mOnlyCoreApps的值是mOnlyCore,正常情况下也为false
final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
if (!lite.coreApp) {
throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
"Not a coreApp: " + apkFile);
}
}
final AssetManager assets = new AssetManager(); // new了一个AssetManager的对象,AssetManager是资源管理框架
try {
final Package pkg = parseBaseApk(apkFile, assets, flags); // 在parseBaseApk的时候,会把assets传人
pkg.codePath = apkFile.getAbsolutePath();
return pkg;
} finally {
IoUtils.closeQuietly(assets);
}
}
—————————————————————————————————————-
private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
throws PackageParserException {
final String apkPath = apkFile.getAbsolutePath(); // 返回apkFile绝对路径名字字符串
mParseError = PackageManager.INSTALL_SUCCEEDED; // 在parse的开始,将mParseError的值设置为INSTALL_SUCCEEDED
mArchiveSourcePath = apkFile.getAbsolutePath(); // 返回apkFile的绝对路径的字符串
if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);
final int cookie = loadApkIntoAssetManager(assets, apkPath, flags);
Resources res = null;
XmlResourceParser parser = null;
try {
res = new Resources(assets, mMetrics, null);
/*
Resources类的成员函数updateConfiguration首先是根据参数config和metrics来更新设备的当前配置信息,
例如,屏幕大小和密码、国家地区和语言、键盘配置情况等等,
接着再调用成员变量mAssets所指向的一个Java层的AssetManager对象的成员函数setConfiguration来将这些配置信息设置到与之关联的C++层的AssetManager对象中去。
*/
assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
Build.VERSION.RESOURCES_SDK_INT);
parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
final String[] outError = new String[1];
final Package pkg = parseBaseApk(res, parser, flags, outError);
if (pkg == null) {
throw new PackageParserException(mParseError,
apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
}
pkg.baseCodePath = apkPath;
pkg.mSignatures = null;
return pkg;
} catch (PackageParserException e) {
throw e;
} catch (Exception e) {
throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
"Failed to read manifest from " + apkPath, e);
} finally {
IoUtils.closeQuietly(parser);
}
}
—————————————————————————————————————
private static int loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags)
throws PackageParserException {
if ((flags & PARSE_MUST_BE_APK) != 0 && !isApkPath(apkPath)) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
"Invalid package file: " + apkPath);
}
// The AssetManager guarantees uniqueness for asset paths, so if this asset path
// already exists in the AssetManager, addAssetPath will only return the cookie
// assigned to it.
int cookie = assets.addAssetPath(apkPath); // addAssetPath 函数的作用是来将参数resDir所描述的Apk文件路径作为它的资源目录。
/* cookie有两种情况,分别为等不等于0. 如果等于0的话
AssetManager类的成员函数addAssetPath接着再检查在其成员变量mAssetPaths所描述的一个类型为asset_path的Vector中是否已经添加过参数path所描述的一个Apk文件路径了。
如果已经添加过了,那么AssetManager类的成员函数addAssetPath就不会再继续往下处理了,而是将与参数path所描述的一个Apk文件路径所对应的一个Cookie返回给调用者,即保存在输出参数cookie中,
前提是参数cookie的值不等于NULL。
一个Apk文件路径所对应的Cookie实际上只是一个整数,这个整数表示该Apk文件路径所对应的一个asset_path对象在成员变量mAssetPaths所描述的一个Vector中的索引再加上1。
*/
if (cookie == 0) { // 等于0的时候,是添加vector失败的。所以会throw一个exception
throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
"Failed adding asset path: " + apkPath);
}
return cookie;
}