- 原文发布与个人博客KuTear,转载请注明*
pm命令的使用
pm 基本使用
pm
工具在Android系统System/bin
下,由于该路径已经在环境变量PATH
下,所以我们可以在Android终端下运行pm
命令
pm <command>
通过键入pm
我们可以看见他的用法,主要包括以下几个方面
- list => 列出安装的APK/权限组/机器硬件功能(NFC/蓝牙/WIFI)/设备支持库
- path => 查询指定apk的路径
- install => APK安装
- dump => 获取系统中的信息(电量/activity/server)
- clear => 清除APK数据
- hide/unhide => 隐藏与恢复应用(被隐藏应用在应用管理中变得不可见,桌面图标也会消失)
- enable/disable => 禁用和启用应用
- grant/revoke => 赋予/撤销应用权限
pm 命令源码分析
Pm命令的源码在framework/base/cmds/pm下(本文基于5.1.1 r1 代码地址),只有一个类Pm.java
,在其静态入口函数main(args)
中执行函数run(args)
,这里我们可以看看其中一部分Pm list package
。
public int run(String[] args) throws IOException, RemoteException {
boolean validCommand = false;
if (args.length < 1) {
return showUsage();
}
mUm = IUserManager.Stub.asInterface(ServiceManager.getService("user"));
mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
if (mPm == null) {
System.err.println(PM_NOT_RUNNING_ERR);
return 1;
}
mInstaller = mPm.getPackageInstaller();
mArgs = args;
String op = args[0];
mNextArg = 1;
if ("list".equals(op)) {
return runList();
}
//....其他命令
}
上面的代码通过进程间通信获取的PackageManagaerServer
,然后运行具体的指令,下面看看list
指令是怎么获取到数据的。
/**
* Execute the list sub-command.
*
* pm list [package | packages]
* pm list permission-groups
* pm list permissions
* pm list features
* pm list libraries
* pm list instrumentation
*/
private int runList() {
String type = nextArg();
if (type == null) {
System.err.println("Error: didn't specify type of data to list");
return 1;
}
if ("package".equals(type) || "packages".equals(type)) {
return runListPackages(false);
} else if ("permission-groups".equals(type)) {
return runListPermissionGroups();
} else if ("permissions".equals(type)) {
return runListPermissions();
} else if ("features".equals(type)) {
return runListFeatures();
} else if ("libraries".equals(type)) {
return runListLibraries();
} else if ("instrumentation".equals(type)) {
return runListInstrumentation();
} else if ("users".equals(type)) {
return runListUsers();
} else {
System.err.println("Error: unknown list type '" + type + "'");
return 1;
}
}
这里没有实质性的操作,只是分发list
的子指令,下面我们看package
指令的实现逻辑。
/*
* Lists all the installed packages.
*/
private int runListPackages(boolean showApplicationPackage) {
int getFlags = 0;
boolean listDisabled = false, listEnabled = false;
boolean listSystem = false, listThirdParty = false;
boolean listInstaller = false;
//A user id constant to indicate the "owner" user of the device
int userId = UserHandle.USER_OWNER;
try {
String opt;
while ((opt=nextOption()) != null) {
if (opt.equals("-l")) {
// old compat
} else if (opt.equals("-lf")) {
showApplicationPackage = true;
} else if (opt.equals("-f")) {
showApplicationPackage = true;
} else if (opt.equals("-d")) {
listDisabled = true;
} else if (opt.equals("-e")) {
listEnabled = true;
} else if (opt.equals("-s")) {
listSystem = true;
} else if (opt.equals("-3")) {
listThirdParty = true;
} else if (opt.equals("-i")) {
listInstaller = true;
} else if (opt.equals("--user")) {
userId = Integer.parseInt(nextArg());
} else if (opt.equals("-u")) {
getFlags |= PackageManager.GET_UNINSTALLED_PACKAGES;
} else {
System.err.println("Error: Unknown option: " + opt);
return 1;
}
}
} catch (RuntimeException ex) {
System.err.println("Error: " + ex.toString());
return 1;
}
String filter = nextArg();
try {
//重点关注
final List<PackageInfo> packages = getInstalledPackages(mPm, getFlags, userId);
int count = packages.size();
for (int p = 0 ; p < count ; p++) {
PackageInfo info = packages.get(p);
if (filter != null && !info.packageName.contains(filter)) {
continue;
}
final boolean isSystem =
(info.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0;
if ((!listDisabled || !info.applicationInfo.enabled) &&
(!listEnabled || info.applicationInfo.enabled) &&
(!listSystem || isSystem) &&
(!listThirdParty || !isSystem)) {
System.out.print("package:");
if (showApplicationPackage) {
System.out.print(info.applicationInfo.sourceDir);
System.out.print("=");
}
System.out.print(info.packageName);
if (listInstaller) {
System.out.print(" installer=");
System.out.print(mPm.getInstallerPackageName(info.packageName));
}
System.out.println();
}
}
return 0;
} catch (RemoteException e) {
System.err.println(e.toString());
System.err.println(PM_NOT_RUNNING_ERR);
return 1;
}
}
上面的代码一方面是添加过滤参数,然后获取所有安装的APK,在根据过滤参数展示对应的APK,而我们最为关心的就是获取所有APK的过程.到底是怎么获取的呢, 我们接着往下看。
@SuppressWarnings("unchecked")
private List<PackageInfo> getInstalledPackages(IPackageManager pm, int flags, int userId)
throws RemoteException {
ParceledListSlice<PackageInfo> slice = pm.getInstalledPackages(flags, userId);
return slice.getList();
}
到此,我们发现其实获取APK列表的过程是由IPackageManager
来实现的,同样的方式分析其他命令的操作,其最后都是归集到IPackageManager
上,而IPackageManager
就是我们今天的主角,下面我们会重点分析PM
启动的过程
PackageManagerServer
的启动位置
在SystemServer
启动后,会实例化PackageManagerServer
, 具体位置在PackageManagerServer#startBootstrapServices()
。
mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
在这个main()
函数中实现很简单,只是实例化一个PMS
对象,然后将其加入SystemServer
中,而最为重要的过程就是PackageManagerServer
的实例化的过程,下面将会重点分析这个过程。
PackageManagerServer
的实例过程
PM相关的目录
-
系统APK
-
/system/priv-app
-
/system/app
-
/vendor/app
-
/oem/app
-
-
用户APK
-
/data/app
-
PMS配置文件
-
/data/system/packages.xml
记录系统中所有已经安装的应用信息,包括基本信息,签名和权限。
-
/system/etc/permissions/
读取当前Android设备的硬件特性和设定的相关权限。
参考:PMS运行时的一些规则
PM初始化过程
//PackageManagerServer.java构造函数
//build类型,可以在shell下通过getprop来查看全部
//使用getprop ro.build.type获取到该build type
//build type有user,userdebug,eng
//user:性能好,Log/Debug信息少,相当于是正式版
//eng:Log/Debug信息相对多
//userdebug:debug强
//根据这里的返回,我们猜测返回值和dexopt相关
mLazyDexOpt = "eng".equals(SystemProperties.get("ro.build.type"));
mMetrics = new DisplayMetrics();
mSettings = new Settings(mPackages);
mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.log", LOG_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
上面主要就是新建一个Setting
对象,然后调用函数addSharedUserLPw(...)
,在Setting
的构造函数中主要就是为上文说的/data/system/packages.xml
等文件的创建和赋权限.现在看看函数addSharedUserLPw()
的实现。
SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
//ArrayMap<String, SharedUserSetting> mSharedUsers
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;
}
该函数主要就是共享UID,例如有的系统应用会有
android:sharedUserId="android.uis.system"
而根据上面的设置就是该APK的UID为Process.SYSTEM_UID
,从而达到共享系统UID的目的.而下面调用的函数addUserIdLPw(...)
就是保存该UID和对应的ShareUserSetting
。接着看PackageMangerService
的构造函数的下一部分。
SystemConfig systemConfig = SystemConfig.getInstance();
mGlobalGids = systemConfig.getGlobalGids();
mSystemPermissions = systemConfig.getSystemPermissions();
mAvailableFeatures = systemConfig.getAvailableFeatures();
SystemConfig
在构造函数里对一些目录进行读取,这些目录包括
/system/etc/sysconfig
/system/etc/permissions
/oem/etc/sysconfig
/oem/etc/permissions
然后解析这些目录下面的文件,这些文件的作用是声明当前设备的功能(NFC/WIFI)等.下面我们看看解析函数
private void readPermissionsFromXml(File permFile, boolean onlyFeatures) {
FileReader permReader = null;
try {
permReader = new FileReader(permFile);
} catch (FileNotFoundException e) {
Slog.w(TAG, "Couldn't find or open permissions file " + permFile);
return;
}
final boolean lowRam = ActivityManager.isLowRamDeviceStatic();
try {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(permReader);
int type;
while ((type=parser.next()) != parser.START_TAG
&& type != parser.END_DOCUMENT) {
;
}
if (type != parser.START_TAG) {
throw new XmlPullParserException("No start tag found");
}
//要求根结点为permissions OR config
if (!parser.getName().equals("permissions") && !parser.getName().equals("config")) {
throw new XmlPullParserException("Unexpected start tag in " + permFile
+ ": found " + parser.getName() + ", expected 'permissions' or 'config'");
}
//遍历XML,并将不同的功能保存到不同的ArraySet<>中
while (true) {
XmlUtils.nextElement(parser);
if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
break;
}
String name = parser.getName();
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);
} else {
Slog.w(TAG, "<group> without gid in " + permFile + " at "
+ parser.getPositionDescription());
}
XmlUtils.skipCurrentTag(parser);
continue;
} else if ("permission".equals(name) && !onlyFeatures) {
String perm = parser.getAttributeValue(null, "name");
if (perm == null) {
Slog.w(TAG, "<permission> without name in " + permFile + " at "
+ parser.getPositionDescription());
XmlUtils.skipCurrentTag(parser);
continue;
}
perm = perm.intern();
readPermission(parser, perm);
} else if ("assign-permission".equals(name) && !onlyFeatures) {
String perm = parser.getAttributeValue(null, "name");
if (perm == null) {
Slog.w(TAG, "<assign-permission> without name in " + permFile + " at "
+ parser.getPositionDescription());
XmlUtils.skipCurrentTag(parser);
continue;
}
String uidStr = parser.getAttributeValue(null, "uid");
if (uidStr == null) {
Slog.w(TAG, "<assign-permission> without uid in " + permFile + " at "
+ parser.getPositionDescription());
XmlUtils.skipCurrentTag(parser);
continue;
}
int uid = Process.getUidForName(uidStr);
if (uid < 0) {
Slog.w(TAG, "<assign-permission> with unknown uid \""
+ uidStr + " in " + permFile + " at "
+ parser.getPositionDescription());
XmlUtils.skipCurrentTag(parser);
continue;
}
perm = perm.intern();
ArraySet<String> perms = mSystemPermissions.get(uid);
if (perms == null) {
perms = new ArraySet<String>();
mSystemPermissions.put(uid, perms);
}
perms.add(perm);
XmlUtils.skipCurrentTag(parser);
} else if ("library".equals(name) && !onlyFeatures) {
String lname = parser.getAttributeValue(null, "name");
String lfile = parser.getAttributeValue(null, "file");
if (lname == null) {
Slog.w(TAG, "<library> without name in " + permFile + " at "
+ parser.getPositionDescription());
} else if (lfile == null) {
Slog.w(TAG, "<library> without file in " + permFile + " at "
+ parser.getPositionDescription());
} else {
//Log.i(TAG, "Got library " + lname + " in " + lfile);
mSharedLibraries.put(lname, lfile);
}
XmlUtils.skipCurrentTag(parser);
continue;
} else if ("feature".equals(name)) {
String fname = parser.getAttributeValue(null, "name");
boolean allowed;
if (!lowRam) {
allowed = true;
} else {
String notLowRam = parser.getAttributeValue(null, "notLowRam");
allowed = !"true".equals(notLowRam);
}
if (fname == null) {
Slog.w(TAG, "<feature> without name in " + permFile + " at "
+ parser.getPositionDescription());
} else if (allowed) {
//Log.i(TAG, "Got feature " + fname);
FeatureInfo fi = new FeatureInfo();
fi.name = fname;
mAvailableFeatures.put(fname, fi);
}
XmlUtils.skipCurrentTag(parser);
continue;
} else if ("unavailable-feature".equals(name)) {
String fname = parser.getAttributeValue(null, "name");
if (fname == null) {
Slog.w(TAG, "<unavailable-feature> without name in " + permFile + " at "
+ parser.getPositionDescription());
} else {
mUnavailableFeatures.add(fname);
}
XmlUtils.skipCurrentTag(parser);
continue;
} else if ("allow-in-power-save-except-idle".equals(name) && !onlyFeatures) {
String pkgname = parser.getAttributeValue(null, "package");
if (pkgname == null) {
Slog.w(TAG, "<allow-in-power-save-except-idle> without package in "
+ permFile + " at " + parser.getPositionDescription());
} else {
mAllowInPowerSaveExceptIdle.add(pkgname);
}
XmlUtils.skipCurrentTag(parser);
continue;
} else if ("allow-in-power-save".equals(name) && !onlyFeatures) {
String pkgname = parser.getAttributeValue(null, "package");
if (pkgname == null) {
Slog.w(TAG, "<allow-in-power-save> without package in " + permFile + " at "
+ parser.getPositionDescription());
} else {
mAllowInPowerSave.add(pkgname);
}
XmlUtils.skipCurrentTag(parser);
continue;
} else if ("fixed-ime-app".equals(name) && !onlyFeatures) {
String pkgname = parser.getAttributeValue(null, "package");
if (pkgname == null) {
Slog.w(TAG, "<fixed-ime-app> without package in " + permFile + " at "
+ parser.getPositionDescription());
} else {
mFixedImeApps.add(pkgname);
}
XmlUtils.skipCurrentTag(parser);
continue;
} else if ("app-link".equals(name)) {
String pkgname = parser.getAttributeValue(null, "package");
if (pkgname == null) {
Slog.w(TAG, "<app-link> without package in " + permFile + " at "
+ parser.getPositionDescription());
} else {
mLinkedApps.add(pkgname);
}
XmlUtils.skipCurrentTag(parser);
} else {
XmlUtils.skipCurrentTag(parser);
continue;
}
}
} catch (XmlPullParserException e) {
Slog.w(TAG, "Got exception parsing permissions.", e);
} catch (IOException e) {
Slog.w(TAG, "Got exception parsing permissions.", e);
} finally {
IoUtils.closeQuietly(permReader);
}
for (String fname : mUnavailableFeatures) {
if (mAvailableFeatures.remove(fname) != null) {
Slog.d(TAG, "Removed unavailable feature " + fname);
}
}
}
上面代码很容易理解,解析xml节点的数据到具体的List或Set中,在PackageManagerService
的构造函数中就取出了mGlobalGids/mAvailableFeatures/mSystemPermissions
出来,分别对应的TAG节点为
<group gid="" ></group>
<feature></feature>
<assign-permission></assign-permission>
下面接着分析PackageManagerService
的构造函数
mHandlerThread = new ServiceThread(TAG,
Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/); //HandlerThread
mHandlerThread.start();
mHandler = new PackageHandler(mHandlerThread.getLooper());
Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
File dataDir = Environment.getDataDirectory(); //dataDir = "/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-asec"
mUserAppDataDir = new File(dataDir, "user"); //= "/data/user"
mDrmAppPrivateInstallDir = new File(dataDir, "app-private"); // = "/data/app-private"
sUserManager = new UserManagerService(context, this,
mInstallLock, mPackages);
// Propagate permission configuration in to package manager.
//合并SystemConfig读取的permission到Settings下
ArrayMap<String, SystemConfig.PermissionEntry> permConfig
= systemConfig.getPermissions(); //permission标签下的
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.setGids(perm.gids, perm.perUser);
}
}
//从SystemConfig中读取的libs存入共享库
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));
}
mFoundPolicyFile = SELinuxMMAC.readInstallPolicy();
上面的代码主要就是通过上面的SystemConfig
获取到的信息存在在Settings
和PMS
内部。
//这里会读取前面说的/data/system/packages.xml文件以及他的备份文件
//这里特别说明下,会把解析的application存放到mSetting的mPackages中,后面会用到
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 | SCAN_INITIAL;
//已经dexopt的apk存放位置
final ArraySet<String> alreadyDexOpted = new ArraySet<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");
//系统库类不要优化 可以通过echo $BOOTCLASSPATH查看
if (bootClassPath != null) {
String[] bootClassPathElements = splitString(bootClassPath, ':');
for (String element : bootClassPathElements) {
alreadyDexOpted.add(element);
}
} else {
Slog.w(TAG, "No BOOTCLASSPATH found!");
}
if (systemServerClassPath != null) {
String[] systemServerClassPathElements = splitString(systemServerClassPath, ':');
for (String element : systemServerClassPathElements) {
alreadyDexOpted.add(element);
}
} else {
Slog.w(TAG, "No SYSTEMSERVERCLASSPATH found!");
}
上面代码开始时解析packages.xml
,该文件保存了系统内安装了的APK的信息,然后就是添加一些库类到alreadyDexOpted
这个List里面,目的是以后做dexopt的时候跳过这些不必要的优化。这里说明一下packages.xml
中的字段的保存位置(以下是在Settings.java
中)。
package -> mPackages(readPackageLPw()-->addPackageLPw()) //重点
permissions -> mPermissions(readPermissionsLPw())
permission-trees -> mPermissionTrees(readPermissionsLPw())
shared-user -> mSharedUsers(readSharedUserLPw()-->addSharedUserLPw())
updated-package -> mDisabledSysPackages(readDisabledSysPackageLPw()) //这个标签是在OTA中添加的?删除也会有这个标记?
renamed-package -> mRenamedPackages
这里强调一下,会把package
的信息存在Settings.mPackages
中,并根据package
标签下的installStatus
字段判断app安装的状态,这在后面有用到。下面继续看PMS
的构造函数。
//通过命令getprop ro.product.cpu.abilist查看设备支持的指令集
final List<String> allInstructionSets = InstructionSets.getAllInstructionSets();
final String[] dexCodeInstructionSets =
getDexCodeInstructionSets(
allInstructionSets.toArray(new String[allInstructionSets.size()]));
/**
* Ensure all external libraries have had dexopt run on them.
*/
if (mSharedLibraries.size() > 0) {
// 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) {
for (SharedLibraryEntry libEntry : mSharedLibraries.values()) {
final String lib = libEntry.path;
if (lib == null) {
continue;
}
try {
int dexoptNeeded = DexFile.getDexOptNeeded(lib, null, dexCodeInstructionSet, false);
if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
alreadyDexOpted.add(lib);
mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded);
}
} 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());
}
}
}
}
这段代码主要就是执行dexopt的过程,并将优化过的apk/jar放入alreadyDexOpted
,这里mInstaller
内部通过LocalStock
发送一条指令dexopt apkpath ....
,让LocalStock
的服务端处理这个请求,我没有找到服务端的实现位置....但不影响我们继续阅读。
File frameworkDir = new File(Environment.getRootDirectory(), "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");
// 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");
/**
* 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();
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();
// Skip the file if we already did it.
if (alreadyDexOpted.contains(path)) {
continue;
}
// Skip the file if it is not a type we want to dexopt.
if (!path.endsWith(".apk") && !path.endsWith(".jar")) {
continue;
}
try {
int dexoptNeeded = DexFile.getDexOptNeeded(path, null, dexCodeInstructionSet, false);
if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
mInstaller.dexopt(path, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded);
}
} catch (FileNotFoundException e) {
Slog.w(TAG, "Jar not found: " + path);
} catch (IOException e) {
Slog.w(TAG, "Exception reading jar: " + path, e);
}
}
}
}
这段代码和上面的实现几乎一样,读取/system/framework/
下的几个目录的jar包和apk,判断是否需要进行优化,值得注意的是并没有加入列表alreadyDexOpted
。
final VersionInfo ver = mSettings.getInternalVersion();
mIsUpgrade = !Build.FINGERPRINT.equals(ver.fingerprint);
// when upgrading from pre-M, promote system app permissions from install to runtime 运行时权限
mPromoteSystemApps =
mIsUpgrade && ver.sdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1;
// save off the names of pre-existing system packages prior to scanning; we don't
// want to automatically grant runtime permissions for new system apps
if (mPromoteSystemApps) {
Iterator<PackageSetting> pkgSettingIter = mSettings.mPackages.values().iterator();
while (pkgSettingIter.hasNext()) {
PackageSetting ps = pkgSettingIter.next();
if (isSystemApp(ps)) {
mExistingSystemPackages.add(ps.name);
}
}
}
当系统有LOLLIPOP_MR1
一下升级时到LOLLIPOP_MR1
以上时,android在6.0引入动态权限机制,在这里也就是mPromoteSystemApps=true
,会执行下面一段代码。
// Collect vendor overlay packages.
// (Do this before scanning any apps.)
// For security and version matching reason, only consider
// overlay packages if they reside in VENDOR_OVERLAY_DIR.
File vendorOverlayDir = new File(VENDOR_OVERLAY_DIR);// = "/vendor/overlay"
scanDirLI(vendorOverlayDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags | SCAN_TRUSTED_OVERLAY, 0);
// Find base frameworks (resource packages without code).
scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM //system/framework
| 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(); //执行LocalStock发送movefiles命令
上面的代码的逻辑就是扫描指定的目录,这里的目录包括下面这些
/vendor/overlay
/system/framework
/system/priv-app
/system/app
/vendor/app
/oem/app
也就是我们上文提到的系统APK的存放目录,扫描结束之后会把apk信息存放在mPackages
(这里是PMS,区别于Settings
的mPackages
)。下面我们来分析scanDirLI()
的过程。
private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
final File[] files = dir.listFiles();
if (ArrayUtils.isEmpty(files)) {
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));
}
//遍历该目录下的APK
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 {
scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
scanFlags, currentTime, null);
} catch (PackageManagerException e) {
Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage());
// Delete invalid userdata apps
// 删除无效的用户APK
if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
e.error == PackageManager.INSTALL_FAILED_INVALID_APK) {
logCriticalInfo(Log.WARN, "Deleting invalid package at " + file);
if (file.isDirectory()) {
mInstaller.rmPackageDir(file.getAbsolutePath());
} else {
file.delete();
}
}
}
}
}
上面代码核心就是遍历指定的文件夹,对文件夹内部的文件执行函数scanPackageLI(File,...)
,通过其注释我们了解到他是扫描包的。它的代码也比较长,下面选择其中一部分说明。
private PackageParser.Package scanPackageLI(File scanFile,
int parseFlags, int scanMode, long currentTime) {
......
String scanPath = scanFile.getPath();
parseFlags |= mDefParseFlags;
PackageParser pp = new PackageParser();
......
final PackageParser.Package pkg = pp.parsePackage(scanFile,
scanPath, mMetrics, parseFlags);
......
return scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_SIGNATURE, currentTime);
}
为指定的文件创建PackageParser
,将解析结果存入Package
,最后在调用函数scanPackageLI(Package,...)
。而在函数PackageParser.Package.parsePackage(...)
中会判断scanFile
是文件还是目录(Android分包),会对他们做不同的处理,我们这里简单点,就看是文件的分支,当时文件时,会调用函数parseMonolithicPackage(packageFile, flags)
,下面分析这个函数。
public Package parseMonolithicPackage(File apkFile, int flags){
final AssetManager assets = new AssetManager();
final Package pkg = parseBaseApk(apkFile, assets, flags);
pkg.codePath = apkFile.getAbsolutePath();
return pkg;
}
这里的核心就是函数parseBaseApk(File,...)
,根据名称感觉有些明朗了,不就解析APK嘛,看看到底是怎么实现的吧。
private Package parseBaseApk(File apkFile, AssetManager assets, int flags){
....
Resources res = null;
XmlResourceParser parser = null;
res = new Resources(assets, mMetrics, null);
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, "AndroidManifest.xml");
final String[] outError = new String[1];
final Package pkg = parseBaseApk(res, parser, flags, outError);
...
return pkg;
}
卧槽,又是圈套,有调函数parseBaseApk(Resources,...)
来解析,不过上面我们已经看见关键的AndroidManifest.xml
已经出现了。通过阅读parseBaseApk(Resources,...)
,我们发现他会解析AndroidManifest.xml
中的一部分文件,这里大体包括以下标签
- application
- overlay
- key-sets
- permission-group
- permission-tree
- uses-permission
- uses-permission-sdk-m | uses-permission-sdk-23
- uses-configuration
- uses-feature
- feature-group
- uses-sdk
- supports-screens
- protected-broadcast
- instrumentation
- original-package
- adopt-permissions
- uses-gl-texture
- compatible-screens
- supports-input
- eat-comment
惭愧,好多标签没见过,查看官网发现官网并没有列举以上全部AndroidManifest 。我们这里继续跟进application
标签,发现他调用函数parseBaseApplication()
。这个函数就是对Application
内部四大组件进行解析。我们这里选取activity
的部分来看看。
// 函数参数 Package owner
if (tagName.equals("activity")) {
//class Activity extends Component<ActivityIntentInfo>
Activity a = parseActivity(owner, res, parser, attrs, flags, outError, false,
owner.baseHardwareAccelerated);
if (a == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
owner.activities.add(a);
}
parseActivity()
就是解析activity
标签下的内容,比如Activity
的Theme
什么的,解析的过程主要是利用TypedArray
,具体的属性可以看看Activity,很多属性都是存储在一个flags
标记了,这样减少了类中成员字段过多,这在Android中使用的比较多,比如View
中很多属性也是存在一个flags
中。到此一个解析好了的Package
就好了,不知不觉,已经偏了十万八千里,不要急,拉回来,上面我们讲到scanPackageLI(File,...)
的最后调用了scanPackageLI(Package,...)
,那么这个函数有是做什么的呢?这个函数调用了scanPackageDirtyLI()
,这个函数的代码量也是相当吓人,这里不打算具体分析,主要工作就是为app创建目录,也就是/data/data/apk.name/
这个目录,还有就是APK对应的libs的存放位置,App签名验证,收集APK要的权限,最重要的就是把解析信息存放到了PMS
的mPackages
变量中,意味着App安装成功了,后面会用到这个变量。回到PackageManagerService
的构造函数中来。
// Prune any system packages that no longer exist.
final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<String>();
if (!mOnlyCore) {
//mSettings.mPackages来自与package.xml的package标签
Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator();
while (psit.hasNext()) {
PackageSetting ps = psit.next();
/*
* If this is not a system app, it can't be a
* disable system app.
*/
if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0) {
continue;
}
/*
* If the package is scanned, it's not erased.
*/
//PMS的mPackages存放扫描过的APK
final PackageParser.Package scannedPkg = mPackages.get(ps.name);
//扫描到了 && packages.xml中存在
if (scannedPkg != null) {
/*
* If the system app is both scanned and in the
* disabled packages list, then it must have been
* added via OTA. Remove it from the currently
* scanned package so the previously user-installed
* application can be scanned.
*/
//package.xml的package标签和updated-package标签都包含这个pkg
//根据上面注释,意味着这个APK是通过OTA添加的,暂时移除
if (mSettings.isDisabledSystemPackageLPr(ps.name)) {
logCriticalInfo(Log.WARN, "Expecting better updated system app for "
+ ps.name + "; removing system app. Last known codePath="
+ ps.codePathString + ", installStatus=" + ps.installStatus
+ ", versionCode=" + ps.versionCode + "; scanned versionCode="
+ scannedPkg.mVersionCode);
removePackageLI(ps, true);//mPackages.remove(ps.name);
mExpectingBetter.put(ps.name, ps.codePath);
}
continue;
}
//没有扫描到,在package标签下,但不在updated-package标签下,说明该APP已经不存在了
//因此要删掉他的目录
//直接从mSettings.mPackages中移除
if (!mSettings.isDisabledSystemPackageLPr(ps.name)) {
psit.remove();
logCriticalInfo(Log.WARN, "System package " + ps.name
+ " no longer exists; wiping its data");
removeDataDirsLI(null, ps.name);
} else {
//没有扫描到,在package标签下,也在updated-package标签下
//可能由OTA引入(或删除?)
final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(ps.name);
if (disabledPs.codePath == null || !disabledPs.codePath.exists()) {
possiblyDeletedUpdatedSystemApps.add(ps.name);
}
}
}
}
处理被用户隐藏的APP(前面讲的pm hide package
),因为被隐藏的APP在package.xml
还存在,这里就是把这些APP从保存他们的列表中移除。另外就是在package.xml
中还有该APK,但是扫描系统目录发现这个APK已经不存在了的处理方式。执行完之后mSetting.mPackages
剩下的就是无效的APK,我们需要将这些清除,于是就有了下面的几行代码
//look for any incomplete package installations
ArrayList<PackageSetting> deletePkgsList = mSettings.getListOfIncompleteInstallPackagesLPr();//获取mSettings.mPackages中installStatus为未成功安装的App(前文有讲,package中installStatus=false的apk)
//clean up list
for(int i = 0; i < deletePkgsList.size(); i++) {
//clean up here
cleanupInstallFailedPackage(deletePkgsList.get(i));
}
//delete tmp files
deleteTempPackageFiles();
// Remove any shared userIDs that have no associated packages
mSettings.pruneSharedUsersLPw();//移除没有被关联的mSharedUsers
上面的作用就是清除无效APK引入的文件夹等。系统APK装载完了,下面就开始装载用户APK,
scanDirLI(mAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);
scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK,
scanFlags | SCAN_REQUIRE_KNOWN, 0);
上面的代码和前面扫描系统APK是一样的,这是目录和flags变了,逻辑是一样的。这里的目录包括
/data/app
/data/app-private
继续看构造函数。
/**
* Remove disable package settings for any updated system
* apps that were removed via an OTA. If they're not a
* previously-updated app, remove them completely.
* Otherwise, just revoke their system-level permissions.
*/
//在引进了用户app之后mPackages内容增加了,再看看是否有这些app
for (String deletedAppName : possiblyDeletedUpdatedSystemApps) {
PackageParser.Package deletedPkg = mPackages.get(deletedAppName);
mSettings.removeDisabledSystemPackageLPw(deletedAppName);
String msg;
if (deletedPkg == null) { //OTA删除
msg = "Updated system package " + deletedAppName
+ " no longer exists; wiping its data";
removeDataDirsLI(null, deletedAppName);
} else { //在用户app中找到了,当然会移除系统包标识
msg = "Updated system app + " + deletedAppName
+ " no longer present; removing system privileges for "
+ deletedAppName;
deletedPkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM;
PackageSetting deletedPs = mSettings.mPackages.get(deletedAppName);
deletedPs.pkgFlags &= ~ApplicationInfo.FLAG_SYSTEM;
}
logCriticalInfo(Log.WARN, msg);
}
/**
* Make sure all system apps that we expected to appear on
* the userdata partition actually showed up. If they never
* appeared, crawl back and revive the system version.
*/
for (int i = 0; i < mExpectingBetter.size(); i++) { //有新包,更新APK
final String packageName = mExpectingBetter.keyAt(i);
if (!mPackages.containsKey(packageName)) {
final File scanFile = mExpectingBetter.valueAt(i);
logCriticalInfo(Log.WARN, "Expected better " + packageName
+ " but never showed up; reverting to system");
final int reparseFlags;
//不同目录flags不一样
if (FileUtils.contains(privilegedAppDir, scanFile)) {
reparseFlags = PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR
| PackageParser.PARSE_IS_PRIVILEGED;
} else if (FileUtils.contains(systemAppDir, scanFile)) {
reparseFlags = PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR;
} else if (FileUtils.contains(vendorAppDir, scanFile)) {
reparseFlags = PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR;
} else if (FileUtils.contains(oemAppDir, scanFile)) {
reparseFlags = PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR;
} else {
Slog.e(TAG, "Ignoring unexpected fallback path " + scanFile);
continue;
}
//加入mStting.mPackages
mSettings.enableSystemPackageLPw(packageName);
try {
scanPackageLI(scanFile, reparseFlags, scanFlags, 0, null);
} catch (PackageManagerException e) {
Slog.e(TAG, "Failed to parse original system package: "
+ e.getMessage());
}
}
}
mExpectingBetter.clear();
上面这段代码就是删除被OTA移除app的目录,更新新引入的App的目录。
// Now that we know all of the shared libraries, update all clients to have
// the correct library paths.
updateAllSharedLibrariesLPw(); //为需要sharelibs的apk关联libs,放在pkg.usesLibraryFiles
for (SharedUserSetting setting : mSettings.getAllSharedUsersLPw()) {
// NOTE: We ignore potential failures here during a system scan (like
// the rest of the commands above) because there's precious little we
// can do about it. A settings error is reported, though.
adjustCpuAbisForSharedUserLPw(setting.packages, null /* scanned package */,
false /* force dexopt */, false /* defer dexopt */);
}
// Now that we know all the packages we are keeping,
// read and update their last usage times.
mPackageUsage.readLP();//读/data/system/package-usage.list
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END,
SystemClock.uptimeMillis());
Slog.i(TAG, "Time to scan packages: "
+ ((SystemClock.uptimeMillis()-startTime)/1000f)
+ " seconds");
// If the platform SDK has changed since the last time we booted,
// we need to re-grant app permission to catch any new ones that
// appear. This is really a hack, and means that apps can in some
// cases get permissions that the user didn't initially explicitly
// allow... it would be nice to have some better way to handle
// this situation.
int updateFlags = UPDATE_PERMISSIONS_ALL;
if (ver.sdkVersion != mSdkVersion) {
Slog.i(TAG, "Platform changed from " + ver.sdkVersion + " to "
+ mSdkVersion + "; regranting permissions for internal storage");
updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
}
updatePermissionsLPw(null, null, updateFlags);//Apk分配权限
ver.sdkVersion = mSdkVersion;
// clear only after permissions have been updated
mExistingSystemPackages.clear();
mPromoteSystemApps = false;
// If this is the first boot, and it is a normal boot, then
// we need to initialize the default preferred apps.
//第一次启动,初始化默认程序,如浏览器,email程序
if (!mRestoredSettings && !onlyCore) {
mSettings.applyDefaultPreferredAppsLPw(this, UserHandle.USER_OWNER);
applyFactoryDefaultBrowserLPw(UserHandle.USER_OWNER);
primeDomainVerificationsLPw(UserHandle.USER_OWNER);
}
// If this is first boot after an OTA, and a normal boot, then
// we need to clear code cache directories.
if (mIsUpgrade && !onlyCore) {
Slog.i(TAG, "Build fingerprint changed; clearing code caches");
for (int i = 0; i < mSettings.mPackages.size(); i++) {
final PackageSetting ps = mSettings.mPackages.valueAt(i);
if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, ps.volumeUuid)) {
deleteCodeCacheDirsLI(ps.volumeUuid, ps.name);
}
}
ver.fingerprint = Build.FINGERPRINT;
}
checkDefaultBrowser();
// All the changes are done during package scanning.
ver.databaseVersion = Settings.CURRENT_DATABASE_VERSION;
// can downgrade to reader
mSettings.writeLPr(); //写package.xml
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY,
SystemClock.uptimeMillis());
mRequiredVerifierPackage = getRequiredVerifierLPr(); //string
mRequiredInstallerPackage = getRequiredInstallerLPr(); //string
mInstallerService = new PackageInstallerService(context, this); //根据名字知道大概是app安装相关服务
mIntentFilterVerifierComponent = getIntentFilterVerifierComponentNameLPr();
mIntentFilterVerifier = new IntentVerifierProxy(mContext,
mIntentFilterVerifierComponent);
} // synchronized (mPackages)
} // synchronized (mInstallLock)
// Now after opening every single application zip, make sure they
// are all flushed. Not really needed, but keeps things nice and
// tidy.
Runtime.getRuntime().gc();
// Expose private service for system components to use.
LocalServices.addService(PackageManagerInternal.class, new PackageManagerInternalImpl());
}
到此,PMS
的构造函数就阅读完毕了。
总结
本文由pm
命令引入,讲解PMS
的构造过程,这里在梳理一下这个过程。
- 通过
packages.xml(backup)
读取系统已经安装了的app,保存到mSettings
的相关字段中去。 - 通过扫描系统目录/用户app目录,将扫描的app保存到
PMS
的mPackages
中去。 - 根据上两步的结果判断哪些app无效(删除),哪些被OTA的方式更新,删除或重建对应app的目录
- app扫描结束,app权限分配,app引用的库的关联
- 重新保存这些新信息到
packages.xml
供下次开机使用
参考
- Android-6.0之PMS解析系列
- android系统启动之PMS启动源码解析
- Android应用程序安装过程源代码分析
- <<Android 源码设计模式-第十章>>