概述
##1、背景##
在前面一篇文章中,我们介绍了和PackageManagerService相关的一些类和关系,如果大家不是很明白的可以先去看一下这篇文章PackageManagerService源码分析之入门(一)。
由于PackageManagerService(后面简称为PKMS)是系统的核心服务,所以我准备多写几篇文章来分析。
在之前的博客文章中我们讲解了PKMS是通过SystemServer被创建出来的,具体可以参考SystemServer启动流程之SystemServer分析(三)这篇文章,其相关代码如下:
private void startBootstrapServices() {
// Start the package manager.
Slog.i(TAG, "Package Manager");
//调用PackageManagerService的main方法
mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
//判断系统是否第一次启动
mFirstBoot = mPackageManagerService.isFirstBoot();
mPackageManager = mSystemContext.getPackageManager();
//dex优化
mPackageManagerService.performBootDexOpt();
//通知系统进入就绪状态
mPackageManagerService.systemReady();
}
这里可以看到在SystemServer中创建了PKMS服务,并调用其main方法,我们进入其main方法。
public static final PackageManagerService main(Context context, Installer installer,boolean factoryTest, boolean onlyCore) {
//调用PKMS的main方法
PackageManagerService m = new PackageManagerService(context, installer,
factoryTest, onlyCore);
//向ServiceManager注册PKMS
ServiceManager.addService("package", m);
return m;
}
这里我们需要注意的是向ServiceManager中注册PKMS,其key值是package,通过ContextImpl.getPackageManager()来获得PackageManager对象时会使用到。
可以看出其main方法还是比较简单的只有几行代码,但是其执行时间却比较长,因为其执行的都是重体力活,这就是Android启动慢的原因之一。
我们先来大致说明一下PKMS的构造函数的主要功能:扫描系统中几个特定文件夹下的apk,从而建立合适的数据结构来管理Package信息,四大组件信息,权限信息等(PKMS主要解析apk文件的AndroidManifest.xml文件)。由于其工作是如此的繁重,所以我准备分3个阶段来分析。
- 扫描文件夹前的准备工作
- 扫描目标文件夹
- 扫描之后的处理工作
好啦,接下来将正式的进入我们的第一阶段分析。
##2、扫描文件夹前的准备工作##
我们进入PKMS的构造函数,由于其构造函数相关的长,所以我们将分段解析。
(1)初识Settings
final int mSdkVersion = Build.VERSION.SDK_INT;
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,
SystemClock.uptimeMillis());
//mSdkVersion始PKMS的成员变量,其值取自系统属性ro.build.version.sdk
if (mSdkVersion <= 0) {
Slog.w(TAG, "**** ro.build.version.sdk not set!");
}
mContext = context;
mFactoryTest = factoryTest;
mOnlyCore = onlyCore;
//如果是eng版,则扫描Package后,不对package进行dex优化
mLazyDexOpt = "eng".equals(SystemProperties.get("ro.build.type"));
//显示屏相关属性(屏幕尺寸和分辨率)
mMetrics = new DisplayMetrics();
//重点分析对象
mSettings = new Settings(context);
mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
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);
}
我们先来看一下Settings这个类的构造函数。
Settings(Context context) {
this(context, Environment.getDataDirectory());
}
Settings(Context context, File dataDir) {
//创建data/system目录
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, 0640, 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类在data目录下新建了system目录,并在该目录下新建几个重要的文件,我们首先来看一下packages.xml文件。
1.packages.xml:记录系统中所有已安装应用的相关信息
<package name="com.android.calendar" codePath="/system/app/MtkCalendar" nativeLibraryPath="/system/app/MtkCalendar/lib" publicFlags="940162629" privateFlags="0" pkgFlagsEx="0" ft="157a9940280" it="157a9940280" ut="157a9940280" version="23" userId="10059">
<sigs count="1">
<cert index="1" />
</sigs>
<perms>
<item name="android.permission.WRITE_SETTINGS" granted="true" flags="0" />
<item name="android.permission.USE_CREDENTIALS" granted="true" flags="0" />
<item name="android.permission.MANAGE_ACCOUNTS" granted="true" flags="0" />
<item name="android.permission.NFC" granted="true" flags="0" />
<item name="android.permission.WRITE_SYNC_SETTINGS" granted="true" flags="0" />
<item name="android.permission.RECEIVE_BOOT_COMPLETED" granted="true" flags="0" />
<item name="android.permission.INTERNET" granted="true" flags="0" />
<item name="android.permission.INTERACT_ACROSS_USERS_FULL" granted="true" flags="0" />
<item name="android.permission.INTERACT_ACROSS_USERS" granted="true" flags="0" />
<item name="android.permission.READ_SYNC_SETTINGS" granted="true" flags="0" />
<item name="android.permission.VIBRATE" granted="true" flags="0" />
<item name="android.permission.WAKE_LOCK" granted="true" flags="0" />
</perms>
<proper-signing-keyset identifier="1" />
</package>
2.packages-backup.xml:上面文件的备份
3.packages-stopped.xml:被强制停止运行的应用信息
4.packages-stopped-backup.xml:上面文件的备份
5.packages.list:保存普通应用的数据目录和uid信息等
......
com.android.calculator2 10035 0 /data/data/com.android.calculator2 default none
com.slightech.slife.zte 10081 0 /data/data/com.slightech.slife.zte default 3003
com.mediatek.lbs.em2.ui 10056 0 /data/data/com.mediatek.lbs.em2.ui platform 3003
com.android.wallpaper 10054 0 /data/data/com.android.wallpaper default none
com.android.vpndialogs 10026 0 /data/data/com.android.vpndialogs platform none
com.android.email 10043 0 /data/data/com.android.email platform 1023,1015,3003
com.android.music 10062 0 /data/data/com.android.music platform 1023,1015,3003
......
当Android对文件packages.xml和packages-stopped.xml写之前会先将它们备份,如果文件写入成功了就把备份文件删除掉,如果写的时候,系统出问题重启了,重启后会读取这两份文件时,发现有备份文件,会使用备份文件的内容,因为此时原文件已经损坏了。
Settings类的构造器已经分析完毕了,我们回到PackageManagerService的构造器中,此时Settings对象已经创建好了,接下来将调用其addSharedUserLPw()方法,我们以第一个为例。
mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
第一个参数是字符串android.uid.system,第二个参数是SYSTEM_UID值为1000,表示系统进程的用户id,第三个参数FLAG_SYSTEM表示是系统Package、而FLAG_PRIVILEGED表示其具有高权限。
我们进入addSharedUserLPw方法。
final ArrayMap<String, SharedUserSetting> mSharedUsers =
new ArrayMap<String, SharedUserSetting>();
SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags) {
//mSharedUsers是一个ArrayMap,其key为字符串,值为SharedUserSetting对象
SharedUserSetting s = mSharedUsers.get(name);
if (s != null) {
if (s.userId == uid) {
return s;
}
return null;
}
//创建SharedUserSetting对象,并设置其userId
s = new SharedUserSetting(name, pkgFlags);
s.userId = uid;
//这里调用addUserIdLPw()方法
if (addUserIdLPw(uid, s, name)) {
//将name设置为key值存储s
mSharedUsers.put(name, s);
return s;
}
return null;
}
从以上代码分析可知,Settings有一个成员变量mSharedUsers,该ArrayMap以字符串为key,SharedUserSetting为值的键值对,接下来我们来看一下和SharedUserSetting相关类的关系图。
可以看出:
- 可以看出Settings类定义了一个mSharedUsers变量(ArrayMap类型),以字符串为key(如android.uid.system),对应的Value是SharedUserSetting对象。
- SharedUserSetting继承于GrantedPermissions类,从命名可知它和权限相关,SharedUserSetting类中定义了一个成员变量packages(类型是ArraySet),用于保存声明了相同sharedUserId的Package权限设置信息。
- 每个Package有自己的权限设置,权限由PackageSetting类表达,该类继承于PackageSettingBase,而PackageSettingBase类又继承于GrantedPermissions类。
- Settings类中还有两个成员变量mUserIds(类型是ArrayList)和mOtherUserIds(类型是SparseArray),其目的是通过UID找到对应的SharedUserSetting对象。
下面来分析一下addUserIdLPw()方法。
private boolean addUserIdLPw(int uid, Object obj, Object name) {
//应用APK所在进程的uid>10000,而系统APK所在进程的uid<10000
//uid不能超过限制,LAST_APPLICATION_UID的值是19999
if (uid > Process.LAST_APPLICATION_UID) {
return false;
}
//表示应用APK的uid
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) {
return false;
}
//mUserIds保存应用Package
mUserIds.set(index, obj);
} else {
if (mOtherUserIds.get(uid) != null) {
return false;
}
//mOtherUserIds保存系统Package
mOtherUserIds.put(uid, obj);
}
return true;
}
好啦,到此Settings的分析就结束啦,我们接着PKMS的构造器往下分析。
(2)扫描XML文件
//将传入的installer对象赋值给本类的mInstaller对象,以便后续安装应用时使用
mInstaller = installer;
//获取当前的显示屏信息
getDefaultDisplayMetrics(context, mMetrics);
//创建SystemConfig对象,并调用其相关方法
SystemConfig systemConfig = SystemConfig.getInstance();
mGlobalGids = systemConfig.getGlobalGids();
mSystemPermissions = systemConfig.getSystemPermissions();
mAvailableFeatures = systemConfig.getAvailableFeatures();
private static void getDefaultDisplayMetrics(Context context, DisplayMetrics metrics) {
DisplayManager displayManager = (DisplayManager) context.getSystemService(
Context.DISPLAY_SERVICE);
displayManager.getDisplay(Display.DEFAULT_DISPLAY).getMetrics(metrics);
}
这里我们主要来分析SystemConfig类,我们进入该类的getInstance()方法。
public static SystemConfig getInstance() {
synchronized (SystemConfig.class) {
if (sInstance == null) {
sInstance = new SystemConfig();
}
return sInstance;
}
}
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);
}
public static File buildPath(File base, String... segments) {
File cur = base;
for (String segment : segments) {
if (cur == null) {
cur = new File(segment);
} else {
cur = new File(cur, segment);
}
}
return cur;
}
可以看出这是一个单例类,然后一层一层的建立文件夹,依次为system/etc/sysconfig,system/etc/permissions,oem/etc/sysconfig,oem/etc/permissions四个文件夹(可能有的文件夹手机中不存在)。
当四个文件夹创建完成后,接下来就是调用readPermissions方法对其循环遍历。
void readPermissions(File libraryDir, boolean onlyFeatures) {
// Read permissions from given directory.
if (!libraryDir.exists() || !libraryDir.isDirectory()) {
return;
}
if (!libraryDir.canRead()) {
return;
}
// Iterate over the files in the directory and scan .xml files
File platformFile = null;
//获取libraryDir下所有文件路径,然后通过for循环遍历操作该目录下所有文件
for (File f : libraryDir.listFiles()) {
// We'll read platform.xml last
//当遇到该目录下的platform.xml文件时,将其保存到变量platformFile中后续处理
if (f.getPath().endsWith("etc/permissions/platform.xml")) {
platformFile = f;
continue;
}
if (!f.getPath().endsWith(".xml")) {
continue;
}
if (!f.canRead()) {
continue;
}
//调用readPermissionsFromXml方法对结尾为xml文件进行处理
readPermissionsFromXml(f, onlyFeatures);
}
// Read platform permissions last so it will take precedence
//最后也是调用readPermissionsFromXml方法单独处理platform.xml文件
if (platformFile != null) {
readPermissionsFromXml(platformFile, onlyFeatures);
}
}
可以看出该方法主要就是将上面四个目录下的xml文件进行解析,同时保存platform.xml文件至最后单独处理,我们再次进入readPermissionsFromXml方法。
//该方法源码去除了大量的条件判断语句,方便代码阅读
private void readPermissionsFromXml(File permFile, boolean onlyFeatures) {
//......
permReader = new FileReader(permFile);
//......
while (true) {
//......
if ("group".equals(name) && !onlyFeatures) {
String gidStr = parser.getAttributeValue(null, "gid");
if (gidStr != null) {
int gid = android.os.Process.getGidForName(gidStr);
mGlobalGids = appendInt(mGlobalGids, gid);
}
continue;
} else if ("permission".equals(name) && !onlyFeatures) {
String perm = parser.getAttributeValue(null, "name");
//这里再次调用readPermission方法来进行处理保存
readPermission(parser, perm);
} else if ("assign-permission".equals(name) && !onlyFeatures) {
int uid = Process.getUidForName(uidStr);
ArraySet<String> perms = mSystemPermissions.get(uid);
if (perms == null) {
perms = new ArraySet<String>();
mSystemPermissions.put(uid, perms);
}
} else if ("library".equals(name) && !onlyFeatures) {
String lname = parser.getAttributeValue(null, "name");
String lfile = parser.getAttributeValue(null, "file");
mSharedLibraries.put(lname, lfile);
continue;
} else if ("feature".equals(name)) {
String fname = parser.getAttributeValue(null, "name");
fi.name = fname;
mAvailableFeatures.put(fname, fi);
continue;
} else if ("unavailable-feature".equals(name)) {
String fname = parser.getAttributeValue(null, "name");
mUnavailableFeatures.add(fname);
continue;
} else if ("allow-in-power-save".equals(name)&& !onlyFeatures) {
String pkgname = parser.getAttributeValue(null, "package");
mAllowInPowerSave.add(pkgname);
continue;
} else if ("fixed-ime-app".equals(name) && !onlyFeatures) {
String pkgname = parser.getAttributeValue(null, "package");
mFixedImeApps.add(pkgname);
continue;
}
}
从以上源码可知,该方法主要是对xml文件进行标签解读,当解读到不同的标签时对其值进行保存处理,我们来看一下其保存不同值的成员变量。
// Group-ids that are given to all packages as read from etc/permissions/*.xml.
int[] mGlobalGids;
// These are the built-in uid -> permission mappings that were read from the
// system configuration files.
final SparseArray<ArraySet<String>> mSystemPermissions = new SparseArray<>();
// These are the built-in shared libraries that were read from the
// system configuration files. Keys are the library names; strings are the
// paths to the libraries.
final ArrayMap<String, String> mSharedLibraries = new ArrayMap<>();
// These are the features this devices supports that were read from the
// system configuration files.
final ArrayMap<String, FeatureInfo> mAvailableFeatures = new ArrayMap<>();
// These are the features which this device doesn't support; the OEM
// partition uses these to opt-out of features from the system image.
final ArraySet<String> mUnavailableFeatures = new ArraySet<>();
// These are the permission -> gid mappings that were read from the
// system configuration files.
final ArrayMap<String, PermissionEntry> mPermissions = new ArrayMap<>();
// These are the packages that are white-listed to be able to run in the
// background while in power save mode, as read from the configuration files.
final ArraySet<String> mAllowInPowerSave = new ArraySet<>();
// These are the app package names that should not allow IME switching.
final ArraySet<String> mFixedImeApps = new ArraySet<>();
以上就是SystemConfig中的主要成员变量,通过解析不同路径下的xml文件,然后保存到各个变量中后面可以直接拿来使用,这里我们在来简单的看一下platform.xml文件。
<?xml version="1.0" encoding="utf-8"?>
<permissions>
<!--......-->
<permission name="android.permission.BLUETOOTH_ADMIN" >
<group gid="net_bt_admin" />
</permission>
<permission name="android.permission.BLUETOOTH" >
<group gid="net_bt" />
</permission>
<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="media" />
<assign-permission name="android.permission.WAKE_LOCK" uid="media" />
<library name="android.test.runner"
file="/system/framework/android.test.runner.jar" />
<library name="javax.obex"
file="/system/framework/javax.obex.jar"/>
<allow-in-power-save package="com.android.providers.downloads" />
<!--......-->
</permissions>
我们来看一下SystemConfig的数据结构关系图。
接下来我们继续往下分析。
//(1)创建一个ServiceThread对象,其实就是一个带消息循环处理的线程,用于程序的安装与卸载
mHandlerThread = new ServiceThread(TAG,
Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
mHandlerThread.start();
mHandler = new PackageHandler(mHandlerThread.getLooper());
//(2)将该Handler添加到Watchdog中进行监听
Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
//(3)创建多个文件目录,用于后期apk扫描
//data目录
File dataDir = Environment.getDataDirectory();
mAppDataDir = new File(dataDir, "data");
mAppInstallDir = new File(dataDir, "app");
mAppLib32InstallDir = new File(dataDir, "app-lib");
mAsecInternalPath = new File(dataDir, "app-asec").getPath();
mUserAppDataDir = new File(dataDir, "user");
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
mPreloadInstallDir = new File(Environment.getRootDirectory(), "preloadapp");
mDeleteRecord = new File(mAppInstallDir, ".delrecord");
sUserManager = new UserManagerService(context, this,
mInstallLock, mPackages);
//(4)调用Settings.readLPw方法
mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false),
mSdkVersion, mOnlyCore);
这段代码所进行的操作还是蛮多的,我们一步一步分析。
(1)首先创建了一个ServiceThread对象,以便用于程序的按照和卸载,这个我们后面会单独分析,再次就不详细阐述了。
(2)将处理应用程序安装和卸载的Handler添加到Watchdog监听器中,以监听其是否发生死锁等现象,如果大家对Watchdog不是很熟悉的,可以先看一下我的另外一篇博客SystemServer启动流程之WatchDog分析(四)。
(3)此处会创建多个data/目录下的文件,主要用于后面的apk扫描,这个我们下篇博客将详细阐述。
(4)此处会调用Settings类的readLPw方法,我们进入该方法。
boolean readLPw(PackageManagerService service, List<UserInfo> users, int sdkVersion,boolean onlyCore) {
FileInputStream str = null;
//首先判断packages-backup.xml文件是否存在,如果存在就删除packages.xml文件
if (mBackupSettingsFilename.exists()) {
try {
str = new FileInputStream(mBackupSettingsFilename);
if (mSettingsFilename.exists()) {
// If both the backup and settings file exist, we
// ignore the settings since it might have been
// corrupted.
mSettingsFilename.delete();
}
} catch (java.io.IOException e) {
}
}
if (str == null) {
//如果不存在packages-backup.xml文件就加载packages.xml文件
if (!mSettingsFilename.exists()) {
mInternalSdkPlatform = mExternalSdkPlatform = sdkVersion;
mFingerprint = Build.FINGERPRINT;
return false;
}
str = new FileInputStream(mSettingsFilename);
}
XmlPullParser parser = Xml.newPullParser();
parser.setInput(str, null);
//接下来主要就对packages.xml文件进行xml解析
//......
}
这个方法主要是对packages.xml文件进行解析,在文章的开头Settings构造器中创建的这些文件。
好啦,到这里PKMS构造器的第一阶段就分析完毕啦,其主要工作就是扫描并解析XML文件,下一篇文章我们将分析其构造器的第二、三阶段PackageManagerService源码分析之第二、三阶段(三)。
最后
以上就是害羞萝莉为你收集整理的PackageManagerService源码分析之第一阶段(二)的全部内容,希望文章能够帮你解决PackageManagerService源码分析之第一阶段(二)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复