概述

这里主要介绍 PackageManagerService(简称PMS)的启动一个应用的安装过程。这里只是大致总结,供参考,不少地方同样需要进一步深入了解学习的。

该篇相关代码也是基于AndroidQ的。

PMS也是核心服务之一,管理包相关内容,解析AndroidManifest.xml、管理应用等,最常见的是应用的安装和卸载。

和AMS类似,PMS提供服务也是通过binder完成,也有相应的服务端和客户端。

PMS的几个相关类:

IPackageManager.aidl:接口,定义了服务端和客户端之间通信的函数方法。

PackageManagerService:服务端。继承IPackageManager.Stub,继承Binder。

PackageManager:抽象类,对外的接口(可调用,SDK)。可以通过Context#getPackageManager获取,可以看下下面源码中关于PackageManager的注释部分。

ApplicationPackageManager:客户端。PackageManager实现类,通过内部的mPM变量参与Binder通信。

//PackageManager.java
/**
* Class for retrieving various kinds of information related to the application
* packages that are currently installed on the device.
*
* You can find this class through {@link Context#getPackageManager}.
*/
public abstract class PackageManager {
......
} //ApplicationPackageManager.java
protected ApplicationPackageManager(ContextImpl context,
IPackageManager pm) {
mContext = context;
mPM = pm;
}

PMS的启动

之前已经总结过 AMS的启动过程 以及 应用的第一次启动过程 ,比较详细,关于PMS的启动就容易理解了。

PMS启动过程 和 AMS启动过程 类似,都是在SystemServer启动之后完成的,若想详细了解可以先参考AMS的启动过程了解下,关于PMS的启动这里就大致说明下。

SystemServer中启动PMS

PMS的启动也是在SystemServer启动之后,也属于引导服务,在startBootstrapServices()中启动。

//SystemServer.java
private void startBootstrapServices() {
......
//Installer服务,真正安装的服务,与installd交互
Installer installer = mSystemServiceManager.startService(Installer.class);
......
//标记1
mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
......
//标记2
mPackageManager = mSystemContext.getPackageManager();
} private void startOtherServices() {
......
//完成dex优化
mPackageManagerService.updatePackagesIfNeeded();
......
//清理磁盘,释放空间
mPackageManagerService.performFstrimIfNeeded();
......
//PMS准备就绪
mPackageManagerService.systemReady();
......
}

可以看注释大致了解下。

这里主要看下标记1和标记2处,PackageManagerService.main()mSystemContext.getPackageManager()

标记1:mSystemContext.getPackageManager()

先看下mSystemContext.getPackageManager(),一路看下去,就看到如下代码。即Context#getPackageManager是创建了ApplicationPackageManager,mPM得到赋值 IPackageManager对象,可以通过mPm参与PMS通信。

//ContextImpl.java
@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;
}

标记2:PackageManagerService.main():

这是真正创建PMS服务,并启动。

//PackageManagerService.java
public static PackageManagerService main(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
// Self-check for initial settings.
PackageManagerServiceCompilerMapping.checkProperties();
//初始化PMS
PackageManagerService m = new PackageManagerService(context, installer,
factoryTest, onlyCore);
m.enableSystemUserPackages();
//注册服务 package
ServiceManager.addService("package", m);
final PackageManagerNative pmn = m.new PackageManagerNative();
//注册服务 package_native
ServiceManager.addService("package_native", pmn);
return m;
}

这里很简单,主要检查PMS环境,然创建并注册了两个服务package和package_native。

接下来主要看 创建PMS对象过程做了些什么。

PMS的创建

PMS的构造方法很长,下面折叠起来了,要了解可以打开

代码已折叠,点击显示PMS的完整构造方法
    public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
LockGuard.installLock(mPackages, LockGuard.INDEX_PACKAGES);
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "create package manager");
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,
SystemClock.uptimeMillis()); if (mSdkVersion <= 0) {
Slog.w(TAG, "**** ro.build.version.sdk not set!");
} mContext = context; mFactoryTest = factoryTest;
mOnlyCore = onlyCore;
mMetrics = new DisplayMetrics();
mInstaller = installer; // Create sub-components that provide services / data. Order here is important.
synchronized (mInstallLock) {
synchronized (mPackages) {
// Expose private service for system components to use.
LocalServices.addService(
PackageManagerInternal.class, new PackageManagerInternalImpl());
sUserManager = new UserManagerService(context, this,
new UserDataPreparer(mInstaller, mInstallLock, mContext, mOnlyCore), mPackages);
mComponentResolver = new ComponentResolver(sUserManager,
LocalServices.getService(PackageManagerInternal.class),
mPackages);
mPermissionManager = PermissionManagerService.create(context,
mPackages /*externalLock*/);
mDefaultPermissionPolicy = mPermissionManager.getDefaultPermissionGrantPolicy();
mSettings = new Settings(Environment.getDataDirectory(),
mPermissionManager.getPermissionSettings(), 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);
mSettings.addSharedUserLPw("android.uid.se", SE_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.networkstack", NETWORKSTACK_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); String separateProcesses = SystemProperties.get("debug.separate_processes");
if (separateProcesses != null && separateProcesses.length() > 0) {
if ("*".equals(separateProcesses)) {
mDefParseFlags = PackageParser.PARSE_IGNORE_PROCESSES;
mSeparateProcesses = null;
Slog.w(TAG, "Running with debug.separate_processes: * (ALL)");
} else {
mDefParseFlags = 0;
mSeparateProcesses = separateProcesses.split(",");
Slog.w(TAG, "Running with debug.separate_processes: "
+ separateProcesses);
}
} else {
mDefParseFlags = 0;
mSeparateProcesses = null;
} mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
"*dexopt*");
mDexManager = new DexManager(mContext, this, mPackageDexOptimizer, installer, mInstallLock);
mArtManagerService = new ArtManagerService(mContext, this, installer, mInstallLock);
mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper()); mViewCompiler = new ViewCompiler(mInstallLock, mInstaller); mOnPermissionChangeListeners = new OnPermissionChangeListeners(
FgThread.get().getLooper()); getDefaultDisplayMetrics(context, mMetrics); Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "get system config");
SystemConfig systemConfig = SystemConfig.getInstance();
mAvailableFeatures = systemConfig.getAvailableFeatures();
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); mProtectedPackages = new ProtectedPackages(mContext); mApexManager = new ApexManager(context);
synchronized (mInstallLock) {
// writer
synchronized (mPackages) {
mHandlerThread = new ServiceThread(TAG,
Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
mHandlerThread.start();
mHandler = new PackageHandler(mHandlerThread.getLooper());
mProcessLoggingHandler = new ProcessLoggingHandler();
Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
mInstantAppRegistry = new InstantAppRegistry(this); ArrayMap<String, SystemConfig.SharedLibraryEntry> libConfig
= systemConfig.getSharedLibraries();
final int builtInLibCount = libConfig.size();
for (int i = 0; i < builtInLibCount; i++) {
String name = libConfig.keyAt(i);
SystemConfig.SharedLibraryEntry entry = libConfig.valueAt(i);
addBuiltInSharedLibraryLocked(entry.filename, name);
} // Now that we have added all the libraries, iterate again to add dependency
// information IFF their dependencies are added.
long undefinedVersion = SharedLibraryInfo.VERSION_UNDEFINED;
for (int i = 0; i < builtInLibCount; i++) {
String name = libConfig.keyAt(i);
SystemConfig.SharedLibraryEntry entry = libConfig.valueAt(i);
final int dependencyCount = entry.dependencies.length;
for (int j = 0; j < dependencyCount; j++) {
final SharedLibraryInfo dependency =
getSharedLibraryInfoLPr(entry.dependencies[j], undefinedVersion);
if (dependency != null) {
getSharedLibraryInfoLPr(name, undefinedVersion).addDependency(dependency);
}
}
} SELinuxMMAC.readInstallPolicy(); Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "loadFallbacks");
FallbackCategoryProvider.loadFallbacks();
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "read user settings");
mFirstBoot = !mSettings.readLPw(sUserManager.getUsers(false));
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); // Clean up orphaned packages for which the code path doesn't exist
// and they are an update to a system app - caused by bug/32321269
final int packageSettingCount = mSettings.mPackages.size();
for (int i = packageSettingCount - 1; i >= 0; i--) {
PackageSetting ps = mSettings.mPackages.valueAt(i);
if (!isExternal(ps) && (ps.codePath == null || !ps.codePath.exists())
&& mSettings.getDisabledSystemPkgLPr(ps.name) != null) {
mSettings.mPackages.removeAt(i);
mSettings.enableSystemPackageLPw(ps.name);
}
} if (!mOnlyCore && mFirstBoot) {
requestCopyPreoptedFiles();
} String customResolverActivityName = Resources.getSystem().getString(
R.string.config_customResolverActivity);
if (!TextUtils.isEmpty(customResolverActivityName)) {
mCustomResolverComponentName = ComponentName.unflattenFromString(
customResolverActivityName);
} long startTime = SystemClock.uptimeMillis(); EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START,
startTime); final String bootClassPath = System.getenv("BOOTCLASSPATH");
final String systemServerClassPath = System.getenv("SYSTEMSERVERCLASSPATH"); if (bootClassPath == null) {
Slog.w(TAG, "No BOOTCLASSPATH found!");
} if (systemServerClassPath == null) {
Slog.w(TAG, "No SYSTEMSERVERCLASSPATH found!");
} File frameworkDir = new File(Environment.getRootDirectory(), "framework"); final VersionInfo ver = mSettings.getInternalVersion();
mIsUpgrade = !Build.FINGERPRINT.equals(ver.fingerprint);
if (mIsUpgrade) {
logCriticalInfo(Log.INFO,
"Upgrading from " + ver.fingerprint + " to " + Build.FINGERPRINT);
} // when upgrading from pre-M, promote system app permissions from install to runtime
mPromoteSystemApps =
mIsUpgrade && ver.sdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1; // When upgrading from pre-N, we need to handle package extraction like first boot,
// as there is no profiling data available.
mIsPreNUpgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N; mIsPreNMR1Upgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N_MR1;
mIsPreQUpgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.Q; int preUpgradeSdkVersion = ver.sdkVersion; // 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);
}
}
} mCacheDir = preparePackageParserCache(); // Set flag to monitor and not change apk file paths when
// scanning install directories.
int scanFlags = SCAN_BOOTING | SCAN_INITIAL; if (mIsUpgrade || mFirstBoot) {
scanFlags = scanFlags | SCAN_FIRST_BOOT_OR_UPGRADE;
} // Collect vendor/product/product_services overlay packages. (Do this before scanning
// any apps.)
// For security and version matching reason, only consider overlay packages if they
// reside in the right directory.
scanDirTracedLI(new File(VENDOR_OVERLAY_DIR),
mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags
| SCAN_AS_SYSTEM
| SCAN_AS_VENDOR,
0);
scanDirTracedLI(new File(PRODUCT_OVERLAY_DIR),
mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags
| SCAN_AS_SYSTEM
| SCAN_AS_PRODUCT,
0);
scanDirTracedLI(new File(PRODUCT_SERVICES_OVERLAY_DIR),
mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags
| SCAN_AS_SYSTEM
| SCAN_AS_PRODUCT_SERVICES,
0);
scanDirTracedLI(new File(ODM_OVERLAY_DIR),
mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags
| SCAN_AS_SYSTEM
| SCAN_AS_ODM,
0);
scanDirTracedLI(new File(OEM_OVERLAY_DIR),
mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags
| SCAN_AS_SYSTEM
| SCAN_AS_OEM,
0); mParallelPackageParserCallback.findStaticOverlayPackages(); // Find base frameworks (resource packages without code).
scanDirTracedLI(frameworkDir,
mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags
| SCAN_NO_DEX
| SCAN_AS_SYSTEM
| SCAN_AS_PRIVILEGED,
0);
if (!mPackages.containsKey("android")) {
throw new IllegalStateException(
"Failed to load frameworks package; check log for warnings");
} // Collect privileged system packages.
final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
scanDirTracedLI(privilegedAppDir,
mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags
| SCAN_AS_SYSTEM
| SCAN_AS_PRIVILEGED,
0); // Collect ordinary system packages.
final File systemAppDir = new File(Environment.getRootDirectory(), "app");
scanDirTracedLI(systemAppDir,
mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags
| SCAN_AS_SYSTEM,
0); // Collect privileged vendor packages.
File privilegedVendorAppDir = new File(Environment.getVendorDirectory(), "priv-app");
try {
privilegedVendorAppDir = privilegedVendorAppDir.getCanonicalFile();
} catch (IOException e) {
// failed to look up canonical path, continue with original one
}
scanDirTracedLI(privilegedVendorAppDir,
mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags
| SCAN_AS_SYSTEM
| SCAN_AS_VENDOR
| SCAN_AS_PRIVILEGED,
0); // Collect ordinary vendor packages.
File vendorAppDir = new File(Environment.getVendorDirectory(), "app");
try {
vendorAppDir = vendorAppDir.getCanonicalFile();
} catch (IOException e) {
// failed to look up canonical path, continue with original one
}
scanDirTracedLI(vendorAppDir,
mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags
| SCAN_AS_SYSTEM
| SCAN_AS_VENDOR,
0); // Collect privileged odm packages. /odm is another vendor partition
// other than /vendor.
File privilegedOdmAppDir = new File(Environment.getOdmDirectory(),
"priv-app");
try {
privilegedOdmAppDir = privilegedOdmAppDir.getCanonicalFile();
} catch (IOException e) {
// failed to look up canonical path, continue with original one
}
scanDirTracedLI(privilegedOdmAppDir,
mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags
| SCAN_AS_SYSTEM
| SCAN_AS_VENDOR
| SCAN_AS_PRIVILEGED,
0); // Collect ordinary odm packages. /odm is another vendor partition
// other than /vendor.
File odmAppDir = new File(Environment.getOdmDirectory(), "app");
try {
odmAppDir = odmAppDir.getCanonicalFile();
} catch (IOException e) {
// failed to look up canonical path, continue with original one
}
scanDirTracedLI(odmAppDir,
mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags
| SCAN_AS_SYSTEM
| SCAN_AS_VENDOR,
0); // Collect all OEM packages.
final File oemAppDir = new File(Environment.getOemDirectory(), "app");
scanDirTracedLI(oemAppDir,
mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags
| SCAN_AS_SYSTEM
| SCAN_AS_OEM,
0); // Collected privileged /product packages.
File privilegedProductAppDir = new File(Environment.getProductDirectory(), "priv-app");
try {
privilegedProductAppDir = privilegedProductAppDir.getCanonicalFile();
} catch (IOException e) {
// failed to look up canonical path, continue with original one
}
scanDirTracedLI(privilegedProductAppDir,
mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags
| SCAN_AS_SYSTEM
| SCAN_AS_PRODUCT
| SCAN_AS_PRIVILEGED,
0); // Collect ordinary /product packages.
File productAppDir = new File(Environment.getProductDirectory(), "app");
try {
productAppDir = productAppDir.getCanonicalFile();
} catch (IOException e) {
// failed to look up canonical path, continue with original one
}
scanDirTracedLI(productAppDir,
mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags
| SCAN_AS_SYSTEM
| SCAN_AS_PRODUCT,
0); // Collected privileged /product_services packages.
File privilegedProductServicesAppDir =
new File(Environment.getProductServicesDirectory(), "priv-app");
try {
privilegedProductServicesAppDir =
privilegedProductServicesAppDir.getCanonicalFile();
} catch (IOException e) {
// failed to look up canonical path, continue with original one
}
scanDirTracedLI(privilegedProductServicesAppDir,
mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags
| SCAN_AS_SYSTEM
| SCAN_AS_PRODUCT_SERVICES
| SCAN_AS_PRIVILEGED,
0); // Collect ordinary /product_services packages.
File productServicesAppDir = new File(Environment.getProductServicesDirectory(), "app");
try {
productServicesAppDir = productServicesAppDir.getCanonicalFile();
} catch (IOException e) {
// failed to look up canonical path, continue with original one
}
scanDirTracedLI(productServicesAppDir,
mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags
| SCAN_AS_SYSTEM
| SCAN_AS_PRODUCT_SERVICES,
0); // Prune any system packages that no longer exist.
final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<>();
// Stub packages must either be replaced with full versions in the /data
// partition or be disabled.
final List<String> stubSystemApps = new ArrayList<>();
if (!mOnlyCore) {
// do this first before mucking with mPackages for the "expecting better" case
final Iterator<PackageParser.Package> pkgIterator = mPackages.values().iterator();
while (pkgIterator.hasNext()) {
final PackageParser.Package pkg = pkgIterator.next();
if (pkg.isStub) {
stubSystemApps.add(pkg.packageName);
}
} final 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.
*/
final PackageParser.Package scannedPkg = mPackages.get(ps.name);
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.
*/
if (mSettings.isDisabledSystemPackageLPr(ps.name)) {
logCriticalInfo(Log.WARN,
"Expecting better updated system app for " + ps.name
+ "; removing system app. Last known"
+ " codePath=" + ps.codePathString
+ ", versionCode=" + ps.versionCode
+ "; scanned versionCode=" + scannedPkg.getLongVersionCode());
removePackageLI(scannedPkg, true);
mExpectingBetter.put(ps.name, ps.codePath);
} continue;
} if (!mSettings.isDisabledSystemPackageLPr(ps.name)) {
psit.remove();
logCriticalInfo(Log.WARN, "System package " + ps.name
+ " no longer exists; it's data will be wiped");
// Actual deletion of code and data will be handled by later
// reconciliation step
} else {
// we still have a disabled system package, but, it still might have
// been removed. check the code path still exists and check there's
// still a package. the latter can happen if an OTA keeps the same
// code path, but, changes the package name.
final PackageSetting disabledPs =
mSettings.getDisabledSystemPkgLPr(ps.name);
if (disabledPs.codePath == null || !disabledPs.codePath.exists()
|| disabledPs.pkg == null) {
possiblyDeletedUpdatedSystemApps.add(ps.name);
} else {
// We're expecting that the system app should remain disabled, but add
// it to expecting better to recover in case the data version cannot
// be scanned.
mExpectingBetter.put(disabledPs.name, disabledPs.codePath);
}
}
}
} //delete tmp files
deleteTempPackageFiles(); final int cachedSystemApps = PackageParser.sCachedPackageReadCount.get(); // Remove any shared userIDs that have no associated packages
mSettings.pruneSharedUsersLPw();
final long systemScanTime = SystemClock.uptimeMillis() - startTime;
final int systemPackagesCount = mPackages.size();
Slog.i(TAG, "Finished scanning system apps. Time: " + systemScanTime
+ " ms, packageCount: " + systemPackagesCount
+ " , timePerPackage: "
+ (systemPackagesCount == 0 ? 0 : systemScanTime / systemPackagesCount)
+ " , cached: " + cachedSystemApps);
if (mIsUpgrade && systemPackagesCount > 0) {
MetricsLogger.histogram(null, "ota_package_manager_system_app_avg_scan_time",
((int) systemScanTime) / systemPackagesCount);
}
if (!mOnlyCore) {
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
SystemClock.uptimeMillis());
scanDirTracedLI(sAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0); // Remove disable package settings for updated system apps that were
// removed via an OTA. If the update is no longer present, remove the
// app completely. Otherwise, revoke their system privileges.
for (int i = possiblyDeletedUpdatedSystemApps.size() - 1; i >= 0; --i) {
final String packageName = possiblyDeletedUpdatedSystemApps.get(i);
final PackageParser.Package pkg = mPackages.get(packageName);
final String msg; // remove from the disabled system list; do this first so any future
// scans of this package are performed without this state
mSettings.removeDisabledSystemPackageLPw(packageName); if (pkg == null) {
// should have found an update, but, we didn't; remove everything
msg = "Updated system package " + packageName
+ " no longer exists; removing its data";
// Actual deletion of code and data will be handled by later
// reconciliation step
} else {
// found an update; revoke system privileges
msg = "Updated system package " + packageName
+ " no longer exists; rescanning package on data"; // NOTE: We don't do anything special if a stub is removed from the
// system image. But, if we were [like removing the uncompressed
// version from the /data partition], this is where it'd be done. // remove the package from the system and re-scan it without any
// special privileges
removePackageLI(pkg, true);
try {
final File codePath = new File(pkg.applicationInfo.getCodePath());
scanPackageTracedLI(codePath, 0, scanFlags, 0, null);
} catch (PackageManagerException e) {
Slog.e(TAG, "Failed to parse updated, ex-system package: "
+ e.getMessage());
}
} // one final check. if we still have a package setting [ie. it was
// previously scanned and known to the system], but, we don't have
// a package [ie. there was an error scanning it from the /data
// partition], completely remove the package data.
final PackageSetting ps = mSettings.mPackages.get(packageName);
if (ps != null && mPackages.get(packageName) == null) {
removePackageDataLIF(ps, null, null, 0, false); }
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++) {
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 @ParseFlags int reparseFlags;
final @ScanFlags int rescanFlags;
if (FileUtils.contains(privilegedAppDir, scanFile)) {
reparseFlags =
mDefParseFlags |
PackageParser.PARSE_IS_SYSTEM_DIR;
rescanFlags =
scanFlags
| SCAN_AS_SYSTEM
| SCAN_AS_PRIVILEGED;
} else if (FileUtils.contains(systemAppDir, scanFile)) {
reparseFlags =
mDefParseFlags |
PackageParser.PARSE_IS_SYSTEM_DIR;
rescanFlags =
scanFlags
| SCAN_AS_SYSTEM;
} else if (FileUtils.contains(privilegedVendorAppDir, scanFile)
|| FileUtils.contains(privilegedOdmAppDir, scanFile)) {
reparseFlags =
mDefParseFlags |
PackageParser.PARSE_IS_SYSTEM_DIR;
rescanFlags =
scanFlags
| SCAN_AS_SYSTEM
| SCAN_AS_VENDOR
| SCAN_AS_PRIVILEGED;
} else if (FileUtils.contains(vendorAppDir, scanFile)
|| FileUtils.contains(odmAppDir, scanFile)) {
reparseFlags =
mDefParseFlags |
PackageParser.PARSE_IS_SYSTEM_DIR;
rescanFlags =
scanFlags
| SCAN_AS_SYSTEM
| SCAN_AS_VENDOR;
} else if (FileUtils.contains(oemAppDir, scanFile)) {
reparseFlags =
mDefParseFlags |
PackageParser.PARSE_IS_SYSTEM_DIR;
rescanFlags =
scanFlags
| SCAN_AS_SYSTEM
| SCAN_AS_OEM;
} else if (FileUtils.contains(privilegedProductAppDir, scanFile)) {
reparseFlags =
mDefParseFlags |
PackageParser.PARSE_IS_SYSTEM_DIR;
rescanFlags =
scanFlags
| SCAN_AS_SYSTEM
| SCAN_AS_PRODUCT
| SCAN_AS_PRIVILEGED;
} else if (FileUtils.contains(productAppDir, scanFile)) {
reparseFlags =
mDefParseFlags |
PackageParser.PARSE_IS_SYSTEM_DIR;
rescanFlags =
scanFlags
| SCAN_AS_SYSTEM
| SCAN_AS_PRODUCT;
} else if (FileUtils.contains(privilegedProductServicesAppDir, scanFile)) {
reparseFlags =
mDefParseFlags |
PackageParser.PARSE_IS_SYSTEM_DIR;
rescanFlags =
scanFlags
| SCAN_AS_SYSTEM
| SCAN_AS_PRODUCT_SERVICES
| SCAN_AS_PRIVILEGED;
} else if (FileUtils.contains(productServicesAppDir, scanFile)) {
reparseFlags =
mDefParseFlags |
PackageParser.PARSE_IS_SYSTEM_DIR;
rescanFlags =
scanFlags
| SCAN_AS_SYSTEM
| SCAN_AS_PRODUCT_SERVICES;
} else {
Slog.e(TAG, "Ignoring unexpected fallback path " + scanFile);
continue;
} mSettings.enableSystemPackageLPw(packageName); try {
scanPackageTracedLI(scanFile, reparseFlags, rescanFlags, 0, null);
} catch (PackageManagerException e) {
Slog.e(TAG, "Failed to parse original system package: "
+ e.getMessage());
}
}
} // Uncompress and install any stubbed system applications.
// This must be done last to ensure all stubs are replaced or disabled.
installSystemStubPackages(stubSystemApps, scanFlags); final int cachedNonSystemApps = PackageParser.sCachedPackageReadCount.get()
- cachedSystemApps; final long dataScanTime = SystemClock.uptimeMillis() - systemScanTime - startTime;
final int dataPackagesCount = mPackages.size() - systemPackagesCount;
Slog.i(TAG, "Finished scanning non-system apps. Time: " + dataScanTime
+ " ms, packageCount: " + dataPackagesCount
+ " , timePerPackage: "
+ (dataPackagesCount == 0 ? 0 : dataScanTime / dataPackagesCount)
+ " , cached: " + cachedNonSystemApps);
if (mIsUpgrade && dataPackagesCount > 0) {
MetricsLogger.histogram(null, "ota_package_manager_data_app_avg_scan_time",
((int) dataScanTime) / dataPackagesCount);
}
}
mExpectingBetter.clear(); // Resolve the storage manager.
mStorageManagerPackage = getStorageManagerPackageName(); // Resolve protected action filters. Only the setup wizard is allowed to
// have a high priority filter for these actions.
mSetupWizardPackage = getSetupWizardPackageName();
mComponentResolver.fixProtectedFilterPriorities(); mSystemTextClassifierPackage = getSystemTextClassifierPackageName(); mWellbeingPackage = getWellbeingPackageName();
mDocumenterPackage = getDocumenterPackageName();
mConfiguratorPackage = getDeviceConfiguratorPackageName();
mAppPredictionServicePackage = getAppPredictionServicePackageName();
mIncidentReportApproverPackage = getIncidentReportApproverPackageName(); // Now that we know all of the shared libraries, update all clients to have
// the correct library paths.
updateAllSharedLibrariesLocked(null, Collections.unmodifiableMap(mPackages)); 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.
final List<String> changedAbiCodePath =
adjustCpuAbisForSharedUserLPw(setting.packages, null /*scannedPackage*/);
if (changedAbiCodePath != null && changedAbiCodePath.size() > 0) {
for (int i = changedAbiCodePath.size() - 1; i >= 0; --i) {
final String codePathString = changedAbiCodePath.get(i);
try {
mInstaller.rmdex(codePathString,
getDexCodeInstructionSet(getPreferredInstructionSet()));
} catch (InstallerException ignored) {
}
}
}
// Adjust seInfo to ensure apps which share a sharedUserId are placed in the same
// SELinux domain.
setting.fixSeInfoLocked();
} // Now that we know all the packages we are keeping,
// read and update their last usage times.
mPackageUsage.read(mPackages);
mCompilerStats.read(); 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.
final boolean sdkUpdated = (ver.sdkVersion != mSdkVersion);
if (sdkUpdated) {
Slog.i(TAG, "Platform changed from " + ver.sdkVersion + " to "
+ mSdkVersion + "; regranting permissions for internal storage");
}
mPermissionManager.updateAllPermissions(
StorageManager.UUID_PRIVATE_INTERNAL, sdkUpdated, mPackages.values(),
mPermissionCallback);
ver.sdkVersion = mSdkVersion; // If this is the first boot or an update from pre-M, and it is a normal
// boot, then we need to initialize the default preferred apps across
// all defined users.
if (!onlyCore && (mPromoteSystemApps || mFirstBoot)) {
for (UserInfo user : sUserManager.getUsers(true)) {
mSettings.applyDefaultPreferredAppsLPw(user.id);
primeDomainVerificationsLPw(user.id);
}
} // Prepare storage for system user really early during boot,
// since core system apps like SettingsProvider and SystemUI
// can't wait for user to start
final int storageFlags;
if (StorageManager.isFileEncryptedNativeOrEmulated()) {
storageFlags = StorageManager.FLAG_STORAGE_DE;
} else {
storageFlags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
}
List<String> deferPackages = reconcileAppsDataLI(StorageManager.UUID_PRIVATE_INTERNAL,
UserHandle.USER_SYSTEM, storageFlags, true /* migrateAppData */,
true /* onlyCoreApps */);
mPrepareAppDataFuture = SystemServerInitThreadPool.get().submit(() -> {
TimingsTraceLog traceLog = new TimingsTraceLog("SystemServerTimingAsync",
Trace.TRACE_TAG_PACKAGE_MANAGER);
traceLog.traceBegin("AppDataFixup");
try {
mInstaller.fixupAppData(StorageManager.UUID_PRIVATE_INTERNAL,
StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
} catch (InstallerException e) {
Slog.w(TAG, "Trouble fixing GIDs", e);
}
traceLog.traceEnd(); traceLog.traceBegin("AppDataPrepare");
if (deferPackages == null || deferPackages.isEmpty()) {
return;
}
int count = 0;
for (String pkgName : deferPackages) {
PackageParser.Package pkg = null;
synchronized (mPackages) {
PackageSetting ps = mSettings.getPackageLPr(pkgName);
if (ps != null && ps.getInstalled(UserHandle.USER_SYSTEM)) {
pkg = ps.pkg;
}
}
if (pkg != null) {
synchronized (mInstallLock) {
prepareAppDataAndMigrateLIF(pkg, UserHandle.USER_SYSTEM, storageFlags,
true /* maybeMigrateAppData */);
}
count++;
}
}
traceLog.traceEnd();
Slog.i(TAG, "Deferred reconcileAppsData finished " + count + " packages");
}, "prepareAppData"); // If this is first boot after an OTA, and a normal boot, then
// we need to clear code cache directories.
// Note that we do *not* clear the application profiles. These remain valid
// across OTAs and are used to drive profile verification (post OTA) and
// profile compilation (without waiting to collect a fresh set of profiles).
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)) {
// No apps are running this early, so no need to freeze
clearAppDataLIF(ps.pkg, UserHandle.USER_ALL,
FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
| Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
}
}
ver.fingerprint = Build.FINGERPRINT;
} // Grandfather existing (installed before Q) non-system apps to hide
// their icons in launcher.
if (!onlyCore && mIsPreQUpgrade) {
Slog.i(TAG, "Whitelisting all existing apps to hide their icons");
int size = mSettings.mPackages.size();
for (int i = 0; i < size; i++) {
final PackageSetting ps = mSettings.mPackages.valueAt(i);
if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0) {
continue;
}
ps.disableComponentLPw(PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME,
UserHandle.USER_SYSTEM);
}
} // clear only after permissions and other defaults have been updated
mExistingSystemPackages.clear();
mPromoteSystemApps = false; // All the changes are done during package scanning.
ver.databaseVersion = Settings.CURRENT_DATABASE_VERSION; // can downgrade to reader
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "write settings");
mSettings.writeLPr();
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY,
SystemClock.uptimeMillis()); if (!mOnlyCore) {
mRequiredVerifierPackage = getRequiredButNotReallyRequiredVerifierLPr();
mRequiredInstallerPackage = getRequiredInstallerLPr();
mRequiredUninstallerPackage = getRequiredUninstallerLPr();
mIntentFilterVerifierComponent = getIntentFilterVerifierComponentNameLPr();
if (mIntentFilterVerifierComponent != null) {
mIntentFilterVerifier = new IntentVerifierProxy(mContext,
mIntentFilterVerifierComponent);
} else {
mIntentFilterVerifier = null;
}
mServicesSystemSharedLibraryPackageName = getRequiredSharedLibraryLPr(
PackageManager.SYSTEM_SHARED_LIBRARY_SERVICES,
SharedLibraryInfo.VERSION_UNDEFINED);
mSharedSystemSharedLibraryPackageName = getRequiredSharedLibraryLPr(
PackageManager.SYSTEM_SHARED_LIBRARY_SHARED,
SharedLibraryInfo.VERSION_UNDEFINED);
} else {
mRequiredVerifierPackage = null;
mRequiredInstallerPackage = null;
mRequiredUninstallerPackage = null;
mIntentFilterVerifierComponent = null;
mIntentFilterVerifier = null;
mServicesSystemSharedLibraryPackageName = null;
mSharedSystemSharedLibraryPackageName = null;
}
// PermissionController hosts default permission granting and role management, so it's a
// critical part of the core system.
mRequiredPermissionControllerPackage = getRequiredPermissionControllerLPr(); // Initialize InstantAppRegistry's Instant App list for all users.
final int[] userIds = UserManagerService.getInstance().getUserIds();
for (PackageParser.Package pkg : mPackages.values()) {
if (pkg.isSystem()) {
continue;
}
for (int userId : userIds) {
final PackageSetting ps = (PackageSetting) pkg.mExtras;
if (ps == null || !ps.getInstantApp(userId) || !ps.getInstalled(userId)) {
continue;
}
mInstantAppRegistry.addInstantAppLPw(userId, ps.appId);
}
} mInstallerService = new PackageInstallerService(context, this, mApexManager);
final Pair<ComponentName, String> instantAppResolverComponent =
getInstantAppResolverLPr();
if (instantAppResolverComponent != null) {
if (DEBUG_INSTANT) {
Slog.d(TAG, "Set ephemeral resolver: " + instantAppResolverComponent);
}
mInstantAppResolverConnection = new InstantAppResolverConnection(
mContext, instantAppResolverComponent.first,
instantAppResolverComponent.second);
mInstantAppResolverSettingsComponent =
getInstantAppResolverSettingsLPr(instantAppResolverComponent.first);
} else {
mInstantAppResolverConnection = null;
mInstantAppResolverSettingsComponent = null;
}
updateInstantAppInstallerLocked(null); // Read and update the usage of dex files.
// Do this at the end of PM init so that all the packages have their
// data directory reconciled.
// At this point we know the code paths of the packages, so we can validate
// the disk file and build the internal cache.
// The usage file is expected to be small so loading and verifying it
// should take a fairly small time compare to the other activities (e.g. package
// scanning).
final Map<Integer, List<PackageInfo>> userPackages = new HashMap<>();
for (int userId : userIds) {
userPackages.put(userId, getInstalledPackages(/*flags*/ 0, userId).getList());
}
mDexManager.load(userPackages);
if (mIsUpgrade) {
MetricsLogger.histogram(null, "ota_package_manager_init_time",
(int) (SystemClock.uptimeMillis() - startTime));
}
} // synchronized (mPackages)
} // synchronized (mInstallLock) mModuleInfoProvider = new ModuleInfoProvider(mContext, this); // Now after opening every single application zip, make sure they
// are all flushed. Not really needed, but keeps things nice and
// tidy.
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "GC");
Runtime.getRuntime().gc();
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); // The initial scanning above does many calls into installd while
// holding the mPackages lock, but we're mostly interested in yelling
// once we have a booted system.
mInstaller.setWarnIfHeld(mPackages); PackageParser.readConfigUseRoundIcon(mContext.getResources()); mServiceStartWithDelay = SystemClock.uptimeMillis() + (60 * 1000L); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}

从构造方法中的EventLogTags的标记,大致分为5个阶段,根据这个大致了解下。

  1. BOOT_PROGRESS_PMS_START

    创建和保存需要使用的相关对象,下面列出一些比较重要的:
  • mInstaller:Installer对象,系统服务,与installd交互,管理包的安装、卸载、数据、迁移等。
  • sUserManager:UserManagerService,用户管理服务。
  • mPermissionManager:PermissionManagerServiceInternal,权限管理相关。
  • mSettings:Settings,保存动态设置信息。这里通过addSharedUserLPw设置shared uid到Settings中(有8种:system、phone、log、nfc、bluetooth、shell、se、networkstack)。这里的Settings是frameworks/base/services/core/java/com/android/server/pm/Settings.java而不是frameworks/base/core/java/android/provider/Settings.java下的。
  • mPackageDexOptimizer:PackageDexOptimizer,助手工具类,进行dex优化等,dexopt。
  • mDexManager:DexManager,dex管理类,跟踪dex文件的使用等。
  • SystemConfig:用于获取系统的全局配置信息。
  • mApexManager:ApexManager,apex管理类,处理与apex服务通信。
  1. BOOT_PROGRESS_PMS_SYSTEM_SCAN_START

    扫描系统目录中的app等,通过scanDirTracedLI()方法。

    这个阶段主要扫描的路径是:/vendor/overlay;/product/overlay;/product_services/overlay;/odm/overlay;/oem/overlay;/system/framework;/system/priv-app;/system/app;/vendor/priv-app;/vendor/app;/odm/priv-app;/odm/app;/oem/app;/product/priv-app;/product/app;/product_services/priv-app;/product_services/app;

    概括起来,就是vendor/product/product_services/odm/oem下的overlay目录;/system/framework/;system/vendor/odm/oem/product/product_services下的priv-app和app目录(没有/oem/priv-app)。

  2. BOOT_PROGRESS_PMS_DATA_SCAN_START

    扫描非系统目录app等。

    这个阶段主要扫描目录:/data/app;

  3. BOOT_PROGRESS_PMS_SCAN_END

    扫描完成,将结果写入文件中。

    通过mSettings.writeLPr();方法。写入到文件:/data/system/packages.xml和/data/system/packages.list。

    安装、卸载等都会更新这个目录。

  4. BOOT_PROGRESS_PMS_READY

    PMS准备完成,创建PackageInstallerService服务。mInstallerService = new PackageInstallerService(context, this, mApexManager);

题外话:

apex是 AndroidQ开始提出的mainline计划后 而有的一种新格式,google将framework也分成一个个小模块,最终希望通过play store对system的核心内容的进行更新,不需要odm、oem干预。而这一个个模块与apk应用不同,因此有了apex这种格式。其实google在Android O开始就再弄的Treble计划,一步步将system与vendor分开了独立,最终整个系统可以直接使用一套公共的system.img即GSI(Generic System Image)。在Android R开始,kernel部分也开始使用这套,弄一套通用内核镜像GKI(Generic Kernel Image)。 Android各个版本之间变动是非常大的,可能用户感受不到。为了实现这些,引入了很多新的概念和技术。

/data/system/packages.xml,在 Android调试非常有用的命令集 总结中也提到过,这里就是该文件内容是什么,怎么来的。

所以预置应用越多,扫描时间会越长,影响开机时间。

一个应用的安装

应用的安装,最常见的是点击apk后的有界面的安装 和 开发中通过adb命令的静默安装。

下面关于这两种安装方式,简单说明下。

点击安装(有界面)

1.点击安装,PackageInstaller.apk处理过程

这个过程通过系统预置的PackageInstaller.apk或类似应用完成。

点击apk,进行一些验证后跳转到了PackageInstallerActivity,即弹出的”是否安装此应用“的确认安装界面。点击确认安装后,进入了InstallInstalling。

点击apk进行安装,PackageInstaller.apk是从InstallStart->PackageInstallerActivity->InstallInstalling。

注:这里以aosp中的PackageInstaller.apk说明(com.android.packageinstaller),在外单中 一般预置的是GMS包中的GooglePackageInstaller。

下面是大致的代码过程的关键代码:

//InstallStart.java
protected void onCreate(@Nullable Bundle savedInstanceState) {
Intent nextActivity = new Intent(intent);
.....
if (isSessionInstall) {
nextActivity.setClass(this, PackageInstallerActivity.class);
}
......
if (nextActivity != null) {
startActivity(nextActivity);
}
} //PackageInstallerActivity.java
protected void onCreate(Bundle icicle) {
.......
bindUi();
} private void bindUi() {
......
mAlert.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.install),
(ignored, ignored2) -> {
if (mOk.isEnabled()) {
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, true);
finish();
} else {
startInstall();
}
}
}, null);
......
} private void startInstall() {
Intent newIntent = new Intent();
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
mPkgInfo.applicationInfo);
newIntent.setData(mPackageURI);
newIntent.setClass(this, InstallInstalling.class);
......
startActivity(newIntent);
finish();
}

可以清晰看到,点击确认安装后,通过startInstall()方法进入了InstallInstalling。

接着看InstallInstalling的处理:

//InstallInstalling.java
protected void onCreate(@Nullable Bundle savedInstanceState) {
......
mPackageURI = getIntent().getData(); if ("package".equals(mPackageURI.getScheme())) {
.......
} else {
......
if (savedInstanceState != null) {
......
} else {
......
try {
mInstallId = InstallEventReceiver
.addObserver(this, EventResultPersister.GENERATE_NEW_ID,
this::launchFinishBasedOnResult);
} catch (EventResultPersister.OutOfIdsException e) {
launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
} try {
mSessionId = getPackageManager().getPackageInstaller().createSession(params);
} catch (IOException e) {
launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
}
} mCancelButton = mAlert.getButton(DialogInterface.BUTTON_NEGATIVE); mSessionCallback = new InstallSessionCallback();
}
} @Override
protected void onResume() {
super.onResume(); // This is the first onResume in a single life of the activity
if (mInstallingTask == null) {
PackageInstaller installer = getPackageManager().getPackageInstaller();
PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId); if (sessionInfo != null && !sessionInfo.isActive()) {
mInstallingTask = new InstallingAsyncTask();
mInstallingTask.execute();
} else {
// we will receive a broadcast when the install is finished
mCancelButton.setEnabled(false);
setFinishOnTouchOutside(false);
}
}
}

onCreate()中创建了一个安装会话并返回获得一个sessionId。onResume()中进行进一步安装。

2.创建安装的会话

先看onCreate()中创建安装会话,返回的sessionId很重要。

InstallEventReceiver是一个BroadcastReceiver,接收安装事件并回调给EventResultPersister。mSessionId是安装的会话id。

添加log可以看到,mPackageURI.getScheme()的值是"file"。最后关键执行到的是getPackageManager().getPackageInstaller().createSession(params)这句。

getPackageManager()获取PM对象,然后调用实现类ApplicationPackageManager中getPackageInstaller()得到的是PackageInstaller对象。最终调用的是PackageInstaller中的createSession()创建安装会话并返回sessionId。这里不列出代码,直接看createSession()方法。

//PackageInstaller.java
//frameworks/base/core/java/android/content/pm/PackageInstaller.java
private final IPackageInstaller mInstaller;
public int createSession(@NonNull SessionParams params) throws IOException {
try {
final String installerPackage;
......
return mInstaller.createSession(params, installerPackage, mUserId);
}
......
}

注意,这里调用的是mInstaller.createSession()。mInstaller是IPackageInstaller,一看就知道这也是通过binder调用的一个服务,这个服务是PackageInstallerService,即最终调用的是PackageInstallerService的createSession()方法创建了一个安装会话 并 返回了sessionId。

这里不细述创建安装会话的过程了。

3.安装会话创建后,进一步安装

接着看onResume()。这里主要是看mInstallingTask.execute()

/**
* Send the package to the package installer and then register a event result observer that
* will call {@link #launchFinishBasedOnResult(int, int, String)}
*/
private final class InstallingAsyncTask extends AsyncTask<Void, Void,
PackageInstaller.Session> {
volatile boolean isDone; @Override
protected PackageInstaller.Session doInBackground(Void... params) {
PackageInstaller.Session session;
try {
session = getPackageManager().getPackageInstaller().openSession(mSessionId);
} catch (IOException e) {
return null;
} session.setStagingProgress(0); try {
File file = new File(mPackageURI.getPath()); try (InputStream in = new FileInputStream(file)) {
long sizeBytes = file.length();
try (OutputStream out = session
.openWrite("PackageInstaller", 0, sizeBytes)) {
byte[] buffer = new byte[1024 * 1024];
while (true) {
int numRead = in.read(buffer); if (numRead == -1) {
session.fsync(out);
break;
} if (isCancelled()) {
session.close();
break;
} out.write(buffer, 0, numRead);
if (sizeBytes > 0) {
float fraction = ((float) numRead / (float) sizeBytes);
session.addProgress(fraction);
}
}
}
} return session;
} catch (IOException | SecurityException e) {
Log.e(LOG_TAG, "Could not write package", e); session.close(); return null;
} finally {
synchronized (this) {
isDone = true;
notifyAll();
}
}
} @Override
protected void onPostExecute(PackageInstaller.Session session) {
if (session != null) {
Intent broadcastIntent = new Intent(BROADCAST_ACTION);
broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
broadcastIntent.setPackage(getPackageName());
broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId); PendingIntent pendingIntent = PendingIntent.getBroadcast(
InstallInstalling.this,
mInstallId,
broadcastIntent,
PendingIntent.FLAG_UPDATE_CURRENT); session.commit(pendingIntent.getIntentSender());
mCancelButton.setEnabled(false);
setFinishOnTouchOutside(false);
} else {
getPackageManager().getPackageInstaller().abandonSession(mSessionId); if (!isCancelled()) {
launchFailure(PackageManager.INSTALL_FAILED_INVALID_APK, null);
}
}
}
}

InstallingAsyncTask是个异步任务。

doInBackground中,apk的信息(通过URI获取)通过IO流写入到PackageInstaller.Session中。在onPostExecute中,通过session.commit()发送安装。

Session就是通过sessionId获取打开的。

接下来主要看发送安装。

PackageInstaller.Session.commit()发送安装

//frameworks/base/core/java/android/content/pm/PackageInstaller.java
public static class Session implements Closeable {
protected final IPackageInstallerSession mSession; public void commit(@NonNull IntentSender statusReceiver) {
try {
mSession.commit(statusReceiver, false);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
} //PackageInstallerSession.java
public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@Override
public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
......
if (!markAsCommitted(statusReceiver, forTransfer)) {
return;
}
if (isMultiPackage()) {
final SparseIntArray remainingSessions = mChildSessionIds.clone();
final IntentSender childIntentSender =
new ChildStatusIntentReceiver(remainingSessions, statusReceiver)
.getIntentSender();
RuntimeException commitException = null;
boolean commitFailed = false;
for (int i = mChildSessionIds.size() - 1; i >= 0; --i) {
final int childSessionId = mChildSessionIds.keyAt(i);
try {
// commit all children, regardless if any of them fail; we'll throw/return
// as appropriate once all children have been processed
if (!mSessionProvider.getSession(childSessionId)
.markAsCommitted(childIntentSender, forTransfer)) {
commitFailed = true;
}
} catch (RuntimeException e) {
commitException = e;
}
}
if (commitException != null) {
throw commitException;
}
if (commitFailed) {
return;
}
}
mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
}
}

最终调用到PackageInstallerSession中的commit()。主要看mHandler.obtainMessage(MSG_COMMIT).sendToTarget(); ,即通过Handler发送了MSG_COMMIT的消息,进程间通信。

接着看消息处理代码:

//PackageInstallerSession.java
private final Handler.Callback mHandlerCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_COMMIT:
handleCommit();
break;
......
} return true;
}
}; private void handleCommit() {
......
// For a multiPackage session, read the child sessions
// outside of the lock, because reading the child
// sessions with the lock held could lead to deadlock
// (b/123391593).
List<PackageInstallerSession> childSessions = getChildSessions(); try {
synchronized (mLock) {
commitNonStagedLocked(childSessions);
}
} catch (PackageManagerException e) {
......
}
} @GuardedBy("mLock")
private void commitNonStagedLocked(List<PackageInstallerSession> childSessions)
throws PackageManagerException {
final PackageManagerService.ActiveInstallSession committingSession =
makeSessionActiveLocked();
if (committingSession == null) {
return;
}
if (isMultiPackage()) {
List<PackageManagerService.ActiveInstallSession> activeChildSessions =
new ArrayList<>(childSessions.size());
boolean success = true;
PackageManagerException failure = null;
for (int i = 0; i < childSessions.size(); ++i) {
final PackageInstallerSession session = childSessions.get(i);
try {
final PackageManagerService.ActiveInstallSession activeSession =
session.makeSessionActiveLocked();
if (activeSession != null) {
activeChildSessions.add(activeSession);
}
} catch (PackageManagerException e) {
failure = e;
success = false;
}
}
if (!success) {
try {
mRemoteObserver.onPackageInstalled(
null, failure.error, failure.getLocalizedMessage(), null);
} catch (RemoteException ignored) {
}
return;
}
mPm.installStage(activeChildSessions);
} else {
mPm.installStage(committingSession);
}
}

很清晰,最终通过mPm.installStage() 进入PMS进行处理。

4.安装进入PMS

直接看PMS的installStage()。

//PackageManagerService.java
void installStage(ActiveInstallSession activeInstallSession) {
if (DEBUG_INSTANT) {
if ((activeInstallSession.getSessionParams().installFlags
& PackageManager.INSTALL_INSTANT_APP) != 0) {
Slog.d(TAG, "Ephemeral install of " + activeInstallSession.getPackageName());
}
}
final Message msg = mHandler.obtainMessage(INIT_COPY);
final InstallParams params = new InstallParams(activeInstallSession);
params.setTraceMethod("installStage").setTraceCookie(System.identityHashCode(params));
msg.obj = params; Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "installStage",
System.identityHashCode(msg.obj));
Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
System.identityHashCode(msg.obj)); mHandler.sendMessage(msg);
} class PackageHandler extends Handler {
void doHandleMessage(Message msg) {
switch (msg.what) {
case INIT_COPY: {
HandlerParams params = (HandlerParams) msg.obj;
if (params != null) {
if (DEBUG_INSTALL) Slog.i(TAG, "init_copy: " + params);
Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
System.identityHashCode(params));
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "startCopy");
params.startCopy();
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
break;
}
......
}
}
}

通过Handler发送了INIT_COPY消息,开始拷贝apk到执行地方进行进一步安装。

5.拷贝APK

看startCopy():

//PackageManagerService.java
private abstract class HandlerParams {
final void startCopy() {
if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);
handleStartCopy();
handleReturnCode();
}
abstract void handleStartCopy();
abstract void handleReturnCode();
} class InstallParams extends HandlerParams {
@Override
public void handleStartCopy() {
int ret = PackageManager.INSTALL_SUCCEEDED; // If we're already staged, we've firmly committed to an install location
...... /*
* If we have too little free space, try to free cache
* before giving up.
*/
...... if (ret == PackageManager.INSTALL_SUCCEEDED) {
int loc = pkgLite.recommendedInstallLocation;
if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {
ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
} else if (loc == PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS) {
ret = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
} else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
} else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {
ret = PackageManager.INSTALL_FAILED_INVALID_APK;
} else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
ret = PackageManager.INSTALL_FAILED_INVALID_URI;
} else if (loc == PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE) {
ret = PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
} else {
// Override with defaults if needed.
......
}
}
final InstallArgs args = createInstallArgs(this);
mVerificationCompleted = true;
mEnableRollbackCompleted = true;
Args = args;
......
mRet = ret;
} @Override
void handleReturnCode() {
if (mVerificationCompleted && mEnableRollbackCompleted) {
......
if (mRet == PackageManager.INSTALL_SUCCEEDED) {
mRet = mArgs.copyApk();
}
processPendingInstall(mArgs, mRet);
}
}
}

startCopy()只有两个方法,handleStartCopy()和handleReturnCode()。

handleStartCopy()中进行安装前的各种验证,检查文件、空间、发送给所有sufficientVerifiers进行验证。 验证成功,即mRet == PackageManager.INSTALL_SUCCEEDED,在handleReturnCode()中进行apk拷贝工作。

拷贝apk目标路径

接着看下handleReturnCode()中的拷贝apk操作:mArgs.copyApk()

mArgs这里是FileInstallArgs。

//PackageManagerService.java
private InstallArgs createInstallArgs(InstallParams params) {
if (params.move != null) {
return new MoveInstallArgs(params);
} else {
return new FileInstallArgs(params);
}
} //PackageManagerService.java
class FileInstallArgs extends InstallArgs {
int copyApk() {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyApk");
try {
return doCopyApk();
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
} private int doCopyApk() {
......
final boolean isEphemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
final File tempDir =
mInstallerService.allocateStageDirLegacy(volumeUuid, isEphemeral);
codeFile = tempDir;
resourceFile = tempDir;
......
int ret = PackageManagerServiceUtils.copyPackage(
origin.file.getAbsolutePath(), codeFile);
......
return ret;
}
} //PackageManagerServiceUtils.java
public static int copyPackage(String packagePath, File targetDir) {
if (packagePath == null) {
return PackageManager.INSTALL_FAILED_INVALID_URI;
} try {
final File packageFile = new File(packagePath);
final PackageParser.PackageLite pkg = PackageParser.parsePackageLite(packageFile, 0);
copyFile(pkg.baseCodePath, targetDir, "base.apk");
if (!ArrayUtils.isEmpty(pkg.splitNames)) {
for (int i = 0; i < pkg.splitNames.length; i++) {
copyFile(pkg.splitCodePaths[i], targetDir,
"split_" + pkg.splitNames[i] + ".apk");
}
}
return PackageManager.INSTALL_SUCCEEDED;
} catch (PackageParserException | IOException | ErrnoException e) {
Slog.w(TAG, "Failed to copy package at " + packagePath + ": " + e);
return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
}
}

拷贝到的目标文件是:/data/app/vmdl[sessionId].tmp/base.apk。若是多个,/data/app/vmdl[sessionId].tmp/split_[pkgname].apk。

例如:如我的一次安装 /data/app/vmdl1101585668.tmp/base.apk,这次安装的sessionId是1101585668。

注:用userdebug软件,安装抓取的log也能看出大致安装过程的信息,可以辅助。如搜索TAG为 PackageManager(PMS类的TAG)。

下面是路径相关代码:

//PackageInstallerService.java
public File allocateStageDirLegacy(String volumeUuid, boolean isEphemeral) throws IOException {
synchronized (mSessions) {
......
final File sessionStageDir = buildTmpSessionDir(sessionId, volumeUuid);
......
}
} private File buildTmpSessionDir(int sessionId, String volumeUuid) {
final File sessionStagingDir = getTmpSessionDir(volumeUuid);
return new File(sessionStagingDir, "vmdl" + sessionId + ".tmp");
} private File getTmpSessionDir(String volumeUuid) {
return Environment.getDataAppDirectory(volumeUuid);
}

6.真正安装

copyFile完成拷贝后,回头看handleReturnCode(),mArgs.copyApk();后执行processPendingInstall(mArgs, mRet);开始真正的安装。

//PackageManagerService.java
private void processPendingInstall(final InstallArgs args, final int currentStatus) {
if (args.mMultiPackageInstallParams != null) {
args.mMultiPackageInstallParams.tryProcessInstallRequest(args, currentStatus);
} else {
//安装参数
PackageInstalledInfo res = createPackageInstalledInfo(currentStatus);
processInstallRequestsAsync(
res.returnCode == PackageManager.INSTALL_SUCCEEDED,
Collections.singletonList(new InstallRequest(args, res)));
}
} // Queue up an async operation since the package installation may take a little while.
private void processInstallRequestsAsync(boolean success,
List<InstallRequest> installRequests) {
mHandler.post(() -> {
if (success) {
for (InstallRequest request : installRequests) {
//如果之前安装失败,清理无用信息
request.args.doPreInstall(request.installResult.returnCode);
}
synchronized (mInstallLock) {
//进行安装
installPackagesTracedLI(installRequests);
}
for (InstallRequest request : installRequests) {
//安装失败,清理无用信息
request.args.doPostInstall(
request.installResult.returnCode, request.installResult.uid);
}
}
for (InstallRequest request : installRequests) {
restoreAndPostInstall(request.args.user.getIdentifier(), request.installResult,
new PostInstallData(request.args, request.installResult, null));
}
});
} private PackageInstalledInfo createPackageInstalledInfo(
int currentStatus) {
PackageInstalledInfo res = new PackageInstalledInfo();
res.setReturnCode(currentStatus);
res.uid = -1;
res.pkg = null;
res.removedInfo = null;
return res;
}

看上述注释大致了解下,我们接着看installPackagesTracedLI()。

//PMS
@GuardedBy({"mInstallLock", "mPackages"})
private void installPackagesTracedLI(List<InstallRequest> requests) {
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages");
installPackagesLI(requests);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
} private void installPackagesLI(List<InstallRequest> requests) {
......
try {
for (InstallRequest request : requests) {
// TODO(b/109941548): remove this once we've pulled everything from it and into
// scan, reconcile or commit.
final PrepareResult prepareResult;
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "preparePackage");
//分析安装状态、分析包并检查验证
//检查包并分析完整性;检查SDK版本、静态库等;检查签名;设置权限;
prepareResult = preparePackageLI(request.args, request.installResult);
}
......
try {
//扫描apk
final List<ScanResult> scanResults = scanPackageTracedLI(
prepareResult.packageToScan, prepareResult.parseFlags,
prepareResult.scanFlags, System.currentTimeMillis(),
request.args.user);
}
......
}
executePostCommitSteps(commitRequest);
} finally {
......
}
}

installPackagesLI()方法很复杂,这里也不细述了。

installPackagesLI()对安装包本身以及安装环境等进行一些相关的检查验证。检查包的完整性、SDK版本、签名、共享库、设置权限等等,检查安装环境是否满足等。可以大致看下方法中这几个方法:

prepareResult = preparePackageLI(request.args, request.installResult);
final List<ScanResult> scanResults = scanPackageTracedLI(
prepareResult.packageToScan, prepareResult.parseFlags,
prepareResult.scanFlags, System.currentTimeMillis(),
request.args.user);
ReconcileRequest reconcileRequest = new ReconcileRequest(preparedScans, installArgs,

最后执行executePostCommitSteps()进一步进行安装,最终通过binder进入installd进程完成安装。这个过程也很复杂,本人也需进一步阅读。executePostCommitSteps()后续简单参考过程:

PackageManagerService.java
installPackagesLI()->executePostCommitSteps()->prepareAppDataAfterInstallLIF()->prepareAppDataLIF()->prepareAppDataLeafLIF()->mInstaller.createAppData()。
Installer.java:
createAppData()->mInstalld.createAppData()。

关于过程中一些路径操作多说一点:

在preparePackageLI()中有个rename的过程,将前面拷贝的文件目录重命名了下,所以安装后能在手机中看到的是重命名后的目录。

重命名规则:/data/app/[packagename]-[suffix]。

例如我的安装:PackageManager: Renaming /data/app/vmdl1101585668.tmp to /data/app/[packagename]-RDISkz-0p77Yh-pAwNtfFA==

相关代码:

private PrepareResult preparePackageLI(InstallArgs args, PackageInstalledInfo res)
......
if (!args.doRename(res.returnCode, pkg)) {
throw new PrepareFailure(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename");
}
......
} class FileInstallArgs extends InstallArgs {
boolean doRename(int status, PackageParser.Package pkg) {
.....
final File targetDir = codeFile.getParentFile();
final File beforeCodeFile = codeFile;
final File afterCodeFile = getNextCodePath(targetDir, pkg.packageName);
if (DEBUG_INSTALL) Slog.d(TAG, "Renaming " + beforeCodeFile + " to " + afterCodeFile);
.....
Os.rename(beforeCodeFile.getAbsolutePath(), afterCodeFile.getAbsolutePath());
.....
}
} private File getNextCodePath(File targetDir, String packageName) {
File result;
SecureRandom random = new SecureRandom();
byte[] bytes = new byte[16];
do {
random.nextBytes(bytes);
String suffix = Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP);
result = new File(targetDir, packageName + "-" + suffix);
} while (result.exists());
return result;
}

一点总结

点击安装,通过类似PackageInstaller.apk处理。

确认安装后,拷贝apk到/data/app/vmdl[sessionId].tmp/base.apk下。

进一步安装中,重命名路径为 /data/app/[packagename]-[suffix]。

如我的安装:拷贝到/data/app/vmdl1101585668.tmp/base.apk。重命名后是:/data/app/[packagename]-RDISkz-0p77Yh-pAwNtfFA==。

查看/data/app/[packagename]-RDISkz-0p77Yh-pAwNtfFA==目录,有base.apk lib/ oat/,安装后的apk、库、odex、vdex都在这个目录下。

系统预置的还在预置目录下,如/system/priv-app。

/data/data/[packagename]存放应用数据,一般有cache code_cache lib。

adb安装(无界面 静默安装)

这也是一个简单参考,不完整。

使用adb命令进行安装,下面大致可以参考:

//system/core/adb/client/commandline.cpp
int adb_commandline(int argc, const char** argv) {
......
else if (!strcmp(argv[0], "install")) {
if (argc < 2) error_exit("install requires an argument");
return install_app(argc, argv);
}
......
} //system/core/adb/client/adb_install.cpp
int install_app(int argc, const char** argv) {
......
switch (installMode) {
case INSTALL_PUSH:
return install_app_legacy(passthrough_argv.size(), passthrough_argv.data(),
use_fastdeploy, use_localagent);
case INSTALL_STREAM:
return install_app_streamed(passthrough_argv.size(), passthrough_argv.data(),
use_fastdeploy, use_localagent);
case INSTALL_DEFAULT:
default:
return 1;
}
} static int install_app_legacy(int argc, const char** argv, bool use_fastdeploy,
bool use_localagent) {
......
result = pm_command(argc, argv);
......
} static int pm_command(int argc, const char** argv) {
std::string cmd = "pm"; while (argc-- > 0) {
cmd += " " + escape_arg(*argv++);
} return send_shell_command(cmd);
}

在AndroidO时,还有源码frameworks/base/cmds/pm/src/com/android/commands/pm/Pm.java。AndroidO最终Pm.java执行mInstaller.createSession()进入PMS。

AndroidQ中,frameworks/base/cmds/pm/src/com/android/commands/pm只有一个Andoird.mk了,pm是system/bin下的一个可执行文件了system/bin/pm

不过应该是和O类似的(AndoridP中也没有Pm.java的源码了),最终也是进入的PMS中方法,回归到类似点击安装的过程。

我们可以在上面点击安装过程中的PMS方法中添加调用栈或log等,了解adb安装的过程是 进入的哪,进一步跟踪了解。

最新文章

  1. IOS开发基础知识--碎片20
  2. EF(Linq)框架使用过程中的小技巧汇总
  3. 在Android中实现service动态更新UI界面
  4. JQ基础练习---图片划过变暗
  5. java 删除字符串中的反斜杠\
  6. 【重点突破】——使用Express创建一个web服务器
  7. Oracle 18c 数据库中scott用户不存在的解决方法
  8. windows部署Apollo
  9. PTA寒假三
  10. [CF920G]List Of Integers
  11. 使用Vivado的block design
  12. PAT甲题题解-1055. The World&#39;s Richest (25)-终于遇见一个排序的不水题
  13. 在pycharm中启动Django服务器
  14. hue安装及基本测试-笔记
  15. 20155212 2016-2017-2 《Java程序设计》第7周学习总结
  16. python全栈开发_day16_包
  17. 如何使用iOS 开发证书 和 Profile 文件
  18. 如何调试nRF5 SDK
  19. IOS UI-自定义UIColectionView布局
  20. $bzoj1027-JSOI2007$ 合金 计算几何 最小环

热门文章

  1. 内网渗透 day3 -metasploit的使用
  2. Spring Security 实战干货:OAuth2授权回调的处理机制
  3. Java基础 之一 基本知识
  4. Html+css 一个简单的网页模板
  5. h5 图片上传旋转问题
  6. js 进度条效果
  7. Mac专用下载器Folx软件中有没有“下载速度控制”功能
  8. starUML软件破解
  9. P1163 银行贷款
  10. Codeforces Round #668 (Div. 2) D. Tree Tag 题解(博弈)