作为Android开发者,我们经常会听到PMS这个词汇,但更多时候我们不知道PMS到底是什么,以及PMS到底有什么用。PackageManagerService是PMS是全称,可以译为包管理服务类。理解该服务的流程,有利于我们更进一步的了解Android系统以及系统启动的时候做了什么。让我们带着以下问题开始今天的PMS之旅吧!
1、我新机开机很快的,为什么后面越来越慢了
2、google每次都说对某个版本的系统启动速度进行优化,到底优化了什么
3、一个APK文件是怎么被解析以及运行的
4、为什么需要AndroidManifest.xml,google为什么要去工程师要在里面进行注册四大组件
OK,带着这些问题开始我们的痛苦之旅吧。先声明下,本次的分析是基于Android10,版本不同可能会存在一些差异的地方。
首先,我们的设备在点击电源键的时候,会执行系统问题的init.rc文件,该文件会配置入口点并执行文件的main方法。嗯,对应到系统里面是SystemServer.java文件的main方法。
//SystemServer.java
/**
* The main entry point from zygote.
* 这里解释得很清楚了,该main方法作为程序的入口点,被zygote方法调用
*/
public static void main(String[] args) {
new SystemServer().run();
}
在java程序中,我们需要有一个意识就是当我们看到这样的程序入口main()方法的时候,表示该程序是一个新的进程。该方法很简单,就只是执行了该类的run()方法。
//SystemServer.java
private void run() {
//.......此处省略N行代码
// Start services. 启动相关服务
try {
traceBeginAndSlog("StartServices");
//启动引导服务
startBootstrapServices();
//启动核心服务
startCoreServices();
//启动其它服务
startOtherServices();
SystemServerInitThreadPool.shutdown();
} catch (Throwable ex) {
Slog.e("System", "******************************************");
Slog.e("System", "************ Failure starting system services", ex);
throw ex;
} finally {
traceEnd();
}
//.......此处省略N行代码
}
SystemServer的run方法主要是做一个初始化的准备工作,然后启动相关服务。而我们的PMS以及AMS其实就是在startBootstrapServices() 这个方法里被启动的。
//SystemServer.java
private void startBootstrapServices() {
//.......此处省略N行代码
// Start the package manager.
if (!mRuntimeRestart) {
MetricsLogger.histogram(null, "boot_package_manager_init_start",
(int) SystemClock.elapsedRealtime());
}
traceBeginAndSlog("StartPackageManagerService");
try {
Watchdog.getInstance().pauseWatchingCurrentThread("packagemanagermain");
//此处调用PackageManagerService.main方法,注意该main方法其实只是PackageManagerService的一个静态方法,并没有开启一个新的进程
mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
} finally {
Watchdog.getInstance().resumeWatchingCurrentThread("packagemanagermain");
}
mFirstBoot = mPackageManagerService.isFirstBoot();
mPackageManager = mSystemContext.getPackageManager();
//.......此处省略N行代码
}
然后通过调用PackageManagerService.main方法去实例化PackageManagerService。应该需要注意到的时候由于这里其实是被SystemServer的run方法开始执行而来,也就是说PMS其实是执行在SystemServer所对应的进程的,并没有新开进程。
//PackageManagerService.java
public static PackageManagerService main(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
// Self-check for initial settings.
PackageManagerServiceCompilerMapping.checkProperties();
//实例化PackageManagerService
PackageManagerService m = new PackageManagerService(context, installer,
factoryTest, onlyCore);
m.enableSystemUserPackages();
//将自己添加到ServiceManager
ServiceManager.addService("package", m);
final PackageManagerNative pmn = m.new PackageManagerNative();
ServiceManager.addService("package_native", pmn);
return m;
}
PackageManagerService 的main方法主要是实例化PMS,并且将PMS添加到ServiceManager。我们平时在开发的时候会通过getPackageManager()来获取PackageManager,其获取流程如下:
public PackageManager getPackageManager() {
return mBase.getPackageManager();
}
public PackageManager getPackageManager() {
if (mPackageManager != null) {
return mPackageManager;
}
IPackageManager pm = ActivityThread.getPackageManager();
if (pm != null) {
// Doesn't matter if we make more than one instance.
return (mPackageManager = new ApplicationPackageManager(this, pm));
}
return null;
}
public static IPackageManager getPackageManager() {
if (sPackageManager != null) {
//Slog.v("PackageManager", "returning cur default = " + sPackageManager);
return sPackageManager;
}
//重点是这里
IBinder b = ServiceManager.getService("package");
//Slog.v("PackageManager", "default service binder = " + b);
sPackageManager = IPackageManager.Stub.asInterface(b);
//Slog.v("PackageManager", "default service = " + sPackageManager);
return sPackageManager;
}
请注意看IBinder b = ServiceManager.getService(“package”);sPackageManager = IPackageManager.Stub.asInterface(b);这两句,获取的时候传的参数为package,系统在添加的时候也是这个,获取的其实是一个Binder接口。嗯,是的,我们APP获取的PackageManager其实是需要跨进程的。OK,我们回到主题,PackageManagerService的实例化部分。
//PackageManagerService.java
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
//.......此处省略N行代码
//这里只贴了关键的代码,实在是太多啦
// Collect ordinary system packages.
//systemAppDir 对应的路为/system/app
final File systemAppDir = new File(Environment.getRootDirectory(), "app");
scanDirTracedLI(systemAppDir,
mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags
| SCAN_AS_SYSTEM,
0);
//这里的sAppInstallDir对应一个文件,路径为/data/app
scanDirTracedLI(sAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);
//.......此处省略N行代码
}
嗯哼,发现了嘛,其实就是会依次调用scanDirTracedLI去扫描/system/app和/data/app这两个目录。而我们的所有应用的安装位置都在这两个目录里。
//PackageManagerService.java
//这里会调用scanDirLI,前后加入了Trace机制,目的是记录执行时间,作为优化的依据
private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags, long currentTime) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
try {
scanDirLI(scanDir, parseFlags, scanFlags, currentTime);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
//PackageManagerService.java
private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime) {
final File[] files = scanDir.listFiles();
if (ArrayUtils.isEmpty(files)) {
Log.d(TAG, "No files in app dir " + scanDir);
return;
}
if (DEBUG_PACKAGE_SCANNING) {
Log.d(TAG, "Scanning app dir " + scanDir + " scanFlags=" + scanFlags
+ " flags=0x" + Integer.toHexString(parseFlags));
}
try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir,
mParallelPackageParserCallback)) {
// Submit files for parsing in parallel
int fileCount = 0;
for (File file : files) {
//判断是否为apk,其实该判断十分粗暴,就是判断文件是否为.apk结尾,对应的方法在PackageParse.java类里
final boolean isPackage = (isApkFile(file) || file.isDirectory())
&& !PackageInstallerService.isStageName(file.getName());
if (!isPackage) {
// Ignore entries which are not packages
continue;
}
//开始解析,这是从9.0开始做为优化项,解析会以多个线程同时执行的方式进行,之前不是
parallelPackageParser.submit(file, parseFlags);
fileCount++;
}
//.......此处省略N行代码
}
}
//ParallelPackageParser.java
private static final int MAX_THREADS = 4;
private final ExecutorService mService = ConcurrentUtils.newFixedThreadPool(MAX_THREADS,
"package-parsing-thread", Process.THREAD_PRIORITY_FOREGROUND);
public void submit(File scanFile, int parseFlags) {
mService.submit(() -> {
ParseResult pr = new ParseResult();
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parallel parsePackage [" + scanFile + "]");
try {
//实例化PackageParser
PackageParser pp = new PackageParser();
pp.setSeparateProcesses(mSeparateProcesses);
pp.setOnlyCoreApps(mOnlyCore);
pp.setDisplayMetrics(mMetrics);
pp.setCacheDir(mCacheDir);
pp.setCallback(mPackageParserCallback);
pr.scanFile = scanFile;
//重点是这,parsePackage 到这又与之前的方式重合了,开始解析apk
pr.pkg = parsePackage(pp, scanFile, parseFlags);
} catch (Throwable e) {
pr.throwable = e;
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
try {
mQueue.put(pr);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
// Propagate result to callers of take().
// This is helpful to prevent main thread from getting stuck waiting on
// ParallelPackageParser to finish in case of interruption
mInterruptedInThread = Thread.currentThread().getName();
}
});
}
//
//ParallelPackageParser.java
protected PackageParser.Package parsePackage(PackageParser packageParser, File scanFile,
int parseFlags) throws PackageParser.PackageParserException {
return packageParser.parsePackage(scanFile, parseFlags, true /* useCaches */);
}
//PackageParser.java
public Package parsePackage(File packageFile, int flags, boolean useCaches)
throws PackageParserException {
//这里有优化项 useCaches 8.0及之前是没有的
//判断是否有缓存,有的化直接使用缓存,不用在解析一次
Package parsed = useCaches ? getCachedResult(packageFile, flags) : null;
if (parsed != null) {
return parsed;
}
long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
if (packageFile.isDirectory()) {
parsed = parseClusterPackage(packageFile, flags);
} else {
//是apk文件则调用该方法进行解析
parsed = parseMonolithicPackage(packageFile, flags);
}
long cacheTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
cacheResult(packageFile, flags, parsed);
if (LOG_PARSE_TIMINGS) {
parseTime = cacheTime - parseTime;
cacheTime = SystemClock.uptimeMillis() - cacheTime;
if (parseTime + cacheTime > LOG_PARSE_TIMINGS_THRESHOLD_MS) {
Slog.i(TAG, "Parse times for '" + packageFile + "': parse=" + parseTime
+ "ms, update_cache=" + cacheTime + " ms");
}
}
return parsed;
}
//PackageParser.java
public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
if (mOnlyCoreApps) {
if (!lite.coreApp) {
throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
"Not a coreApp: " + apkFile);
}
}
final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
try {
//重点来了,开始解析apk
final Package pkg = parseBaseApk(apkFile, assetLoader.getBaseAssetManager(), flags);
pkg.setCodePath(apkFile.getCanonicalPath());
pkg.setUse32bitAbi(lite.use32bitAbi);
return pkg;
} catch (IOException e) {
throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
"Failed to get path: " + apkFile, e);
} finally {
IoUtils.closeQuietly(assetLoader);
}
}
//PackageParser.java
private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
throws PackageParserException {
final String apkPath = apkFile.getAbsolutePath();
String volumeUuid = null;
if (apkPath.startsWith(MNT_EXPAND)) {
final int end = apkPath.indexOf('/', MNT_EXPAND.length());
volumeUuid = apkPath.substring(MNT_EXPAND.length(), end);
}
mParseError = PackageManager.INSTALL_SUCCEEDED;
mArchiveSourcePath = apkFile.getAbsolutePath();
if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);
XmlResourceParser parser = null;
try {
final int cookie = assets.findCookieForPath(apkPath);
if (cookie == 0) {
throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
"Failed adding asset path: " + apkPath);
}
//得到xml解析其
parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
final Resources res = new Resources(assets, mMetrics, null);
final String[] outError = new String[1];
//使用解析器开始解析
final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError);
if (pkg == null) {
throw new PackageParserException(mParseError,
apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
}
pkg.setVolumeUuid(volumeUuid);
pkg.setApplicationVolumeUuid(volumeUuid);
pkg.setBaseCodePath(apkPath);
pkg.setSigningDetails(SigningDetails.UNKNOWN);
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);
}
}
//PackageParser.java
private Package parseBaseApk(String apkPath, Resources res, XmlResourceParser parser, int flags,
String[] outError) throws XmlPullParserException, IOException {
final String splitName;
final String pkgName;
try {
Pair<String, String> packageSplit = parsePackageSplitNames(parser, parser);
pkgName = packageSplit.first;
splitName = packageSplit.second;
if (!TextUtils.isEmpty(splitName)) {
outError[0] = "Expected base APK, but found split " + splitName;
mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
return null;
}
} catch (PackageParserException e) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME;
return null;
}
if (mCallback != null) {
String[] overlayPaths = mCallback.getOverlayPaths(pkgName, apkPath);
if (overlayPaths != null && overlayPaths.length > 0) {
for (String overlayPath : overlayPaths) {
res.getAssets().addOverlayPath(overlayPath);
}
}
}
//好戏开始,备好瓜子了么?
final Package pkg = new Package(pkgName);
//下面的这一系列标签看着都熟悉吧,什么versionCode、versionName、coreApp等等。不然你认为你凭什么得到一个PacnageManager就可以获取到这些信息了
TypedArray sa = res.obtainAttributes(parser,
com.android.internal.R.styleable.AndroidManifest);
pkg.mVersionCode = sa.getInteger(
com.android.internal.R.styleable.AndroidManifest_versionCode, 0);
pkg.mVersionCodeMajor = sa.getInteger(
com.android.internal.R.styleable.AndroidManifest_versionCodeMajor, 0);
pkg.applicationInfo.setVersionCode(pkg.getLongVersionCode());
pkg.baseRevisionCode = sa.getInteger(
com.android.internal.R.styleable.AndroidManifest_revisionCode, 0);
pkg.mVersionName = sa.getNonConfigurationString(
com.android.internal.R.styleable.AndroidManifest_versionName, 0);
if (pkg.mVersionName != null) {
pkg.mVersionName = pkg.mVersionName.intern();
}
pkg.coreApp = parser.getAttributeBooleanValue(null, "coreApp", false);
pkg.mCompileSdkVersion = sa.getInteger(
com.android.internal.R.styleable.AndroidManifest_compileSdkVersion, 0);
pkg.applicationInfo.compileSdkVersion = pkg.mCompileSdkVersion;
pkg.mCompileSdkVersionCodename = sa.getNonConfigurationString(
com.android.internal.R.styleable.AndroidManifest_compileSdkVersionCodename, 0);
if (pkg.mCompileSdkVersionCodename != null) {
pkg.mCompileSdkVersionCodename = pkg.mCompileSdkVersionCodename.intern();
}
pkg.applicationInfo.compileSdkVersionCodename = pkg.mCompileSdkVersionCodename;
sa.recycle();
//继续深入解析
return parseBaseApkCommon(pkg, null, res, parser, flags, outError);
}
//PackageParser.java
private Package parseBaseApkCommon(Package pkg, Set<String> acceptedTags, Resources res,
XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException,
IOException {
//.......此处省略N行代码
//四大组件是在APPLICATION里的节点,所以先找到这个节点
if (tagName.equals(TAG_APPLICATION)) {
if (foundApp) {
if (RIGID_PARSER) {
outError[0] = "<manifest> has more than one <application>";
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return null;
} else {
Slog.w(TAG, "<manifest> has more than one <application>");
XmlUtils.skipCurrentTag(parser);
continue;
}
}
foundApp = true;
//解析Application里面的节点
if (!parseBaseApplication(pkg, res, parser, flags, outError)) {
return null;
}
}
//.......此处省略N行代码
}
//PackageParser.java
private boolean parseBaseApplication(Package owner, Resources res,
XmlResourceParser parser, int flags, String[] outError)
throws XmlPullParserException, IOException {
//.......此处省略N行代码
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
String tagName = parser.getName();
//解析activity
if (tagName.equals("activity")) {
//注意,这个Activity并不是我们的活动类activity,只是一个实体类
Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs, false,
owner.baseHardwareAccelerated);
if (a == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
hasActivityOrder |= (a.order != 0);
owner.activities.add(a);
} else if (tagName.equals("receiver")) {//解析receiver
Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs,
true, false);
if (a == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
hasReceiverOrder |= (a.order != 0);
owner.receivers.add(a);
} else if (tagName.equals("service")) {//解析service
Service s = parseService(owner, res, parser, flags, outError, cachedArgs);
if (s == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
hasServiceOrder |= (s.order != 0);
owner.services.add(s);
} else if (tagName.equals("provider")) {//解析provider
Provider p = parseProvider(owner, res, parser, flags, outError, cachedArgs);
if (p == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
owner.providers.add(p);
} //...这后面其实还有很多,就只保留四大组件的吧
}
//.......此处省略N行代码
}
到这里整个AndroidManifest.xml的解析就基本完成了,parseActivity、parseService、parseProvider这些方法的具体代码有兴趣的自己跟进去看吧,值得注意的是由于activity与reciver的实体类基本一致,所以解析receiver实际用的是parseActivity这个方法。
OK,让我们在回头看下我们在一开始提出的问题:
1、我新机开机很快的,为什么后面越来越慢了
除开硬件老化而带来的性能下降之外,随着apk的安装越来越多,开机需要解析的apk也越来越多,所以会变慢。
2、google每次都说对某个版本的系统启动速度进行优化,到底优化了什么
在代码中我其实已经标注了相应的优化项,而其实开机时间所耗的70%基本都是用于apk文件的解析来了。
3、一个APK文件是怎么被解析以及运行的
PMS只是负责解析APK文件对应的AndroidManifest.xml文件,为后续启动做准备,真正是如何启动的,留待后续的AMS来吧。需要特别注意的是这里的解析只是解析了AndroidManifest.xml这个文件,将里面的内容解析出来留作备用,并没有去解析任何一个具体的java文件,解析的四大组件activity与我们编写的活动是不同的。不然将所有的文件解析出来我们的机器需要多大的内存才有可能支撑啊得 住啊。
4、为什么需要AndroidManifest.xml,google为什么要去工程师要在里面进行注册四大组件
嗯,这个其实吧要是你不注册,解析的时候压根都解析不到,那后续真正运行的时候又去哪找呢?要是你说等到允许的时候在去找不行嘛,从理论上来说是可以的,但你点击之后让你等1秒你的意见不是也是蛮大嘛。在程序的世界里有用时间换空间,也有用空间换时间的说法,至于如何取舍,根据具体场景具体分析吧。
结束语:这些代码其实我们一般是不会去更改它的,开机的逻辑一般的开发者我们也不会修改。分析这个流程的目的是让我们对整个启动流程更加清晰,明白其原理。当在特定场景下涉及到一些优化的时候,我们可以更从容的应对或者跳过某些坑。在则嘛,多懂一些知识也没坏处吧。
PS ME:为什么那么懒,老是不将知识点记录下来呢!今年要改掉这个坏习惯。