android 服务设置在哪个文件夹,Android重学系列 PackageManagerService的启动与安装(上)...

前言

PackageManagerService 是Android系统中对所有apk包的管理服务中心,之后我将成其为PMS。PMS除了管理所有已经安装好的apk包的数据,还包含了安装apk的服务,让我们一探究竟。

正文

PMS的启动

PMS的启动,从SystemServer开始,更加详细的原理可以去SystemServer到Home的启动下阅读:

private void startBootstrapServices() {

...

mPackageManagerService = PackageManagerService.main(mSystemContext, installer,

mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);

mFirstBoot = mPackageManagerService.isFirstBoot();

...

}

private void startOtherServices() {

...

if (!mOnlyCore) {

try {

mPackageManagerService.updatePackagesIfNeeded();

} catch (Throwable e) {

reportWtf("update packages", e);

}

traceEnd();

}

...

try {

mPackageManagerService.performFstrimIfNeeded();

} catch (Throwable e) {

reportWtf("performing fstrim", e);

}

...

mPackageManagerService.systemReady();

...

mActivityManagerService.systemReady(() -> {

...

mPackageManagerService.waitForAppDataPrepared();

...

}

}

在SystemServer的启动依照如下顺序:

1.PackageManagerService.main 将安装服务Intstaller传入,并实例化PMS

2.mPackageManagerService.updatePackagesIfNeeded

3.mPackageManagerService.performFstrimIfNeeded

4.mPackageManagerService. systemReady

5.mPackageManagerService. waitForAppDataPrepared

PackageManagerService.main

public static PackageManagerService main(Context context, Installer installer,

boolean factoryTest, boolean onlyCore) {

PackageManagerService m = new PackageManagerService(context, installer,

factoryTest, onlyCore);

m.enableSystemUserPackages();

ServiceManager.addService("package", m);

final PackageManagerNative pmn = m.new PackageManagerNative();

ServiceManager.addService("package_native", pmn);

return m;

}

在PMS的构造函数中,完成了两个对象的实例化,并加入到ServiceManager中。

PackageManagerService

PackageManagerNative PackageManagerNative 是PMS的Binder接口对象,我们可以不用看,主要看看PMS本身的实例化都做了什么。

PackageManagerService的实例化

整个构造函数方法很长,我们拆分为几段和大家聊聊:

public PackageManagerService(Context context, Installer installer,

boolean factoryTest, boolean onlyCore) {

LockGuard.installLock(mPackages, LockGuard.INDEX_PACKAGES);

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);

mPermissionManager = PermissionManagerService.create(context,

new DefaultPermissionGrantedCallback() {

@Override

public void onDefaultRuntimePermissionsGranted(int userId) {

synchronized(mPackages) {

mSettings.onDefaultRuntimePermissionsGrantedLPr(userId);

}

}

}, mPackages /*externalLock*/);

mDefaultPermissionPolicy = mPermissionManager.getDefaultPermissionGrantPolicy();

mSettings = new Settings(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);

....

}

1.实例化DisplayMetrics对象,这个对象出现过很多次,里面包含了Display的屏幕信息

2.实例化一个PackageManagerInternalImpl对象,这个对象将会作为本地的服务对外提供一些PMS的功能

3.实例化UserManagerService 用户管理服务。在Android系统中是一个多用户系统,每一个应用就代表一个用户。而这个服务其实就是管理每一个应用用户相关的权限和信息。

4.实例化PermissionManagerService 动态权限服务,所有的动态权限最终都会到这个服务下进行权限的设置操作,把权限相关的信息写入到一个名字为"package-perms-"+userId的文件中。

5.实例化一个关键的对象,Settings对象。这个对象管理了开机时候需要读取的文件,如记录每一个安装的apk包中所有组件的packages.list,如记录每一个应用动态权限文件。

6.Settings将会添加如下几个公共用户id:

SYSTEM_UID 系统

RADIO_UID 电话

LOG_UID 打印

NFC_UID NFC设备

BLUETOOTH_UID 蓝牙设备

SHELL_UID shell 命令

SE_UID selinux

public static final int SYSTEM_UID = 1000;

public static final int PHONE_UID = 1001;

public static final int SHELL_UID = 2000;

public static final int LOG_UID = 1007;

public static final int NFC_UID = 1027;

public static final int BLUETOOTH_UID = 1002;

public static final int SE_UID = 1068;

我们来看看核心对象Settings是怎么实现的。至于UserManagerService,PermissionManagerService等暂时不再讨论范围内。

Settings 初始化

Settings(PermissionSettings permissions, Object lock) {

this(Environment.getDataDirectory(), permissions, lock);

}

Settings(File dataDir, PermissionSettings permission, Object lock) {

mLock = lock;

mPermissions = permission;

mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);

mSystemDir = new File(dataDir, "system");

mSystemDir.mkdirs();

FileUtils.setPermissions(mSystemDir.toString(),

FileUtils.S_IRWXU|FileUtils.S_IRWXG

|FileUtils.S_IROTH|FileUtils.S_IXOTH,

-1, -1);

mSettingsFilename = new File(mSystemDir, "packages.xml");

mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");

mPackageListFilename = new File(mSystemDir, "packages.list");

FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID);

final File kernelDir = new File("/config/sdcardfs");

mKernelMappingFilename = kernelDir.exists() ? kernelDir : null;

// Deprecated: Needed for migration

mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");

mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");

}

1.整个PMS的Settings 也就是设置相关的文件都保存在根目录/data 文件夹下。

2.接着在data文件夹下创建一个system文件夹'/data/system',并为这个文件夹设置只有本进程用户能读写执行,同一个进程用户组也能读写执行,其他进程组只能读或者执行,定义如下:

public static final int S_IRWXU = 00700;

public static final int S_IRUSR = 00400;

public static final int S_IWUSR = 00200;

public static final int S_IXUSR = 00100;

public static final int S_IRWXG = 00070;

public static final int S_IRGRP = 00040;

public static final int S_IWGRP = 00020;

public static final int S_IXGRP = 00010;

public static final int S_IRWXO = 00007;

public static final int S_IROTH = 00004;

public static final int S_IWOTH = 00002;

public static final int S_IXOTH = 00001;

3.在'/data/system' 下创建一个配置文件packages.xml,以及一个备份的配置文件packages-backup.xml,该文件将会存储每一个apk包的java代码的文件夹以及so库的文件夹位置

4.创建缓存所有应用相关信息的packages.list文件,并且设置当前的权限是0640,也就是本进程用户能读写,同一个进程(用户)组只能读,其他进程没有任何权限.

5.判断/config/sdcardfs文件是否存在,存在则mKernelMappingFilename

6.packages-stopped.xml维护的是被停掉的应用,packages-stopped-backup.xml则是它的备份信息。

本文关注的重点是关于包存储信息packages.list以及packages.xml,我们扒一扒这文件中存储的是什么东西?

packages.list文件内容

注意在Android 9.0中,已经没有权限打开这个权限。因此我将打开低版本Android 4.3中缓存的数据作为例子:

这里是packages.list文件内容:

com.google.android.location 10018 0 /data/data/com.google.android.location default

com.android.soundrecorder 10038 0 /data/data/com.android.soundrecorder release

com.android.sdksetup 10036 0 /data/data/com.android.sdksetup platform

com.android.defcontainer 10010 0 /data/data/com.android.defcontainer platform

com.android.launcher 10022 0 /data/data/com.android.launcher shared

com.android.smoketest 10047 0 /data/data/com.android.smoketest default

com.android.quicksearchbox 10035 0 /data/data/com.android.quicksearchbox shared

com.android.contacts 10000 0 /data/data/com.android.contacts shared

....

能看到在packages.list可以把这个数据分为如下几个部分:

com.google.android.location 包名

10018 这个应用对应的userId,也正是因为记录当前的userId,所以每一次才能保证userId是一致的,保证了在Android系统中可以通过userId正确的找到应用

0当前是否是debug模式,由AndroidManifest.xml中是否设置了android:debuggable

/data/data/com.google.android.location 确定了当前的应用存储数据的目录

default / release / platform / shared 这些字符串为在mac_permission.xml为每一个进程定义好的seinfo标签,seinfo不是描述文件的安全性,而是用来在seapp_contexts文件中查找对应的类型对象。

mac_permission.xml如下设置:

那么在seapp_contexts文件中有:

···java

user=_app seinfo=platform domain=platform_app type=app_data_file levelFrom=user

···

当 PackageManagerService 安装 App 的时候,它就会根据其签名或者包名查找到对应的 seinfo,并且将这个 seinfo 传递给另外一个守护进程 installed。

这部分属于SELinux的内容了,感兴趣的可以去阅读这一篇文章:SELinux的介绍。总之一句话就是,SELinux就是控制了不同权限的资源只能由对应的不同权限的进程才能访问。

packages.xml 内容

...

...

...

...

在packages大标签中,分为如下几个部分:

permissions 里面包含如 。permissions定义了所有在Android系统中当前的系统和App权限。可以分为两个两类:系统和App应用拥有的权限

package 代表了每一个安装在系统中App的应用。

该package标签包含了如下内容:

1.name 包名

2.codePath apk安装路径.主要是/system/app和/data/app两种

3.nativeLibraryPath 是so文件保存的位置

4.userId 是当前应用的userId

5.sigs 签名内容

shared-user标签包含如下内容

shared-user这标签就是指能够访问共享的进程。 com.google.android.apps.maps就是这个共享进程的包名,userId 是指当前进程的userId,以及perms是指这个进程中的权限

PMS实例化第二段

mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,

"*dexopt*");

DexManager.Listener dexManagerListener = DexLogger.getListener(this,

installer, mInstallLock);

mDexManager = new DexManager(mContext, this, mPackageDexOptimizer, installer, mInstallLock,

dexManagerListener);

mArtManagerService = new ArtManagerService(mContext, this, installer, mInstallLock);

mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());

mOnPermissionChangeListeners = new OnPermissionChangeListeners(

FgThread.get().getLooper());

getDefaultDisplayMetrics(context, mMetrics);

SystemConfig systemConfig = SystemConfig.getInstance();

mAvailableFeatures = systemConfig.getAvailableFeatures();

mProtectedPackages = new ProtectedPackages(mContext);

synchronized (mInstallLock) {

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 libConfig = systemConfig.getSharedLibraries();

final int builtInLibCount = libConfig.size();

for (int i = 0; i < builtInLibCount; i++) {

String name = libConfig.keyAt(i);

String path = libConfig.valueAt(i);

addSharedLibraryLPw(path, null, name, SharedLibraryInfo.VERSION_UNDEFINED,

SharedLibraryInfo.TYPE_BUILTIN, PLATFORM_PACKAGE_NAME, 0);

}

SELinuxMMAC.readInstallPolicy();

FallbackCategoryProvider.loadFallbacks();

mFirstBoot = !mSettings.readLPw(sUserManager.getUsers(false));

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 (mFirstBoot) {

requestCopyPreoptedFiles();

}

....

...

1.构造了一个PackageDexOptimizer对象,这个对象将会操作Installer对象,对dex文件进行优化成odex文件。odex文件是经过dex文件的优化,进行一些提前的校验,切换18种指令为更加高效的指令,构建vtable 虚方法table等。之后有机会会解析dex2oat,实际上其实dex2oat 几乎也完成了dexopt的工作。

2.构造了DexManager对象,用于控制PackageDexOptimizer对象,是Dex优化管理器。

3.构建ArtManagerService对象,这是一个Binder对象。开放给其他服务,在运行时进行art编译处理。

4.创建一个ServiceThread对象,这是一个HandlerThread对象。这就是一个带着Looper的线程,可以把Looper赋值给PackageHandler,创建PMS中的异步线程Handler对象。

5.创建一个WatchDog,监听PMS的死锁等情况

6.从系统配置systemConfig中,获取系统允许共享出来的共享库,保存在mSharedLibraries中。

7.调用readLPw读取保存在系统中所有安装的packages.xml的包中所有的信息,通过返回值确定是否是第一次启动PMS。读取完所有的所有的包后,从Settings的包集合判断这些包中是否还包含代码路径,调用enableSystemPackageLPw方法,处理是否是保存在mDisabledSysPackages集合中,也就是禁止使用的系统应用,如果存在则重新添加,不存则返回。

8.如果是第一次启动PMS,则调用requestCopyPreoptedFiles方法。

核心方法是readLPw。

Settings readLPw

boolean readLPw(@NonNull List users) {

FileInputStream str = null;

if (mBackupSettingsFilename.exists()) {

try {

str = new FileInputStream(mBackupSettingsFilename);

mReadMessages.append("Reading from backup settings file\n");

PackageManagerService.reportSettingsProblem(Log.INFO,

"Need to read from backup settings file");

if (mSettingsFilename.exists()) {

mSettingsFilename.delete();

}

} catch (java.io.IOException e) {

// We'll try for the normal settings file.

}

}

mPendingPackages.clear();

mPastSignatures.clear();

mKeySetRefs.clear();

mInstallerPackages.clear();

try {

if (str == null) {

if (!mSettingsFilename.exists()) {

mReadMessages.append("No settings file found\n");

PackageManagerService.reportSettingsProblem(Log.INFO,

"No settings file; creating initial state");

findOrCreateVersion(StorageManager.UUID_PRIVATE_INTERNAL).forceCurrent();

findOrCreateVersion(StorageManager.UUID_PRIMARY_PHYSICAL).forceCurrent();

return false;

}

str = new FileInputStream(mSettingsFilename);

}

XmlPullParser parser = Xml.newPullParser();

parser.setInput(str, StandardCharsets.UTF_8.name());

int type;

while ((type = parser.next()) != XmlPullParser.START_TAG

&& type != XmlPullParser.END_DOCUMENT) {

;

}

if (type != XmlPullParser.START_TAG) {

...

return false;

}

int outerDepth = parser.getDepth();

while ((type = parser.next()) != XmlPullParser.END_DOCUMENT

&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {

if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {

continue;

}

String tagName = parser.getName();

if (tagName.equals("package")) {

readPackageLPw(parser);

} else if (tagName.equals("permissions")) {

...

} else if (tagName.equals("permission-trees")) {

...

} else if (tagName.equals("shared-user")) {

...

} else if (tagName.equals("preferred-packages")) {

} else if (tagName.equals("preferred-activities")) {

...

} else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) {

...

} else if (tagName.equals(TAG_CROSS_PROFILE_INTENT_FILTERS)) {

...

} else if (tagName.equals(TAG_DEFAULT_BROWSER)) {

...

} else if (tagName.equals("updated-package")) {

...

} else if (tagName.equals("cleaning-package")) {

....

} else if (tagName.equals("renamed-package")) {

....

} else if (tagName.equals("restored-ivi")) {

....

} else if (tagName.equals("last-platform-version")) {

....

} else if (tagName.equals("database-version")) {

....

} else if (tagName.equals("verifier")) {

...

} else if (TAG_READ_EXTERNAL_STORAGE.equals(tagName)) {

...

} else if (tagName.equals("keyset-settings")) {

...

} else if (TAG_VERSION.equals(tagName)) {

...

} else {

...

}

}

str.close();

} catch (XmlPullParserException e) {

...

} catch (java.io.IOException e) {

...

}

if (PackageManagerService.CLEAR_RUNTIME_PERMISSIONS_ON_UPGRADE) {

final VersionInfo internal = getInternalVersion();

if (!Build.FINGERPRINT.equals(internal.fingerprint)) {

for (UserInfo user : users) {

mRuntimePermissionsPersistence.deleteUserRuntimePermissionsFile(user.id);

}

}

}

final int N = mPendingPackages.size();

for (int i = 0; i < N; i++) {

final PackageSetting p = mPendingPackages.get(i);

final int sharedUserId = p.getSharedUserId();

final Object idObj = getUserIdLPr(sharedUserId);

if (idObj instanceof SharedUserSetting) {

final SharedUserSetting sharedUser = (SharedUserSetting) idObj;

p.sharedUser = sharedUser;

p.appId = sharedUser.userId;

addPackageSettingLPw(p, sharedUser);

} else if (idObj != null) {

...

} else {

....

}

}

mPendingPackages.clear();

if (mBackupStoppedPackagesFilename.exists()

|| mStoppedPackagesFilename.exists()) {

// Read old file

readStoppedLPw();

mBackupStoppedPackagesFilename.delete();

mStoppedPackagesFilename.delete();

// Migrate to new file format

writePackageRestrictionsLPr(UserHandle.USER_SYSTEM);

} else {

for (UserInfo user : users) {

readPackageRestrictionsLPr(user.id);

}

}

for (UserInfo user : users) {

mRuntimePermissionsPersistence.readStateForUserSyncLPr(user.id);

}

final Iterator disabledIt = mDisabledSysPackages.values().iterator();

while (disabledIt.hasNext()) {

final PackageSetting disabledPs = disabledIt.next();

final Object id = getUserIdLPr(disabledPs.appId);

if (id != null && id instanceof SharedUserSetting) {

disabledPs.sharedUser = (SharedUserSetting) id;

}

}

mReadMessages.append("Read completed successfully: " + mPackages.size() + " packages, "

+ mSharedUsers.size() + " shared uids\n");

writeKernelMappingLPr();

return true;

}

1.首先尝试的查找是否有备份的package.xml数据,存在则说明可能发生过错误,则读取备份文件中的FileStream。并删除了package.xml原来文件

2.不存备份文件,则直接读取packages.xml的FileStream。

3.在这个过程中,就能看到就是一个简单的解析xml文件的过程,每遇到一个标签就进行对应的解析行为。如package信息,权限信息等。

解析完所有的信息后,并开始处理mPendingPackages数据。最后再检测是否存在备份文件或者packages-stopped.xml ,存在两者其一,则读取packages-stopped.xml中的数据,并把备份数据重新写入到新的packages.xml文件中。

值得注意的是解析标签package,在介些这个标签的时候执行了readPackageLPw方法对package标签进一步的解析:

else if (userId > 0) {

packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr),

new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiString,

secondaryCpuAbiString, cpuAbiOverrideString, userId, versionCode, pkgFlags,

pkgPrivateFlags, parentPackageName, null /*childPackageNames*/,

null /*usesStaticLibraries*/, null /*usesStaticLibraryVersions*/);

...

} else {

packageSetting.setTimeStamp(timeStamp);

packageSetting.firstInstallTime = firstInstallTime;

packageSetting.lastUpdateTime = lastUpdateTime;

}

}

调用addPackageLPw添加到缓存中。

addPackageLPw

PackageSetting addPackageLPw(String name, String realName, File codePath, File resourcePath,

String legacyNativeLibraryPathString, String primaryCpuAbiString,

String secondaryCpuAbiString, String cpuAbiOverrideString, int uid, long vc, int

pkgFlags, int pkgPrivateFlags, String parentPackageName,

List childPackageNames, String[] usesStaticLibraries,

long[] usesStaticLibraryNames) {

PackageSetting p = mPackages.get(name);

if (p != null) {

if (p.appId == uid) {

return p;

}

PackageManagerService.reportSettingsProblem(Log.ERROR,

"Adding duplicate package, keeping first: " + name);

return null;

}

p = new PackageSetting(name, realName, codePath, resourcePath,

legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString,

cpuAbiOverrideString, vc, pkgFlags, pkgPrivateFlags, parentPackageName,

childPackageNames, 0 /*userId*/, usesStaticLibraries, usesStaticLibraryNames);

p.appId = uid;

if (addUserIdLPw(uid, p, name)) {

mPackages.put(name, p);

return p;

}

return null;

}

很简单,就是根据当前的路径名,资源文件路径,代码文件路径,so库路径生成一个App应用PackageSetting的配置内存文件,保存到mPackages中。

PMS 实例化第三段

final String bootClassPath = System.getenv("BOOTCLASSPATH");

final String systemServerClassPath = System.getenv("SYSTEMSERVERCLASSPATH");

File frameworkDir = new File(Environment.getRootDirectory(), "framework");

final VersionInfo ver = mSettings.getInternalVersion();

mIsUpgrade = !Build.FINGERPRINT.equals(ver.fingerprint);

mPromoteSystemApps =

mIsUpgrade && ver.sdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1;

mIsPreNUpgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N;

mIsPreNMR1Upgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N_MR1;

if (mPromoteSystemApps) {

...

}

mCacheDir = preparePackageParserCache(mIsUpgrade);

int scanFlags = SCAN_BOOTING | SCAN_INITIAL;

if (mIsUpgrade || mFirstBoot) {

scanFlags = scanFlags | SCAN_FIRST_BOOT_OR_UPGRADE;

}

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);

mParallelPackageParserCallback.findStaticOverlayPackages();

scanDirTracedLI(frameworkDir,

mDefParseFlags

| PackageParser.PARSE_IS_SYSTEM_DIR,

scanFlags

| SCAN_NO_DEX

| SCAN_AS_SYSTEM

| SCAN_AS_PRIVILEGED,

0);

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);

final File systemAppDir = new File(Environment.getRootDirectory(), "app");

scanDirTracedLI(systemAppDir,

mDefParseFlags

| PackageParser.PARSE_IS_SYSTEM_DIR,

scanFlags

| SCAN_AS_SYSTEM,

0);

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);

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);

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);

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);

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);

解析来这一段的工作实际上就是给第三方厂商的提供的包名应用,提供的服务通过scanDirTracedLI方法,把整个包的数据解析扫描到PMS的内存。

这里就有如下几个大目录:

1.mCacheDir 首先通过preparePackageParserCache方法获取当前PMS下扫描结果的缓存目录:/data/system/package_cache/ 所有的包扫描的结果都会缓存到这里

2./vendor/overlay

3./product/overlay 第2和第3点都是第三方厂商提供的资源复写目录

4./system/framework Android系统framework层内置提供的java的核心jar包,odex等

5./system/priv-app,/system/app ,这里面提供了Android系统或者厂商默认的系统应用

6./vendor/priv-app,/vendor/app 这是交给硬件厂商的目录,允许他们内置内置一些系统应用服务。我之前常说的hal层,就是在这个vendor目录安装提供的。

7./odm/priv-app,/odm/app 可以看作是vendor目录的一种延伸。

原始设计制造商 (ODM) 能够为其特定设备(开发板)自定义系统芯片 (SoC) 供应商板级支持包 (BSP).这样,他们就可以为板级组件、板级守护进程或者其基于硬件抽象层 (HAL) 的自有功能实现内核模块。他们可能还需要替换或自定义 SoC 组件。

我们不是搞hal层的,没必要进一步探讨了。

8./oem/app ,/product/priv-app,/product/app

OEM 会自定义 AOSP 系统映像,以实现自己的功能并满足运营商的要求

product分区则是从Android 9.0开始支持的分区。oem是老版本的product分区,product可以依赖oem分区。product可以多次刷新,oem不可刷新只能出厂一次。这两个分区就是支持自定义 AOSP 系统映像,product的分区出现能够更加灵活多语言多地区的系统映像。

能发现每一个目录下,都调用了整个PMS最核心的方法scanDirTracedLI 对apk,jar包的解析方法。

scanDirTracedLI这个方法我们稍后再看,现在我们可以得知这个方法执行后,PMS就能知道安装apk包中具体的信息了,并把解析出来的PackageParser.Package对象保存在PMS全局变量mPackages中

PMS 第四段

final List possiblyDeletedUpdatedSystemApps = new ArrayList<>();

final List stubSystemApps = new ArrayList<>();

if (!mOnlyCore) {

final Iterator pkgIterator = mPackages.values().iterator();

while (pkgIterator.hasNext()) {

final PackageParser.Package pkg = pkgIterator.next();

if (pkg.isStub) {

stubSystemApps.add(pkg.packageName);

}

}

final Iterator psit = mSettings.mPackages.values().iterator();

while (psit.hasNext()) {

PackageSetting ps = psit.next();

if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0) {

continue;

}

final PackageParser.Package scannedPkg = mPackages.get(ps.name);

if (scannedPkg != null) {

if (mSettings.isDisabledSystemPackageLPr(ps.name)) {

removePackageLI(scannedPkg, true);

mExpectingBetter.put(ps.name, ps.codePath);

}

continue;

}

if (!mSettings.isDisabledSystemPackageLPr(ps.name)) {

psit.remove();

} else {

final PackageSetting disabledPs =

mSettings.getDisabledSystemPkgLPr(ps.name);

if (disabledPs.codePath == null || !disabledPs.codePath.exists()

|| disabledPs.pkg == null) {

possiblyDeletedUpdatedSystemApps.add(ps.name);

}

}

}

}

//delete tmp files

deleteTempPackageFiles();

...

if (!mOnlyCore) {

scanDirTracedLI(sAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);

scanDirTracedLI(sDrmAppPrivateInstallDir, mDefParseFlags

| PackageParser.PARSE_FORWARD_LOCK,

scanFlags | SCAN_REQUIRE_KNOWN, 0);

for (String deletedAppName : possiblyDeletedUpdatedSystemApps) {

PackageParser.Package deletedPkg = mPackages.get(deletedAppName);

mSettings.removeDisabledSystemPackageLPw(deletedAppName);

final String msg;

if (deletedPkg == null) {

} else {

msg = "Updated system package + " + deletedAppName

+ " no longer exists; revoking system privileges";

final PackageSetting deletedPs = mSettings.mPackages.get(deletedAppName);

deletedPkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM;

deletedPs.pkgFlags &= ~ApplicationInfo.FLAG_SYSTEM;

}

logCriticalInfo(Log.WARN, msg);

}

for (int i = 0; i < mExpectingBetter.size(); i++) {

final String packageName = mExpectingBetter.keyAt(i);

if (!mPackages.containsKey(packageName)) {

final File scanFile = mExpectingBetter.valueAt(i);

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 {

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());

}

}

}

decompressSystemApplications(stubSystemApps, scanFlags);

final int cachedNonSystemApps = PackageParser.sCachedPackageReadCount.get()

- cachedSystemApps;

final long dataScanTime = SystemClock.uptimeMillis() - systemScanTime - startTime;

final int dataPackagesCount = mPackages.size() - systemPackagesCount;

if (mIsUpgrade && dataPackagesCount > 0) {

MetricsLogger.histogram(null, "ota_package_manager_data_app_avg_scan_time",

((int) dataScanTime) / dataPackagesCount);

}

}

mExpectingBetter.clear();

mStorageManagerPackage = getStorageManagerPackageName();

mSetupWizardPackage = getSetupWizardPackageName();

if (mProtectedFilters.size() > 0) {

for (ActivityIntentInfo filter : mProtectedFilters) {

if (filter.activity.info.packageName.equals(mSetupWizardPackage)) {

continue;

}

filter.setPriority(0);

}

}

mSystemTextClassifierPackage = getSystemTextClassifierPackageName();

mDeferProtectedFilters = false;

mProtectedFilters.clear();

updateAllSharedLibrariesLPw(null);

....

mPackageUsage.read(mPackages);

mCompilerStats.read();

...

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");

...

mSettings.writeLPr();

...

final Map> userPackages = new HashMap<>();

final int[] currentUserIds = UserManagerService.getInstance().getUserIds();

for (int userId : currentUserIds) {

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)

Runtime.getRuntime().gc();

mInstaller.setWarnIfHeld(mPackages);

1.扫描所以在上面安装好系统apk等文件,查找哪些系统禁止的包名,则调用removePackageLI从缓存中移除。删除所有的临时包文件

2.接下来扫描我们应用开发最重要的2个目录:/data/app,/data/app-private.

/data/app 是app安装的路径。所有的app都会安装到这个目录下,可以进进一步的通过对应的包名找到我们的apk应用中的代码等数据。/data/app-private这是每一个应用存储除了代码和资源的其他私密数据。

3.在扫描app安装目录之后,遍历possiblyDeletedUpdatedSystemApps,看看有没有那个apk是需要删除,则调用removeDisabledSystemPackageLPw 从系统配置中移除。这个possiblyDeletedUpdatedSystemApps集合就是系统设置的禁用包集合

4.扫描mExpectingBetter集合中保存的apk包。这个集合说明的是app中有更加新的版本,期望进行更新,所以会进行扫描替换原来的app应用

5.调用Settings的writeLPr方法。更新package.list中的安装包数据列表。

6.通过SystemServerInitThreadPool启动一个特殊的线程池,赋值为mPrepareAppDataFuture对象。执行了如下内容:

1.调用了Installd的fixupAppData方法,创建一个/data/user/用户id和/data/data目录,这个目录由StoreManagerService进行管理。这里要和每一个应用的userId要区分开,其实是指登陆Android不同的用户。也就是我们常见的/data/user/0./data/user/用户id是/data/data的软链接。不同的用户id只能访问到不同userid对应的app安装内容。可以认为其实每一个app安装的实际路径是/data/user/用户ID/包名/。而我们常见到的/data/data/包名其实是他的软连接。

2.调用prepareAppDataAndMigrateLIF方法,准备应用数据。最终会调用到prepareAppDataLeafLIF方法中:

private void prepareAppDataLeafLIF(PackageParser.Package pkg, int userId, int flags) {

...

try {

ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId, flags,

appId, seInfo, app.targetSdkVersion);

} catch (InstallerException e) {

....

}

if (mIsUpgrade || mFirstBoot || (userId != UserHandle.USER_SYSTEM)) {

mArtManagerService.prepareAppProfiles(pkg, userId);

}

...

prepareAppDataContentsLeafLIF(pkg, userId, flags);

}

1.Installer的createAppData 实际上就是遍历所有的包名,为每一个包名创建一个cache以及code_cache的目录,用于缓存编译优化后的结果。

2.prepareAppProfiles,这个方法最后调用了IInstalld的prepareAppProfile方法,并且调用保存在/system/bin/profman 这个程序,在程序目录下生成一个.prof文件,这个文件可以加速dex2oat编译优化的速度。

3.prepareAppDataContentsLeafLIF 核心就是调用了IInstalld的linkNativeLibraryDirectory。其实就是把app的安装so库的目录/data/data/包名/lib和/data/user/用户id/包名/lib链接上。

7.初始化数据存储服务,InstantApp的扫描,以及让DexManager检查持有每一个分配了用户id的应用的代码路径,保存在PackageDexUsage中。

到这里PMS的实例化,大体上都过了一遍,能看到实际上PMS就是在引导时候,把所有之后Android需要使用的代码包都进行了扫描,并且加载了所有包的配置等文件。其中扫描最为重要,扫描核心方法就是scanDirTracedLI。

暂且放一放,我们继续走PMS初始化流程,我们最后回头看看这个方法都做了什么?

PMS updatePackagesIfNeeded

public void updatePackagesIfNeeded() {

enforceSystemOrRoot("Only the system can request package update");

boolean causeUpgrade = isUpgrade();

boolean causeFirstBoot = isFirstBoot() || mIsPreNUpgrade;

boolean causePrunedCache = VMRuntime.didPruneDalvikCache();

if (!causeUpgrade && !causeFirstBoot && !causePrunedCache) {

return;

}

List pkgs;

synchronized (mPackages) {

pkgs = PackageManagerServiceUtils.getPackagesForDexopt(mPackages.values(), this);

}

final long startTime = System.nanoTime();

final int[] stats = performDexOptUpgrade(pkgs, mIsPreNUpgrade /* showDialog */,

causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT,

false /* bootComplete */);

...

}

这里面做了两件事情:

1.PackageManagerServiceUtils的getPackagesForDexopt方法,对需要在开机时候进行dexopt优化的apk包进行优化。此时会对PMS有一个dex优化的优先级顺序,其顺序依次为:

1.coreApp 也就是系统核心app服务的包最早进行优化

2.其次是哪些需要接受Intent.ACTION_PRE_BOOT_COMPLETED的广播接受者对应的apk包

3.还有被前两种apk依赖的代码 apk包

2.最终循环调用performDexOptUpgrade。其中PackageDexOptimizer对象的performDexOpt方法。这个方法最终会调用Installer的dexopt方法,通知Intsalld服务,也是一个Binder对象,跨进程通信到Intsalld服务执行dexopt,对dex文件进行优化

更加详细的超出本文讨论范围,之后有空在聊dex2oat的时候一起聊了。

PMS performFstrimIfNeeded

public void performFstrimIfNeeded() {

enforceSystemOrRoot("Only the system can request fstrim");

try {

IStorageManager sm = PackageHelper.getStorageManager();

if (sm != null) {

boolean doTrim = false;

final long interval = android.provider.Settings.Global.getLong(

mContext.getContentResolver(),

android.provider.Settings.Global.FSTRIM_MANDATORY_INTERVAL,

DEFAULT_MANDATORY_FSTRIM_INTERVAL);

if (interval > 0) {

final long timeSinceLast = System.currentTimeMillis() - sm.lastMaintenance();

if (timeSinceLast > interval) {

doTrim = true;

}

}

if (doTrim) {

final boolean dexOptDialogShown;

synchronized (mPackages) {

dexOptDialogShown = mDexOptDialogShown;

}

if (!isFirstBoot() && dexOptDialogShown) {

try {

ActivityManager.getService().showBootMessage(

mContext.getResources().getString(

R.string.android_upgrading_fstrim), true);

} catch (RemoteException e) {

}

}

sm.runMaintenance();

}

} else {

}

} catch (RemoteException e) {

}

}

这里只做了一件事情:获取StorageManagerService对象,判断此时的时间和上一次操作Android系统的存储磁盘最晚的时间差是多少。默认是超过了3天,则调用StorageManagerService的runMaintenance方法,删除哪些不再有效的数据(注意在操作系统中,文件删除不是立即从磁盘中删除,而是把磁盘中的block中的数据,打上一个标记允许其他数据覆盖)。 之后有机会,会在Linux内核中和大家聊聊整个Linux如何管理磁盘的。

PMS systemReady

public void systemReady() {

enforceSystemOrRoot("Only the system can claim the system is ready");

mSystemReady = true;

final ContentResolver resolver = mContext.getContentResolver();

ContentObserver co = new ContentObserver(mHandler) {

@Override

public void onChange(boolean selfChange) {

mWebInstantAppsDisabled =

(Global.getInt(resolver, Global.ENABLE_EPHEMERAL_FEATURE, 1) == 0) ||

(Secure.getInt(resolver, Secure.INSTANT_APPS_ENABLED, 1) == 0);

}

};

mContext.getContentResolver().registerContentObserver(android.provider.Settings.Global

.getUriFor(Global.ENABLE_EPHEMERAL_FEATURE),

false, co, UserHandle.USER_SYSTEM);

mContext.getContentResolver().registerContentObserver(android.provider.Settings.Secure

.getUriFor(Secure.INSTANT_APPS_ENABLED), false, co, UserHandle.USER_SYSTEM);

co.onChange(true);

...

sUserManager.systemReady();

// If we upgraded grant all default permissions before kicking off.

for (int userId : grantPermissionsUserIds) {

mDefaultPermissionPolicy.grantDefaultPermissions(userId);

}

if (grantPermissionsUserIds == EMPTY_INT_ARRAY) {

mDefaultPermissionPolicy.scheduleReadDefaultPermissionExceptions();

}

synchronized (mPackages) {

mPermissionManager.updateAllPermissions(

StorageManager.UUID_PRIVATE_INTERNAL, false, mPackages.values(),

mPermissionCallback);

}

// Kick off any messages waiting for system ready

if (mPostSystemReadyMessages != null) {

for (Message msg : mPostSystemReadyMessages) {

msg.sendToTarget();

}

mPostSystemReadyMessages = null;

}

// Watch for external volumes that come and go over time

final StorageManager storage = mContext.getSystemService(StorageManager.class);

storage.registerListener(mStorageListener);

mInstallerService.systemReady();

mDexManager.systemReady();

mPackageDexOptimizer.systemReady();

StorageManagerInternal StorageManagerInternal = LocalServices.getService(

StorageManagerInternal.class);

StorageManagerInternal.addExternalStoragePolicy(

new StorageManagerInternal.ExternalStorageMountPolicy() {

@Override

public int getMountMode(int uid, String packageName) {

if (Process.isIsolated(uid)) {

return Zygote.MOUNT_EXTERNAL_NONE;

}

if (checkUidPermission(READ_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) {

return Zygote.MOUNT_EXTERNAL_DEFAULT;

}

if (checkUidPermission(WRITE_EXTERNAL_STORAGE, uid) == PERMISSION_DENIED) {

return Zygote.MOUNT_EXTERNAL_READ;

}

return Zygote.MOUNT_EXTERNAL_WRITE;

}

@Override

public boolean hasExternalStorage(int uid, String packageName) {

return true;

}

});

// Now that we're mostly running, clean up stale users and apps

sUserManager.reconcileUsers(StorageManager.UUID_PRIVATE_INTERNAL);

reconcileApps(StorageManager.UUID_PRIVATE_INTERNAL);

mPermissionManager.systemReady();

if (mInstantAppResolverConnection != null) {

mContext.registerReceiver(new BroadcastReceiver() {

@Override

public void onReceive(Context context, Intent intent) {

mInstantAppResolverConnection.optimisticBind();

mContext.unregisterReceiver(this);

}

}, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));

}

}

1.设置了两个两个CP组件的数据变化监听者,分别是Global.ENABLE_EPHEMERAL_FEATURE以及Secure.INSTANT_APPS_ENABLED

public static final String ENABLE_EPHEMERAL_FEATURE = "enable_ephemeral_feature"

public static final String INSTANT_APPS_ENABLED = "instant_apps_enabled"

这两个标志位共同决定了mWebInstantAppsDisabled 也就是Web的InstantApp是否可以生效。

2.调用UserManager的systemReady

3.注册了StorageManager的监听

4.PackageInstallerService 的systemReady

5.DexManager的systemReady

6.PackageDexOptimizer的systemReady

7.PermissionManagerService的systemReady

8.注册一个Intent.ACTION_BOOT_COMPLETED 系统系统启动完成的广播

PMS waitForAppDataPrepared

当AMS调用了systemReady之后,说明Android系统其实可以启动第一个App也就是桌面应用了,但是此时会调用waitForAppDataPrepared等待PMS的一些事务完成。

public void waitForAppDataPrepared() {

if (mPrepareAppDataFuture == null) {

return;

}

ConcurrentUtils.waitForFutureNoInterrupt(mPrepareAppDataFuture, "wait for prepareAppData");

mPrepareAppDataFuture = null;

}

其实就是等待mPrepareAppDataFuture的完成。而这个对象在PMS的实例化小结聊过,其实就是为每一个应用包创建对应的软连接和目录。

PMS scanDirTracedLI 扫描应用的原理

对于PMS的启动有了一个总体的概括之后,我们来看看PMS中最为的核心方法没有之一的scanDirTracedLI中做了什么?怎么解析apk包的。

private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime) {

final File[] files = scanDir.listFiles();

try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(

mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir,

mParallelPackageParserCallback)) {

// 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++;

}

for (; fileCount > 0; fileCount--) {

ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();

Throwable throwable = parseResult.throwable;

int errorCode = PackageManager.INSTALL_SUCCEEDED;

if (throwable == null) {

if (parseResult.pkg.applicationInfo.isStaticSharedLibrary()) {

renameStaticSharedLibraryPackage(parseResult.pkg);

}

try {

if (errorCode == PackageManager.INSTALL_SUCCEEDED) {

scanPackageChildLI(parseResult.pkg, parseFlags, scanFlags,

currentTime, null);

}

} catch (PackageManagerException e) {

...

}

} else if (throwable instanceof PackageParser.PackageParserException) {

PackageParser.PackageParserException e = (PackageParser.PackageParserException)

throwable;

errorCode = e.error;

} else {

...

}

// Delete invalid userdata apps

if ((scanFlags & SCAN_AS_SYSTEM) == 0 &&

errorCode != PackageManager.INSTALL_SUCCEEDED) {

removeCodePathLI(parseResult.scanFile);

}

}

}

}

把mCacheDir作为参数,构造了一个ParallelPackageParser 并行执行的包解析器。

class ParallelPackageParser implements AutoCloseable {

private static final int QUEUE_CAPACITY = 10;

private static final int MAX_THREADS = 4;

private final String[] mSeparateProcesses;

private final boolean mOnlyCore;

private final DisplayMetrics mMetrics;

private final File mCacheDir;

private final PackageParser.Callback mPackageParserCallback;

private volatile String mInterruptedInThread;

private final BlockingQueue mQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY);

private final ExecutorService mService = ConcurrentUtils.newFixedThreadPool(MAX_THREADS,

"package-parsing-thread", Process.THREAD_PRIORITY_FOREGROUND);

ParallelPackageParser(String[] separateProcesses, boolean onlyCoreApps,

DisplayMetrics metrics, File cacheDir, PackageParser.Callback callback) {

mSeparateProcesses = separateProcesses;

mOnlyCore = onlyCoreApps;

mMetrics = metrics;

mCacheDir = cacheDir;

mPackageParserCallback = callback;

}

能看到在ParallelPackageParser中存在一个名字为package-parsing-thread的线程池,而这个线程吃最大并行数量为4.以及一个ArrayBlockingQueue,这个同步阻塞队列大小为10.

2.遍历该目录所有的文件,如果判断是可以进行解析的apk包,则调用submit方法,为ParallelPackageParser提交一个解析任务。

public void submit(File scanFile, int parseFlags) {

mService.submit(() -> {

ParseResult pr = new ParseResult();

try {

PackageParser pp = new PackageParser();

pp.setSeparateProcesses(mSeparateProcesses);

pp.setOnlyCoreApps(mOnlyCore);

pp.setDisplayMetrics(mMetrics);

pp.setCacheDir(mCacheDir);

pp.setCallback(mPackageParserCallback);

pr.scanFile = scanFile;

pr.pkg = parsePackage(pp, scanFile, parseFlags);

} catch (Throwable e) {

...

} finally {

...

}

try {

mQueue.put(pr);

} catch (InterruptedException e) {

...

}

});

}

当在线程中开始执行的时候,就会实例化一个新的PackageParser对象,并且调用parsePackage方法执行解析,最后把解析的结果放在ArrayBlockingQueue中,等待获取。ArrayBlockingQueue这个队列很简单,就是一个生产者消费者模式,当没数据想取出的时候会被阻塞,等到数据加入后唤醒。当想要放入任务进行消费,但是满了,就会阻塞不允许放入任务。

而parsePackage方法会调用PackageParser.parsePackage.

PackageParser parsePackage

public Package parsePackage(File packageFile, int flags, boolean useCaches)

throws PackageParserException {

Package parsed = useCaches ? getCachedResult(packageFile, flags) : null;

if (parsed != null) {

return parsed;

}

if (packageFile.isDirectory()) {

parsed = parseClusterPackage(packageFile, flags);

} else {

parsed = parseMonolithicPackage(packageFile, flags);

}

long cacheTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;

cacheResult(packageFile, flags, parsed);

...

}

return parsed;

}

1.首先尝试的通过getCachedResult获取是否已经有解析好的缓存数据,有则直接返回Package

2.没有缓存,则判断当前的packageFile是文件夹还是文件:

1.是文件夹则调用parseClusterPackage方法

2.是文件则调用parseMonolithicPackage方法

3.最后通过cacheResult 缓存下来。

我们先跳过缓存的逻辑看看PackageParser是怎么解析的。由于一般存在data/app下都是一个apk文件,所以我们以parseMonolithicPackage为例子

PackageParser parseMonolithicPackage

public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {

final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);

....

final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);

try {

final Package pkg = parseBaseApk(apkFile, assetLoader.getBaseAssetManager(), flags);

pkg.setCodePath(apkFile.getCanonicalPath());

pkg.setUse32bitAbi(lite.use32bitAbi);

return pkg;

} catch (IOException e) {

...

} finally {

IoUtils.closeQuietly(assetLoader);

}

}

先构造一个DefaultSplitAssetLoader对象,这个对象实际上就是通过ApkAssets进行解析,并获取AssetsManager对象。关于AssetsManager相关的原理可以阅读资源管理系统系列文章。

调用parseBaseApk解析apk.

PackageParser parseBaseApk

public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";

private Package parseBaseApk(File apkFile, AssetManager assets, int flags)

throws PackageParserException {

final String apkPath = apkFile.getAbsolutePath();

String volumeUuid = null;

if (apkPath.startsWith(MNT_EXPAND)) {

final int end = apkPath.indexOf('/', MNT_EXPAND.length());

volumeUuid = apkPath.substring(MNT_EXPAND.length(), end);

}

mParseError = PackageManager.INSTALL_SUCCEEDED;

mArchiveSourcePath = apkFile.getAbsolutePath();

XmlResourceParser parser = null;

try {

final int cookie = assets.findCookieForPath(apkPath);

...

parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);

final Resources res = new Resources(assets, mMetrics, null);

final String[] outError = new String[1];

final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError);

...

pkg.setVolumeUuid(volumeUuid);

pkg.setApplicationVolumeUuid(volumeUuid);

pkg.setBaseCodePath(apkPath);

pkg.setSigningDetails(SigningDetails.UNKNOWN);

return pkg;

} catch (PackageParserException e) {

...

} catch (Exception e) {

...

} finally {

IoUtils.closeQuietly(parser);

}

}

很简单,就是通过findCookieForPath找到apk缓存对应的cookieId,并以此为索引,调用AssetManager.openXmlResourceParser方法,打开AndroidManifest.xml的流,等待后面parseBaseApk的解析

parseBaseApk

private Package parseBaseApk(String apkPath, Resources res, XmlResourceParser parser, int flags,

String[] outError) throws XmlPullParserException, IOException {

final String splitName;

final String pkgName;

try {

Pair packageSplit = parsePackageSplitNames(parser, parser);

pkgName = packageSplit.first;

splitName = packageSplit.second;

if (!TextUtils.isEmpty(splitName)) {

...

return null;

}

} catch (PackageParserException e) {

...

return null;

}

if (mCallback != null) {

String[] overlayPaths = mCallback.getOverlayPaths(pkgName, apkPath);

if (overlayPaths != null && overlayPaths.length > 0) {

for (String overlayPath : overlayPaths) {

res.getAssets().addOverlayPath(overlayPath);

}

}

}

final Package pkg = new Package(pkgName);

TypedArray sa = res.obtainAttributes(parser,

com.android.internal.R.styleable.AndroidManifest);

pkg.mVersionCode = sa.getInteger(

com.android.internal.R.styleable.AndroidManifest_versionCode, 0);

pkg.mVersionCodeMajor = sa.getInteger(

com.android.internal.R.styleable.AndroidManifest_versionCodeMajor, 0);

pkg.applicationInfo.setVersionCode(pkg.getLongVersionCode());

pkg.baseRevisionCode = sa.getInteger(

com.android.internal.R.styleable.AndroidManifest_revisionCode, 0);

pkg.mVersionName = sa.getNonConfigurationString(

com.android.internal.R.styleable.AndroidManifest_versionName, 0);

if (pkg.mVersionName != null) {

pkg.mVersionName = pkg.mVersionName.intern();

}

pkg.coreApp = parser.getAttributeBooleanValue(null, "coreApp", false);

pkg.mCompileSdkVersion = sa.getInteger(

com.android.internal.R.styleable.AndroidManifest_compileSdkVersion, 0);

pkg.applicationInfo.compileSdkVersion = pkg.mCompileSdkVersion;

pkg.mCompileSdkVersionCodename = sa.getNonConfigurationString(

com.android.internal.R.styleable.AndroidManifest_compileSdkVersionCodename, 0);

if (pkg.mCompileSdkVersionCodename != null) {

pkg.mCompileSdkVersionCodename = pkg.mCompileSdkVersionCodename.intern();

}

pkg.applicationInfo.compileSdkVersionCodename = pkg.mCompileSdkVersionCodename;

sa.recycle();

return parseBaseApkCommon(pkg, null, res, parser, flags, outError);

}

1.生成Package对象,获取包名以及 标签做的AndroidManifest分割部分(这种很少用,其实就是bundle模块,允许动态切割资源和包,分配提交市场)。

2.从AndroidManifest解析出版本名,版本号等常用参数设置到App应用中。

3.parseBaseApkCommon进行解析Application标签,uses-permission等同等级数据。

if (tagName.equals(TAG_APPLICATION)) {

if (foundApp) {

if (RIGID_PARSER) {

outError[0] = " has more than one ";

mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;

return null;

} else {

Slog.w(TAG, " has more than one ");

XmlUtils.skipCurrentTag(parser);

continue;

}

}

foundApp = true;

if (!parseBaseApplication(pkg, res, parser, flags, outError)) {

return null;

}

}

能看到这里进行了Application数目的校验,只允许一个存在。接着开始解析parseBaseApplication中的组件信息。

parseBaseApplication

这个方法很长,解析所有在Application标签的参数,我们重点关注四大组件是如何解析的

private boolean parseBaseApplication(Package owner, Resources res,

XmlResourceParser parser, int flags, String[] outError)

throws XmlPullParserException, IOException {

final ApplicationInfo ai = owner.applicationInfo;

final String pkgName = owner.applicationInfo.packageName;

TypedArray sa = res.obtainAttributes(parser,

com.android.internal.R.styleable.AndroidManifestApplication);

...

while ((type = parser.next()) != XmlPullParser.END_DOCUMENT

&& (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {

if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {

continue;

}

String tagName = parser.getName();

if (tagName.equals("activity")) {

Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs, false,

owner.baseHardwareAccelerated);

if (a == null) {

mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;

return false;

}

hasActivityOrder |= (a.order != 0);

owner.activities.add(a);

} else if (tagName.equals("receiver")) {

Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs,

true, false);

if (a == null) {

mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;

return false;

}

hasReceiverOrder |= (a.order != 0);

owner.receivers.add(a);

} else if (tagName.equals("service")) {

Service s = parseService(owner, res, parser, flags, outError, cachedArgs);

if (s == null) {

mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;

return false;

}

hasServiceOrder |= (s.order != 0);

owner.services.add(s);

} else if (tagName.equals("provider")) {

Provider p = parseProvider(owner, res, parser, flags, outError, cachedArgs);

if (p == null) {

mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;

return false;

}

owner.providers.add(p);

}

}

...

}

1.解析activity标签:parseActivity解析出了一个Activity对象,保存到Package的activities集合中

2.解析receiver标签: 还是调用parseActivity方法,解析了一个Activity,保存到Package的receivers集合中

3.解析service标签: 调用了parseService方法,解析出了Service,保存到Package的services集合中

4.解析provider标签: 调用了parseProvider方法,解析出了Provider,保存到Package的providers集合中

这四个方法没什么好说的,都是很简单的解析xml中的内容,接着设置到了对应的结构对象中并返回。

PackageParser缓存原理

每一次解析完成之前都会有进行缓存校验和获取。因为每一次解析apk包中的 AndroidManifest.xml确实比较耗时。

获取和存储分别由两个方法完成:

Package parsed = useCaches ? getCachedResult(packageFile, flags) : null;

cacheResult(packageFile, flags, parsed);

我们先来看看cacheResult是怎么存储的。

PackageParser cacheResult

private String getCacheKey(File packageFile, int flags) {

StringBuilder sb = new StringBuilder(packageFile.getName());

sb.append('-');

sb.append(flags);

return sb.toString();

}

private void cacheResult(File packageFile, int flags, Package parsed) {

if (mCacheDir == null) {

return;

}

try {

final String cacheKey = getCacheKey(packageFile, flags);

final File cacheFile = new File(mCacheDir, cacheKey);

if (cacheFile.exists()) {

if (!cacheFile.delete()) {

}

}

final byte[] cacheEntry = toCacheEntry(parsed);

if (cacheEntry == null) {

return;

}

try (FileOutputStream fos = new FileOutputStream(cacheFile)) {

fos.write(cacheEntry);

} catch (IOException ioe) {

cacheFile.delete();

}

} catch (Throwable e) {

Slog.w(TAG, "Error saving package cache.", e);

}

}

通过getCacheKey构建出来的key为包名_flag,也就是包名_0,并在/data/system/package_cache/生成一个对应的文件,也就是/data/system/package_cache/包名_0.

通过toCacheEntry方法,获取获取Package所有的内容,如果为空则返回。

不为空,则通过FileOutputStream把Package存储好的二进制数据全部写到/data/system/package_cache/包名_0文件中。

toCacheEntry系统如何快速获取该文件中所有的内容呢?

toCacheEntry

protected byte[] toCacheEntry(Package pkg) {

return toCacheEntryStatic(pkg);

}

/** static version of {@link #toCacheEntry} for unit tests. */

@VisibleForTesting

public static byte[] toCacheEntryStatic(Package pkg) {

final Parcel p = Parcel.obtain();

final WriteHelper helper = new WriteHelper(p);

pkg.writeToParcel(p, 0 /* flags */);

helper.finishAndUninstall();

byte[] serialized = p.marshall();

p.recycle();

return serialized;

}

很有趣:

1.就是通过Parcel,把package写入到Parcel中。

2.调用了WriteHelper的finishAndUninstall方法。

public static class WriteHelper extends Parcel.ReadWriteHelper {

private final ArrayList mStrings = new ArrayList<>();

private final HashMap mIndexes = new HashMap<>();

private final Parcel mParcel;

private final int mStartPos;

public WriteHelper(Parcel p) {

mParcel = p;

mStartPos = p.dataPosition();

mParcel.writeInt(0); // We come back later here and write the pool position.

mParcel.setReadWriteHelper(this);

}

@Override

public void writeString(Parcel p, String s) {

final Integer cur = mIndexes.get(s);

if (cur != null) {

// String already in the pool. Just write the index.

p.writeInt(cur); // Already in the pool.

if (DEBUG) {

Log.i(TAG, "Duplicate '" + s + "' at " + cur);

}

} else {

final int index = mStrings.size();

mIndexes.put(s, index);

mStrings.add(s);

p.writeInt(index);

}

}

public void finishAndUninstall() {

// Uninstall first, so that writeStringList() uses the native writeString.

mParcel.setReadWriteHelper(null);

final int poolPosition = mParcel.dataPosition();

mParcel.writeStringList(mStrings);

mParcel.setDataPosition(mStartPos);

mParcel.writeInt(poolPosition);

mParcel.setDataPosition(mParcel.dataSize());

if (DEBUG) {

Log.i(TAG, "Wrote " + mStrings.size() + " strings");

}

}

}

注意因为这里设置了setReadWriteHelper。在Package对象中调用writeToParcel全是writeString,此时调用的是WriteHelper的writeString。所有的数据都将写入到mStrings中。此时调用finishAndUninstall,把list中所有的String统一写入到Parcel中。

3.调用了Parcel的marshall方法,核心方法如下

static jbyteArray android_os_Parcel_marshall(JNIEnv* env, jclass clazz, jlong nativePtr)

{

Parcel* parcel = reinterpret_cast(nativePtr);

if (parcel == NULL) {

return NULL;

}

...

jbyteArray ret = env->NewByteArray(parcel->dataSize());

if (ret != NULL)

{

jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(ret, 0);

if (array != NULL)

{

memcpy(array, parcel->data(), parcel->dataSize());

env->ReleasePrimitiveArrayCritical(ret, array, 0);

}

}

return ret;

}

就是在jni中通过memcpy,把数据全部拷贝到java层并返回。

同理,我们来看看PackageParser是怎么读取数据的。

PackageParser getCachedResult

private Package getCachedResult(File packageFile, int flags) {

if (mCacheDir == null) {

return null;

}

final String cacheKey = getCacheKey(packageFile, flags);

final File cacheFile = new File(mCacheDir, cacheKey);

try {

if (!isCacheUpToDate(packageFile, cacheFile)) {

return null;

}

final byte[] bytes = IoUtils.readFileAsByteArray(cacheFile.getAbsolutePath());

Package p = fromCacheEntry(bytes);

if (mCallback != null) {

String[] overlayApks = mCallback.getOverlayApks(p.packageName);

if (overlayApks != null && overlayApks.length > 0) {

for (String overlayApk : overlayApks) {

// If a static RRO is updated, return null.

if (!isCacheUpToDate(new File(overlayApk), cacheFile)) {

return null;

}

}

}

}

return p;

} catch (Throwable e) {

...

cacheFile.delete();

return null;

}

}

1.能看到同样是构造了/data/system/package_cache/包名_0文件

2.isCacheUpToDate 判断当前的缓存文件是否过期了。

private static boolean isCacheUpToDate(File packageFile, File cacheFile) {

try {

final StructStat pkg = android.system.Os.stat(packageFile.getAbsolutePath());

final StructStat cache = android.system.Os.stat(cacheFile.getAbsolutePath());

return pkg.st_mtime < cache.st_mtime;

} catch (ErrnoException ee) {

if (ee.errno != OsConstants.ENOENT) {

Slog.w("Error while stating package cache : ", ee);

}

return false;

}

}

很简单,就是检查apk本身安装的时间,和缓存本身的时间哪个更加老。如果缓存更新。,说明当前的缓存有效。更老了,说明apk版本安装更新了,需要重新读取了

3.readFileAsByteArray 读取文件中所有的内容,并调用fromCacheEntry方法逆向转化为package对象。接着检查overlayapk的的时效和当前的缓存相比较。

总结

到这里就把PMS的启动原理大体上和大家聊完了。老规矩,先上时序图

2afddb959b67

PMS启动流程.png

在PMS初始化中做了如下的事情:

1.实例化Settings PMS的配置对象,内含packages.list和packages.xml.

1.1.packages.list记录了Android系统中安装的应用列表以及对应的userID

2.2.packages.xml记录了每一个安装包的名字,代码路径,so路径,签名等信息

2.对整个Android系统进行分区,扫描分区中所有的尝试提供的服务以及App安装的包,分为如下区域

2.1./data/system/package_cache/ 所有的包扫描的结果都会缓存到这里,每一个结果的缓存为/data/system/package_cache/包名_0

2.2./vendor/overlay

2.3./product/overlay 第2和第3点都是第三方厂商提供的资源复写目录

2.4./system/framework Android系统framework层内置提供的java的核心jar包,odex等

2.5./system/priv-app,/system/app ,这里面提供了Android系统或者厂商默认的系统应用

2.6./vendor/priv-app,/vendor/app 这是交给硬件厂商的目录,允许他们内置内置一些系统应用服务。我之前常说的hal层,就是在这个vendor目录安装提供的。

2.7./odm/priv-app,/odm/app 可以看作是vendor目录的一种延伸。

2.8./oem/app ,/product/priv-app,/product/app

2.9./data/app,/data/app-private.所有的应用apk对象都会拷贝到/data/app

2.10.通过Installd. fixupAppData 在/data/user/用户ID/包名/下构造每一个安装后真正的数据保存路径,并把这个目录链接到/data/data/包名中

2.11.Installd.createAppData 为每一个包名下创建/data/user/用户ID/包名/cache 或/data/user/用户ID/包名/code_cache.用于缓存安装过程中编译优化好的文件,如art,odex,vdex,dex等等

2.12. prepareAppDataContentsLeafLIF 会调用Installd. linkNativeLibraryDirectory创建每一个应用so库的扫描目录/data/user/用户id/包名/lib。并把这个目录链接到/data/data/包名/lib中

2.13.还会调用/system/bin/profman 程序生成每一个应用包名的/data/data/包名/包名.prof文件用于加速后面的dex2oat的编译优化。

3.在扫描的过程中,就以我们开发安装的应用微粒子。会从/data/app目录下取出apk文件,拿到其中的AndroidManifest.xml,解析里面所有的组件和标签并保存到package对象。并把这个package对象通过Parcel进行序列化,保存到data/system/package_cache/包名_0中。

下一次就会优先从这个缓存中获取,直到出现缓存的文件的修改日期时间比/data/app中保存的apk文件修改时间早,说明apk发生了版本更新,才会重新从/data/app中读取。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值