其实提到自升级,相信大多数的伙伴们更多想到的是通过热修复的方式,从dex文件入手利用类加载机制完成bugfix。当然这也是应用自升级的一种方案,像Tinker、Robust这些成熟的框架已经帮我们做了很多基础的工作,但是如果我们是在做ROM,或者智能硬件(车载座舱、电视、汽车等)方面的工作,我们维护的可能就不只是一个app,从系统应用到自研应用少则10几个,多则20+,30+,如果对每个app都做热修复继承,显然是有点儿浪费资源了。
因为业务升级迭代的速度是很快的,但是ROM已经是比较稳定的,如果每次业务升级都需要配合ROM显然是比较重的一次操作,因此在业务快速迭代的阶段,我们对某些app做统一的自升级,
不同的版本升级新的功能,就类似于我们在研发阶段不断地install本地debug应用测试,其实就相当于将v1.0版本的业务app卸载掉,然后重新安装v1.1版本的业务app,那么系统哪个服务可以完成这一系列的操作呢,就是PKMS。
1 PKMS介绍
什么是PKMS,全称为PackageManagerService,从字面意思上来看,就是包管理服务,或者是apk的管理服务,可以完成apk的信息查询、安装以及卸载。当我们打开手机之后进入到Launcher页面之后,我们看到所有的应用展示都是通过PKMS查询到的,以ListView的形式展示出来,包括我们长按应用卸载,也是调用了PKMS的能力。
1.1 PKMS进程间通信
通过前面我们对于系统启动流程的分析,我们知道当SystemServer进程启动之后,就会启动像AMS、PKMS等核心服务,所以PKMS是属于SystemServer进程的,此时客户端想要调用PKMS服务的能力,必然涉及到了跨进程通信,我们先看下跨进程通信是如何完成的。
一般在客户端想要调用PKMS的能力,首先需要获取到PackageManager对象,这是一个抽象类,
packageManager.getPackageInfo("com.lay.pkms",0)
具体实现类为ApplicationPackageManager,我们看下它的源码,这里就会有进程间通信的实现,因为肯定是调用到了PKMS的能力,我们拿getPackageInfo这个方法来看。
注意这个类是一个隐藏类,因此想要查看具体的实现需要从源码中查找,路径如下:
frameworks/base/core/java/android/app/ApplicationPackageManager.java
public class ApplicationPackageManager extends PackageManager {
private static final String TAG = "ApplicationPackageManager";
private final static boolean DEBUG_ICONS = false;
private static final int DEFAULT_EPHEMERAL_COOKIE_MAX_SIZE_BYTES = 16384; // 16KB
// Default flags to use with PackageManager when no flags are given.
private final static int sDefaultFlags = PackageManager.GET_SHARED_LIBRARY_FILES;
private final Object mLock = new Object();
@GuardedBy("mLock")
private UserManager mUserManager;
@GuardedBy("mLock")
private PackageInstaller mInstaller;
@GuardedBy("mLock")
private ArtManager mArtManager;
@GuardedBy("mDelegates")
private final ArrayList<MoveCallbackDelegate> mDelegates = new ArrayList<>();
@GuardedBy("mLock")
private String mPermissionsControllerPackageName;
UserManager getUserManager() {
synchronized (mLock) {
if (mUserManager == null) {
mUserManager = UserManager.get(mContext);
}
return mUserManager;
}
}
@Override
public int getUserId() {
return mContext.getUserId();
}
@Override
public PackageInfo getPackageInfo(String packageName, int flags)
throws NameNotFoundException {
return getPackageInfoAsUser(packageName, flags, mContext.getUserId());
}
@Override
public PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId)
throws NameNotFoundException {
try {
PackageInfo pi = mPM.getPackageInfo(packageName, flags, userId);
if (pi != null) {
return pi;
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
throw new NameNotFoundException(packageName);
}
}
我们看到在调用getPackageInfo方法时,最终调用了getPackageInfoAsUser方法,在这个方法中,我们看到有一个变量mPM,看样子就是PKMS的一个代理对象,所以我们只需要确定mPM是哪个类的对象即可。
private final IPackageManager mPM;
从源码中可以看到,mPM是IPackageManager的一个实例对象,而IPackageManager就是 SystemServer和客户端进程间通信的aidl,其路径在下面:
frameworks/base/core/java/android/content/pm/IPackageManager.aidl
因为这个文件里的方法太多了,我就不贴出来了,有兴趣的伙伴可以自行查看。
那么mPM是在什么时候初始化的呢?
protected ApplicationPackageManager(ContextImpl context,
IPackageManager pm) {
mContext = context;
mPM = pm;
}
通过源码我们发现,在ApplicationPackageManager的构造方法中,我们看到会对其进行赋值,因此我们前面看到getPackageManager方法调用时,应该就是创建了ApplicationPackageManager对象。
@Override
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;
}
所以在getPackageInfoAsUser方法中,就是调用了PKMS的getPackageInfo方法,所以C/S之间的通信方式如下:
1.2 PKMS启动流程分析
前面我们提了一嘴,在SystemServer进程启动之后,就会启动PKMS,现在我们详细分析一下,PKMS的启动流程到底干了什么事。
1.2.1 SystemServer引导服务启动流程
具体的SystemServer启动流程,感兴趣的伙伴可以去前面的文章中看,我们直接进入到SystemServer的startBootstrapServices方法中。
try {
Watchdog.getInstance().pauseWatchingCurrentThread("packagemanagermain");
mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
} finally {
Watchdog.getInstance().resumeWatchingCurrentThread("packagemanagermain");
}
跳过其他服务的启动,我们看到在启动PackageManagerService服务的时候,是直接调用了其mian方法。
// 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.
t.traceBegin("StartInstaller");
Installer installer = mSystemServiceManager.startService(Installer.class);
t.traceEnd();
在此之前,我们先关注一个Installer服务,通过注释我们发现这个服务还是挺重要的,在初始化其他服务之前,首先要保证Installer服务的启动已经完成。
Only run "core" apps if we're encrypting the device.
String cryptState = VoldProperties.decrypt().orElse("");
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;
}
然后会判断手机是否加密了,例如设置了手机密码,那么就只会解析core app,像做车载应用,通常都会将这个变量写死为false,因为没有隐私相关的app可以使用,一般车载也不会有设置密码这个选项。
然后就会调用PackageManagerService的main方法,这里我们看到是把installer服务、手机是否加密作为变量参数传进去了。伙伴们注意一下,这个方法是一个耗时方法,而且在手机开启启动的时候会慢,也是这个方法在捣鬼。
在启动PKMS完成之后,会根据手机设备是否加密,来决定是否启动OtaDexoptService服务。OtaDexoptService主要用来管理A/B升级和OTA升级以及dex文件优化,对于这两个升级名词如果做过系统升级的伙伴可能会了解,当系统升级时,端上会调用OS与A/B升级和OTA升级相关的接口,完成系统升级。
// Manages A/B OTA dexopting. This is a bootstrap service as we need it to rename
// A/B artifacts after boot, before anything else might touch/need them.
// Note: this isn't needed during decryption (we don't have /data anyways).
if (!mOnlyCore) {
boolean disableOtaDexopt = SystemProperties.getBoolean("config.disable_otadexopt",
false);
if (!disableOtaDexopt) {
t.traceBegin("StartOtaDexOptService");
try {
Watchdog.getInstance().pauseWatchingCurrentThread("moveab");
OtaDexoptService.main(mSystemContext, mPackageManagerService);
} catch (Throwable e) {
reportWtf("starting OtaDexOptService", e);
} finally {
Watchdog.getInstance().resumeWatchingCurrentThread("moveab");
t.traceEnd();
}
}
}
当然更新策略也是根据app等级划分,core app > system app > other app,所以硬件设备是否支持加密(设置密码),对OTA升级有着直接影响。
对于dex的优化,就是在引导服务流程结束之后,在startOtherServices方法中进行的。
1.2.2 其他服务启动startOtherServices
private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
// ......
if (!mOnlyCore) {
t.traceBegin("UpdatePackagesIfNeeded");
try {
Watchdog.getInstance().pauseWatchingCurrentThread("dexopt");
mPackageManagerService.updatePackagesIfNeeded();
} catch (Throwable e) {
reportWtf("update packages", e);
} finally {
Watchdog.getInstance().resumeWatchingCurrentThread("dexopt");
}
t.traceEnd();
}
// ......
try {
//完成磁盘清理维护
mPackageManagerService.performFstrimIfNeeded();
} catch (Throwable e) {
reportWtf("performing fstrim", e);
}
// ......
mPackageManagerService.systemReady();
}
这里只看与PKMS相关的处理;首先会判断手机是否加密,如果没有加密,那么就会判断是否有package需要更新,dex是否需要优化。
然后会调用performFstrimIfNeeded进行磁盘清理,最后重要的一步就是会调用systemReady方法,表示PKMS准备就绪了,如果这个时候服务出现异常,那么后续服务就不会再初始化,就会导致手机开启死机。
那么这里我们总结一下,PKMS准备就绪需要的环节:
- 启动Installer服务,是阻塞的,后续服务初始化的前提;
- 判断手机是否加密,如果加密,则mOnlyCore = true,否则为false;
- 执行PKMS的main方法,耗时阻塞的;
- 根据mOnlyCore决定启动升级服务OtaDexoptService;
- 根据mOnlyCore判断是否需要检查包更新,即调用PKMS的updatePackagesIfNeeded;
- 调用PKMS的performFstrimIfNeeded方法进行磁盘清理;
- 执行PKMS的systemReady方法,表示PKMS已经准备就绪了。
所以当手机启动比较慢的时候,分析原因就可以从这几个方向入手。
2 PKMS main方法分析
前面主要介绍了PKMS在启动时到准备就绪的7个步骤,其中在第3步中调用了PKMS的main方法,我们看下在main方法中的处理逻辑。
public static PackageManagerService main(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
// Self-check for initial settings.
PackageManagerServiceCompilerMapping.checkProperties();
final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
Trace.TRACE_TAG_PACKAGE_MANAGER);
t.traceBegin("create package manager");
final Object lock = new Object();
final Object installLock = new Object();
Injector injector = new Injector(
context, lock, installer, installLock, new PackageAbiHelperImpl(),
(i, pm) ->
new ComponentResolver(i.getUserManagerService(), pm.mPmInternal, lock),
(i, pm) ->
PermissionManagerService.create(context, lock),
(i, pm) ->
new UserManagerService(context, pm,
new UserDataPreparer(installer, installLock, context, onlyCore),
lock),
(i, pm) ->
new Settings(Environment.getDataDirectory(),
i.getPermissionManagerServiceInternal().getPermissionSettings(),
lock),
new Injector.LocalServicesProducer<>(ActivityTaskManagerInternal.class),
new Injector.LocalServicesProducer<>(ActivityManagerInternal.class),
new Injector.LocalServicesProducer<>(DeviceIdleInternal.class),
new Injector.LocalServicesProducer<>(StorageManagerInternal.class),
new Injector.LocalServicesProducer<>(NetworkPolicyManagerInternal.class),
new Injector.LocalServicesProducer<>(PermissionPolicyInternal.class),
new Injector.LocalServicesProducer<>(DeviceStorageMonitorInternal.class),
new Injector.SystemServiceProducer<>(DisplayManager.class),
new Injector.SystemServiceProducer<>(StorageManager.class),
new Injector.SystemServiceProducer<>(AppOpsManager.class),
(i, pm) -> AppsFilter.create(pm.mPmInternal, i),
(i, pm) -> (PlatformCompat) ServiceManager.getService("platform_compat"));
PackageManagerService m = new PackageManagerService(injector, onlyCore, factoryTest);
t.traceEnd(); // "create package manager"
injector.getCompatibility().registerListener(SELinuxMMAC.SELINUX_LATEST_CHANGES,
packageName -> {
synchronized (m.mInstallLock) {
final AndroidPackage pkg;
final PackageSetting ps;
final SharedUserSetting sharedUser;
final String oldSeInfo;
synchronized (m.mLock) {
ps = m.mSettings.getPackageLPr(packageName);
if (ps == null) {
Slog.e(TAG, "Failed to find package setting " + packageName);
return;
}
pkg = ps.pkg;
sharedUser = ps.getSharedUser();
oldSeInfo = AndroidPackageUtils.getSeInfo(pkg, ps);
}
if (pkg == null) {
Slog.e(TAG, "Failed to find package " + packageName);
return;
}
final String newSeInfo = SELinuxMMAC.getSeInfo(pkg, sharedUser,
m.mInjector.getCompatibility());
if (!newSeInfo.equals(oldSeInfo)) {
Slog.i(TAG, "Updating seInfo for package " + packageName + " from: "
+ oldSeInfo + " to: " + newSeInfo);
ps.getPkgState().setOverrideSeInfo(newSeInfo);
m.prepareAppDataAfterInstallLIF(pkg);
}
}
});
m.installWhitelistedSystemPackages();
ServiceManager.addService("package", m);
final PackageManagerNative pmn = m.new PackageManagerNative();
ServiceManager.addService("package_native", pmn);
return m;
}
在main方法中是存在返回值的,返回的就是PKMS实例对象,在main方法中,也是通过new出来了一个PackageManagerService对象,我们看下PKMS的构造方法。
如果看过源码的伙伴会发现,PKMS的构造方法有上千行代码,这里只看核心方法即可,通过构造方法打印的日志,可以分为5个阶段进行分析。
2.1 阶段1- BOOT_PROGRESS_PMS_START
这个阶段可以认为是启动阶段,主要做一些初始化的操作。
2.2 阶段2- BOOT_PROGRESS_PMS_SYSTEM_SCAN_START
在这个阶段开始,进入到系统扫描阶段,在这个阶段里会扫描/system/app,system/priv-app等系统目录下的app,例如蓝牙apk、相机apk,以及各个业务存储的/system/app(如果需要预置权限)业务apk,统统都会扫描到,存储在PKMS当中,此时在开启启动的时候,就已经拿到了手机当中存在的所有apk,那么在进入到Launcher之后,就会拿到这些apk的信息,展示在ListView列表当中。
2.3 阶段3- BOOT_PROGRESS_PMS_DATA_SCAN_START
第三个阶段为扫描普通应用阶段,前面主要是从系统目录中扫描系统apk,那么用户从应用商店下载的应用会存储在user分区,那么此时就会从sAppInstallDir目录中去查找。
if (!mOnlyCore) {
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
SystemClock.uptimeMillis());
scanDirTracedLI(sAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0,
packageParser, executorService);
}
我们先看下sAppInstallDir具体是哪个目录,就是存储在/data/app目录下的全部应用;
/** Directory where installed applications are stored */
private static final File sAppInstallDir =
new File(Environment.getDataDirectory(), "app");
所以普通应用的扫描就是从scanDirTracedLI方法开始。
- 第一步,调用scanDirLI方法
private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime,
PackageParser2 packageParser, ExecutorService executorService) {
final File[] files = scanDir.listFiles();
//......
ParallelPackageParser parallelPackageParser =
new ParallelPackageParser(packageParser, executorService);
// Submit files for parsing in parallel
int fileCount = 0;
for (File file : files) {
final boolean isPackage = (isApkFile(file) || file.isDirectory())
&& !PackageInstallerService.isStageName(file.getName());
if (!isPackage) {
// Ignore entries which are not packages
continue;
}
//
parallelPackageParser.submit(file, parseFlags);
fileCount++;
}
// Process results one by one
for (; fileCount > 0; fileCount--) {
ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
Throwable throwable = parseResult.throwable;
int errorCode = PackageManager.INSTALL_SUCCEEDED;
if (throwable == null) {
// TODO(toddke): move lower in the scan chain
// Static shared libraries have synthetic package names
if (parseResult.parsedPackage.isStaticSharedLibrary()) {
renameStaticSharedLibraryPackage(parseResult.parsedPackage);
}
try {
addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags,
currentTime, null);
} catch (PackageManagerException e) {
errorCode = e.error;
Slog.w(TAG, "Failed to scan " + parseResult.scanFile + ": " + e.getMessage());
}
} else if (throwable instanceof PackageParserException) {
PackageParserException e = (PackageParserException)
throwable;
errorCode = e.error;
Slog.w(TAG, "Failed to parse " + parseResult.scanFile + ": " + e.getMessage());
} else {
throw new IllegalStateException("Unexpected exception occurred while parsing "
+ parseResult.scanFile, throwable);
}
if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0 && errorCode != INSTALL_SUCCEEDED) {
mApexManager.reportErrorWithApkInApex(scanDir.getAbsolutePath());
}
// Delete invalid userdata apps
if ((scanFlags & SCAN_AS_SYSTEM) == 0
&& errorCode != PackageManager.INSTALL_SUCCEEDED) {
logCriticalInfo(Log.WARN,
"Deleting invalid package at " + parseResult.scanFile);
removeCodePathLI(parseResult.scanFile);
}
}
}
首先创建一个ParallelPackageParser对象,这个类的主要作用就是内部维护了线程池和阻塞队列。
class ParallelPackageParser {
private static final int QUEUE_CAPACITY = 30;
private static final int MAX_THREADS = 4;
private volatile String mInterruptedInThread;
private final BlockingQueue<ParseResult> mQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY);
static ExecutorService makeExecutorService() {
return ConcurrentUtils.newFixedThreadPool(MAX_THREADS, "package-parsing-thread",
Process.THREAD_PRIORITY_FOREGROUND);
}
}
然后会遍历/data/app目录下全部的文件,找到apk文件,调用ParallelPackageParser的subimt方法,将其入队。
- 第二步:调用ParallelPackageParser的submit方法
/**
* Submits the file for parsing
* @param scanFile file to scan
* @param parseFlags parse flags
*/
public void submit(File scanFile, int parseFlags) {
mExecutorService.submit(() -> {
ParseResult pr = new ParseResult();
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parallel parsePackage [" + scanFile + "]");
try {
pr.scanFile = scanFile;
pr.parsedPackage = parsePackage(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();
}
});
}
这个方法比较简单,就是通过线程池,在内部封装了一个ParseResult对象,将apk文件作为其对象参数,最终将ParseResult对象加入BlockQueue队列中。
- 第三步:PackageParser2.parsePackage
这里为啥要开线程池,一个就是考虑并发的问题,再一个就是我们看到会调用parsePackage方法解析,通过PackageParser2进行解析。
protected ParsedPackage parsePackage(File scanFile, int parseFlags)
throws PackageParser.PackageParserException {
return mPackageParser.parsePackage(scanFile, parseFlags, true);
}
我们看下PackageParser2中的parsePackage方法,这个方法会返回一个解析过的package对象ParsedPackage。
@AnyThread
public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches)
throws PackageParserException {
// ......
ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags);
if (result.isError()) {
throw new PackageParserException(result.getErrorCode(), result.getErrorMessage(),
result.getException());
}
ParsedPackage parsed = (ParsedPackage) result.getResult().hideAsParsed();
long cacheTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
if (mCacher != null) {
mCacher.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;
}
在这个方法内部,会调用ParsingPackageUtils的parsePackage方法,最终会返回解析的结果。
public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile,
int flags)
throws PackageParserException {
if (packageFile.isDirectory()) {
return parseClusterPackage(input, packageFile, flags);
} else {
return parseMonolithicPackage(input, packageFile, flags);
}
}
在parsePackage方法中,首先会判断传进来的文件是文件夹还是文件;如果是文件夹,那么就会调用parseClusterPackage方法,如果是文件,那么就会调用parseMonolithicPackage方法。
因为我们传进来的是apk文件,我们关注下parseMonolithicPackage这个方法。
/**
* Parse the given APK file, treating it as as a single monolithic package.
* <p>
* Note that this <em>does not</em> perform signature verification; that
* must be done separately in {@link #getSigningDetails(ParsingPackageRead, boolean)}.
*/
private ParseResult<ParsingPackage> parseMonolithicPackage(ParseInput input, File apkFile,
int flags) throws PackageParserException {
ParseResult<PackageParser.PackageLite> liteResult =
ApkLiteParseUtils.parseMonolithicPackageLite(input, apkFile, flags);
if (liteResult.isError()) {
return input.error(liteResult);
}
final PackageParser.PackageLite lite = liteResult.getResult();
if (mOnlyCoreApps && !lite.coreApp) {
return input.error(INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED,
"Not a coreApp: " + apkFile);
}
final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
try {
//核心函数
ParseResult<ParsingPackage> result = parseBaseApk(input,
apkFile,
apkFile.getCanonicalPath(),
assetLoader.getBaseAssetManager(), flags);
if (result.isError()) {
return input.error(result);
}
return input.success(result.getResult()
.setUse32BitAbi(lite.use32bitAbi));
} catch (IOException e) {
return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
"Failed to get path: " + apkFile, e);
} finally {
IoUtils.closeQuietly(assetLoader);
}
}
在这个方法中,核心代码为parseBaseApk方法的调用,这个方法用于对单体apk进行解析,最终返回解析之后的结果。
- 第四步:parseBaseApk方法调用
在parseBaseApk方法中,会调用其重载方法,我们关注下这个重载方法,看代码注释是用于解析apk的manifest清单文件。
/**
* Parse the manifest of a <em>base APK</em>. When adding new features you
* need to consider whether they should be supported by split APKs and child
* packages.
*
* @param apkPath The package apk file path
* @param res The resources from which to resolve values
* @param parser The manifest parser
* @param flags Flags how to parse
* @return Parsed package or null on error.
*/
private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, String apkPath,
String codePath, Resources res, XmlResourceParser parser, int flags)
throws XmlPullParserException, IOException, PackageParserException {
final String splitName;
final String pkgName;
ParseResult<Pair<String, String>> packageSplitResult =
ApkLiteParseUtils.parsePackageSplitNames(input, parser, parser);
if (packageSplitResult.isError()) {
return input.error(packageSplitResult);
}
Pair<String, String> packageSplit = packageSplitResult.getResult();
pkgName = packageSplit.first;
splitName = packageSplit.second;
if (!TextUtils.isEmpty(splitName)) {
return input.error(
PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
"Expected base APK, but found split " + splitName
);
}
//解析Manifest
final TypedArray manifestArray = res.obtainAttributes(parser, R.styleable.AndroidManifest);
try {
final boolean isCoreApp =
parser.getAttributeBooleanValue(null, "coreApp", false);
final ParsingPackage pkg = mCallback.startParsingPackage(
pkgName, apkPath, codePath, manifestArray, isCoreApp);
final ParseResult<ParsingPackage> result =
parseBaseApkTags(input, pkg, manifestArray, res, parser, flags);
if (result.isError()) {
return result;
}
return input.success(pkg);
} finally {
manifestArray.recycle();
}
}
在这个方法中,首先会解析manifest清单文件拿到一个TypedArray,其中记录了当前apk清单文件中每个节点的信息,调用parseBaseApkTags方法。
private ParseResult<ParsingPackage> parseBaseApkTags(ParseInput input, ParsingPackage pkg,
TypedArray sa, Resources res, XmlResourceParser parser, int flags)
throws XmlPullParserException, IOException {
ParseResult<ParsingPackage> sharedUserResult = parseSharedUser(input, pkg, sa);
if (sharedUserResult.isError()) {
return sharedUserResult;
}
//设置apk存储位置 pkg.setInstallLocation(anInteger(PackageParser.PARSE_DEFAULT_INSTALL_LOCATION,
R.styleable.AndroidManifest_installLocation, sa))
.setTargetSandboxVersion(anInteger(PackageParser.PARSE_DEFAULT_TARGET_SANDBOX,
R.styleable.AndroidManifest_targetSandboxVersion, sa))
/* Set the global "on SD card" flag */
.setExternalStorage((flags & PackageParser.PARSE_EXTERNAL_STORAGE) != 0);
boolean foundApp = false;
final int depth = parser.getDepth();
int type;
//解析清单文件
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG
|| parser.getDepth() > depth)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
String tagName = parser.getName();
final ParseResult result;
// TODO(b/135203078): Convert to instance methods to share variables
// <application> has special logic, so it's handled outside the general method
// 核心代码
if (PackageParser.TAG_APPLICATION.equals(tagName)) {
if (foundApp) {
if (PackageParser.RIGID_PARSER) {
result = input.error("<manifest> has more than one <application>");
} else {
Slog.w(TAG, "<manifest> has more than one <application>");
result = input.success(null);
}
} else {
foundApp = true;
result = parseBaseApplication(input, pkg, res, parser, flags);
}
} else {
result = parseBaseApkTag(tagName, input, pkg, res, parser, flags);
}
if (result.isError()) {
return input.error(result);
}
}
if (!foundApp && ArrayUtils.size(pkg.getInstrumentations()) == 0) {
ParseResult<?> deferResult = input.deferError(
"<manifest> does not contain an <application> or <instrumentation>",
DeferredError.MISSING_APP_TAG);
if (deferResult.isError()) {
return input.error(deferResult);
}
}
if (!ParsedAttribution.isCombinationValid(pkg.getAttributions())) {
return input.error(
INSTALL_PARSE_FAILED_BAD_MANIFEST,
"Combination <feature> tags are not valid"
);
}
convertNewPermissions(pkg);
convertSplitPermissions(pkg);
// At this point we can check if an application is not supporting densities and hence
// cannot be windowed / resized. Note that an SDK version of 0 is common for
// pre-Doughnut applications.
if (pkg.getTargetSdkVersion() < DONUT
|| (!pkg.isSupportsSmallScreens()
&& !pkg.isSupportsNormalScreens()
&& !pkg.isSupportsLargeScreens()
&& !pkg.isSupportsExtraLargeScreens()
&& !pkg.isResizeable()
&& !pkg.isAnyDensity())) {
adjustPackageToBeUnresizeableAndUnpipable(pkg);
}
return input.success(pkg);
}
parseBaseApkTags从方法名中就可以猜到,其实就是解析apk的参数,也就是Manifest清单文件的解析。
- 核心代码
在解析清单文件时,判断是否为application标签。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.appbuilder">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
如果是application标签,因为foundApp默认为false,就会将其置为true,代表找到了application,此时会调用parseBaseApplication方法。
后续这个foundApp标志位判断,如果为false,那么就会报错,因为没有application标签,具体异常可以看代码,我们接下来看parseBaseApplication方法。
- 第五步:parseBaseApplication解析application标签
private ParseResult<ParsingPackage> parseBaseApplication(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags)
throws XmlPullParserException, IOException {
final String pkgName = pkg.getPackageName();
int targetSdk = pkg.getTargetSdkVersion();
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestApplication);
try {
// TODO(b/135203078): Remove this and force unit tests to mock an empty manifest
// This case can only happen in unit tests where we sometimes need to create fakes
// of various package parser data structures.
if (sa == null) {
return input.error("<application> does not contain any attributes");
}
String name = sa.getNonConfigurationString(R.styleable.AndroidManifestApplication_name,
0);
if (name != null) {
String packageName = pkg.getPackageName();
String outInfoName = ParsingUtils.buildClassName(packageName, name);
if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(outInfoName)) {
return input.error("<application> invalid android:name");
} else if (outInfoName == null) {
return input.error("Empty class name in package " + packageName);
}
pkg.setClassName(outInfoName);
}
TypedValue labelValue = sa.peekValue(R.styleable.AndroidManifestApplication_label);
if (labelValue != null) {
pkg.setLabelRes(labelValue.resourceId);
if (labelValue.resourceId == 0) {
pkg.setNonLocalizedLabel(labelValue.coerceToString());
}
}
parseBaseAppBasicFlags(pkg, sa);
String manageSpaceActivity = nonConfigString(Configuration.NATIVE_CONFIG_VERSION,
R.styleable.AndroidManifestApplication_manageSpaceActivity, sa);
if (manageSpaceActivity != null) {
String manageSpaceActivityName = ParsingUtils.buildClassName(pkgName,
manageSpaceActivity);
if (manageSpaceActivityName == null) {
return input.error("Empty class name in package " + pkgName);
}
pkg.setManageSpaceActivityName(manageSpaceActivityName);
}
if (pkg.isAllowBackup()) {
// backupAgent, killAfterRestore, fullBackupContent, backupInForeground,
// and restoreAnyVersion are only relevant if backup is possible for the
// given application.
String backupAgent = nonConfigString(Configuration.NATIVE_CONFIG_VERSION,
R.styleable.AndroidManifestApplication_backupAgent, sa);
if (backupAgent != null) {
String backupAgentName = ParsingUtils.buildClassName(pkgName, backupAgent);
if (backupAgentName == null) {
return input.error("Empty class name in package " + pkgName);
}
if (PackageParser.DEBUG_BACKUP) {
Slog.v(TAG, "android:backupAgent = " + backupAgentName
+ " from " + pkgName + "+" + backupAgent);
}
pkg.setBackupAgentName(backupAgentName)
.setKillAfterRestore(bool(true,
R.styleable.AndroidManifestApplication_killAfterRestore, sa))
.setRestoreAnyVersion(bool(false,
R.styleable.AndroidManifestApplication_restoreAnyVersion, sa))
.setFullBackupOnly(bool(false,
R.styleable.AndroidManifestApplication_fullBackupOnly, sa))
.setBackupInForeground(bool(false,
R.styleable.AndroidManifestApplication_backupInForeground, sa));
}
TypedValue v = sa.peekValue(
R.styleable.AndroidManifestApplication_fullBackupContent);
int fullBackupContent = 0;
if (v != null) {
fullBackupContent = v.resourceId;
if (v.resourceId == 0) {
if (PackageParser.DEBUG_BACKUP) {
Slog.v(TAG, "fullBackupContent specified as boolean=" +
(v.data == 0 ? "false" : "true"));
}
// "false" => -1, "true" => 0
fullBackupContent = v.data == 0 ? -1 : 0;
}
pkg.setFullBackupContent(fullBackupContent);
}
if (PackageParser.DEBUG_BACKUP) {
Slog.v(TAG, "fullBackupContent=" + fullBackupContent + " for " + pkgName);
}
}
if (sa.getBoolean(R.styleable.AndroidManifestApplication_persistent, false)) {
// Check if persistence is based on a feature being present
final String requiredFeature = sa.getNonResourceString(R.styleable
.AndroidManifestApplication_persistentWhenFeatureAvailable);
pkg.setPersistent(requiredFeature == null || mCallback.hasFeature(requiredFeature));
}
// TODO(b/135203078): Should parsing code be responsible for this? Maybe move to a
// util or just have PackageImpl return true if either flag is set
// Debuggable implies profileable
pkg.setProfileableByShell(pkg.isProfileableByShell() || pkg.isDebuggable());
if (sa.hasValueOrEmpty(R.styleable.AndroidManifestApplication_resizeableActivity)) {
pkg.setResizeableActivity(sa.getBoolean(
R.styleable.AndroidManifestApplication_resizeableActivity, true));
} else {
pkg.setResizeableActivityViaSdkVersion(
targetSdk >= Build.VERSION_CODES.N);
}
String taskAffinity;
if (targetSdk >= Build.VERSION_CODES.FROYO) {
taskAffinity = sa.getNonConfigurationString(
R.styleable.AndroidManifestApplication_taskAffinity,
Configuration.NATIVE_CONFIG_VERSION);
} else {
// Some older apps have been seen to use a resource reference
// here that on older builds was ignored (with a warning). We
// need to continue to do this for them so they don't break.
taskAffinity = sa.getNonResourceString(
R.styleable.AndroidManifestApplication_taskAffinity);
}
ParseResult<String> taskAffinityResult = ComponentParseUtils.buildTaskAffinityName(
pkgName, pkgName, taskAffinity, input);
if (taskAffinityResult.isError()) {
return input.error(taskAffinityResult);
}
pkg.setTaskAffinity(taskAffinityResult.getResult());
String factory = sa.getNonResourceString(
R.styleable.AndroidManifestApplication_appComponentFactory);
if (factory != null) {
String appComponentFactory = ParsingUtils.buildClassName(pkgName, factory);
if (appComponentFactory == null) {
return input.error("Empty class name in package " + pkgName);
}
pkg.setAppComponentFactory(appComponentFactory);
}
CharSequence pname;
if (targetSdk >= Build.VERSION_CODES.FROYO) {
pname = sa.getNonConfigurationString(
R.styleable.AndroidManifestApplication_process,
Configuration.NATIVE_CONFIG_VERSION);
} else {
// Some older apps have been seen to use a resource reference
// here that on older builds was ignored (with a warning). We
// need to continue to do this for them so they don't break.
pname = sa.getNonResourceString(
R.styleable.AndroidManifestApplication_process);
}
ParseResult<String> processNameResult = ComponentParseUtils.buildProcessName(
pkgName, null, pname, flags, mSeparateProcesses, input);
if (processNameResult.isError()) {
return input.error(processNameResult);
}
String processName = processNameResult.getResult();
pkg.setProcessName(processName);
if (pkg.isCantSaveState()) {
// A heavy-weight application can not be in a custom process.
// We can do direct compare because we intern all strings.
if (processName != null && !processName.equals(pkgName)) {
return input.error(
"cantSaveState applications can not use custom processes");
}
}
String classLoaderName = pkg.getClassLoaderName();
if (classLoaderName != null
&& !ClassLoaderFactory.isValidClassLoaderName(classLoaderName)) {
return input.error("Invalid class loader name: " + classLoaderName);
}
pkg.setGwpAsanMode(sa.getInt(R.styleable.AndroidManifestApplication_gwpAsanMode, -1));
} finally {
sa.recycle();
}
boolean hasActivityOrder = false;
boolean hasReceiverOrder = false;
boolean hasServiceOrder = false;
final int depth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG
|| parser.getDepth() > depth)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final ParseResult result;
String tagName = parser.getName();
boolean isActivity = false;
switch (tagName) {
case "activity":
isActivity = true;
// fall-through
case "receiver":
ParseResult<ParsedActivity> activityResult =
ParsedActivityUtils.parseActivityOrReceiver(mSeparateProcesses, pkg,
res, parser, flags, PackageParser.sUseRoundIcon, input);
if (activityResult.isSuccess()) {
ParsedActivity activity = activityResult.getResult();
if (isActivity) {
hasActivityOrder |= (activity.getOrder() != 0);
pkg.addActivity(activity);
} else {
hasReceiverOrder |= (activity.getOrder() != 0);
pkg.addReceiver(activity);
}
}
result = activityResult;
break;
case "service":
ParseResult<ParsedService> serviceResult =
ParsedServiceUtils.parseService(mSeparateProcesses, pkg, res, parser,
flags, PackageParser.sUseRoundIcon, input);
if (serviceResult.isSuccess()) {
ParsedService service = serviceResult.getResult();
hasServiceOrder |= (service.getOrder() != 0);
pkg.addService(service);
}
result = serviceResult;
break;
case "provider":
ParseResult<ParsedProvider> providerResult =
ParsedProviderUtils.parseProvider(mSeparateProcesses, pkg, res, parser,
flags, PackageParser.sUseRoundIcon, input);
if (providerResult.isSuccess()) {
pkg.addProvider(providerResult.getResult());
}
result = providerResult;
break;
case "activity-alias":
activityResult = ParsedActivityUtils.parseActivityAlias(pkg, res,
parser, PackageParser.sUseRoundIcon, input);
if (activityResult.isSuccess()) {
ParsedActivity activity = activityResult.getResult();
hasActivityOrder |= (activity.getOrder() != 0);
pkg.addActivity(activity);
}
result = activityResult;
break;
default:
result = parseBaseAppChildTag(input, tagName, pkg, res, parser, flags);
break;
}
if (result.isError()) {
return input.error(result);
}
}
if (TextUtils.isEmpty(pkg.getStaticSharedLibName())) {
// Add a hidden app detail activity to normal apps which forwards user to App Details
// page.
ParseResult<ParsedActivity> a = generateAppDetailsHiddenActivity(input, pkg);
if (a.isError()) {
// Error should be impossible here, as the only failure case as of SDK R is a
// string validation error on a constant ":app_details" string passed in by the
// parsing code itself. For this reason, this is just a hard failure instead of
// deferred.
return input.error(a);
}
pkg.addActivity(a.getResult());
}
if (hasActivityOrder) {
pkg.sortActivities();
}
if (hasReceiverOrder) {
pkg.sortReceivers();
}
if (hasServiceOrder) {
pkg.sortServices();
}
// Must be run after the entire {@link ApplicationInfo} has been fully processed and after
// every activity info has had a chance to set it from its attributes.
setMaxAspectRatio(pkg);
setMinAspectRatio(pkg);
setSupportsSizeChanges(pkg);
pkg.setHasDomainUrls(hasDomainURLs(pkg));
return input.success(pkg);
}
这个方法中,其实就会解析application标签中所有的信息,包括内部的四大组件标签activity/service/receiver/contentprovider,每解析一个,都会存储在ParsingPackage当中。
ParsingPackage addActivity(ParsedActivity parsedActivity);
ParsingPackage addService(ParsedService parsedService);
那么这么做的好处就是,当我们每次启动一个Activity的时候,不需要每次都去解析Activity相关信息,例如launchMode,直接去ParsingPackage中获取即可。
至此,PKMS的main方法基本算是执行结束了,当然还有剩余的2个阶段,这里不做过多赘述,所以如果我们的手机安装了很多应用,那么开启速度上可能会受到影响,但是这个是用户可以接受的,这也为我们后续启动应用或者启动Activity提供了强力的数据支撑,不需要在启动的时候解析Activity的相关参数信息。
所以这里可能会有一些问题,需要伙伴们注意一下:
- 问题1:当Activity A跳转到Activity B的时候,Activity B的launchMode是什么时候解析的?
因为在手机启动的时候,会扫描所有apk的清单文件,对于每个Activity的launchMode也是写在清单文件当中,所以在第三阶段就拿到了launchMode信息,存储在了PKMS内存当中。
- 问题2:所有静态广播是什么时候注册的?
通过上一个问题,我们也能猜到,因为在PKMS扫描阶段会扫描所有apk的清单文件,对于静态广播也是放在清单文件当中的,所以也是在第三阶段进行注册的。
- 问题3:系统是如何安装apk的?
当系统安装apk时,会将apk拷贝到/data/app目录下,类似于adb push的操作,然后重启PKMS会扫描到新安装的应用,并存储信息到PKMS中。
通过这一节我们大概能够了解PKMS在启动时到底干了什么事,以及客户端在调用PKMS接口时内部进程间通信的具体逻辑,这也对我们后续的自升级方案有一定的理论基础。