Android磁盘统计服务:StorageStatsService,美团Android研发岗二面

@Override
public StorageStats queryStatsForPackage(String volumeUuid, String packageName, int userId,
String callingPackage) {
if (userId != UserHandle.getCallingUserId()) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS, TAG);
}

// 获取对应包名的ApplicationInfo
final ApplicationInfo appInfo;
try {
appInfo = mPackage.getApplicationInfoAsUser(packageName,
PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
} catch (NameNotFoundException e) {
throw new ParcelableException(e);
}

final boolean callerHasStatsPermission;
// 权限检查
if (Binder.getCallingUid() == appInfo.uid) {
// No permissions required when asking about themselves. We still check since it is
// needed later on but don’t throw if caller doesn’t have the permission.
callerHasStatsPermission = checkStatsPermission(
Binder.getCallingUid(), callingPackage, false) == null;
} else {
enforceStatsPermission(Binder.getCallingUid(), callingPackage);
callerHasStatsPermission = true;
}

// 如果uid仅对应一个包名,则直接调用queryStatsForUid
if (defeatNullable(mPackage.getPackagesForUid(appInfo.uid)).length == 1) {
// Only one package inside UID means we can fast-path
return queryStatsForUid(volumeUuid, appInfo.uid, callingPackage);
} else {
// 如果uid对应多个包名(通过sharedUserId),需要mInstaller.getAppSize计算
// Multiple packages means we need to go manual
final int appId = UserHandle.getUserId(appInfo.uid);
final String[] packageNames = new String[] { packageName };
final long[] ceDataInodes = new long[1];
String[] codePaths = new String[0];

// 系统镜像中的系统应用codePath不计入
if (appInfo.isSystemApp() && !appInfo.isUpdatedSystemApp()) {
// We don’t count code baked into system image
} else {
codePaths = ArrayUtils.appendElement(String.class, codePaths,
appInfo.getCodePath());
}

final PackageStats stats = new PackageStats(TAG);
try {
mInstaller.getAppSize(volumeUuid, packageNames, userId, 0,
appId, ceDataInodes, codePaths, stats);
} catch (InstallerException e) {
throw new ParcelableException(new IOException(e.getMessage()));
}
if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) {
forEachStorageStatsAugmenter((storageStatsAugmenter) -> {
storageStatsAugmenter.augmentStatsForPackage(stats,
packageName, userId, callerHasStatsPermission);
}, “queryStatsForPackage”);
}
// PackageStats转换为StorageStats
return translate(stats);
}
}

queryStatsForUid

可以看到queryStatsForUid逻辑大体上与queryStatsForPackage是一致的 最终都是使用mInstaller.getAppSize获取应用的各项磁盘占用。

@Override
public StorageStats queryStatsForUid(String volumeUuid, int uid, String callingPackage) {
final int userId = UserHandle.getUserId(uid);
final int appId = UserHandle.getAppId(uid);

if (userId != UserHandle.getCallingUserId()) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS, TAG);
}

final boolean callerHasStatsPermission;
if (Binder.getCallingUid() == uid) {
// No permissions required when asking about themselves. We still check since it is
// needed later on but don’t throw if caller doesn’t have the permission.
callerHasStatsPermission = checkStatsPermission(
Binder.getCallingUid(), callingPackage, false) == null;
} else {
enforceStatsPermission(Binder.getCallingUid(), callingPackage);
callerHasStatsPermission = true;
}

final String[] packageNames = defeatNullable(mPackage.getPackagesForUid(uid));
final long[] ceDataInodes = new long[packageNames.length];
String[] codePaths = new String[0];

for (int i = 0; i < packageNames.length; i++) {
try {
final ApplicationInfo appInfo = mPackage.getApplicationInfoAsUser(packageNames[i],
PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
if (appInfo.isSystemApp() && !appInfo.isUpdatedSystemApp()) {
// We don’t count code baked into system image
} else {
codePaths = ArrayUtils.appendElement(String.class, codePaths,
appInfo.getCodePath());
}
} catch (NameNotFoundException e) {
throw new ParcelableException(e);
}
}

final PackageStats stats = new PackageStats(TAG);
try {
mInstaller.getAppSize(volumeUuid, packageNames, userId, getDefaultFlags(),
appId, ceDataInodes, codePaths, stats);

if (SystemProperties.getBoolean(PROP_VERIFY_STORAGE, false)) {
final PackageStats manualStats = new PackageStats(TAG);
mInstaller.getAppSize(volumeUuid, packageNames, userId, 0,
appId, ceDataInodes, codePaths, manualStats);
checkEquals("UID " + uid, manualStats, stats);
}
} catch (InstallerException e) {
throw new ParcelableException(new IOException(e.getMessage()));
}

if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) {
forEachStorageStatsAugmenter((storageStatsAugmenter) -> {
storageStatsAugmenter.augmentStatsForUid(stats, uid, callerHasStatsPermission);
}, “queryStatsForUid”);
}
return translate(stats);
}

Installer

Installer调用mInstalld, 他的最终实现是在InstalldNativeService.cpp

public void getAppSize(String uuid, String[] packageNames, int userId, int flags, int appId,
long[] ceDataInodes, String[] codePaths, PackageStats stats)
throws InstallerException {
if (!checkBeforeRemote()) return;
if (codePaths != null) {
for (String codePath : codePaths) {
BlockGuard.getVmPolicy().onPathAccess(codePath);
}
}
try {
final long[] res = mInstalld.getAppSize(uuid, packageNames, userId, flags,
appId, ceDataInodes, codePaths);
stats.codeSize += res[0];
stats.dataSize += res[1];
stats.cacheSize += res[2];
stats.externalCodeSize += res[3];
stats.externalDataSize += res[4];
stats.externalCacheSize += res[5];
} catch (Exception e) {
throw InstallerException.from(e);
}
}

应用内存占用包括dataBytes和codeBytes

dataBytes包含的主要路径如下:

  • Context#getDataDir()----------------------------------------/data/user/0/
  • Context#getCacheDir()------------------------------------/data/user/0//cache
  • Context#getCodeCacheDir()---------------------------/data/user/0//code_cache
  • Context#getExternalFilesDir(String)-------------/Android/data/\app>/files
  • Context#getExternalCacheDir()-------------------/Android/data//cache
  • Context#getExternalMediaDirs()----------------------/Android/media/

codeBytes包含的主要路径如下:

  • Context#getObbDir()--------------/storage/emulated/0/Android/obb/
cePath和dePath

在启用了 FBE 的设备上,每位用户均有两个可供应用使用的存储位置:

  • 凭据加密 (CE) 存储空间:这是默认存储位置,只有在用户解锁设备后才可用。
  • 设备加密 (DE) 存储空间:在直接启动模式期间以及用户解锁设备后均可用。
InstalldNativeService

getAppSize计算

  • 首先将obb计入extStats.codeSize,calculate_tree_size函数最终是作累加处理
  • 如果支持quota:
  1. 累加codePath,计入stats.codeSize
  2. 如果支持quota,则用quota计算
  • 不支持quota:
  1. 累加codePath,计入stats.codeSize
  2. 计算cePath 和 dePath计入stats.dataSize
  3. profiles计入stats.dataSize
  4. external路径计入extStats.dataSize
  5. dalvik_cache_path计入stats.codeSize

各模块对应的具体路径请查看源码:

frameworks/native/cmds/installd/utils.cpp

binder::Status InstalldNativeService::getAppSize(const std::unique_ptrstd::string& uuid,
const std::vectorstd::string& packageNames, int32_t userId, int32_t flags,
int32_t appId, const std::vector<int64_t>& ceDataInodes,
const std::vectorstd::string& codePaths, std::vector<int64_t>* _aidl_return) {
ENFORCE_UID(AID_SYSTEM);
CHECK_ARGUMENT_UUID(uuid);
for (const auto& packageName : packageNames) {
CHECK_ARGUMENT_PACKAGE_NAME(packageName);
}
for (const auto& codePath : codePaths) {
CHECK_ARGUMENT_PATH(codePath);
}
// NOTE: Locking is relaxed on this method, since it’s limited to
// read-only measurements without mutation.

// When modifying this logic, always verify using tests:
// runtest -x frameworks/base/services/tests/servicestests/src/com/android/server/pm/InstallerTest.java -m testGetAppSize

#if MEASURE_DEBUG
LOG(INFO) << "Measuring user " << userId << " app " << appId;
#endif

// Here’s a summary of the common storage locations across the platform,
// and how they’re each tagged:
//
// /data/app/com.example UID system
// /data/app/com.example/oat UID system
// /data/user/0/com.example UID u0_a10 GID u0_a10
// /data/user/0/com.example/cache UID u0_a10 GID u0_a10_cache
// /data/media/0/foo.txt UID u0_media_rw
// /data/media/0/bar.jpg UID u0_media_rw GID u0_media_image
// /data/media/0/Android/data/com.example UID u0_media_rw GID u0_a10_ext
// /data/media/0/Android/data/com.example/cache UID u0_media_rw GID u0_a10_ext_cache

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

最后

分享一份工作1到5年以上的Android程序员架构进阶学习路线体系,希望能对那些还在从事Android开发却还不知道如何去提升自己的,还处于迷茫的朋友!

  • 阿里P7级Android架构师技术脑图;查漏补缺,体系化深入学习提升(详细点击我的GitHub领取)

  • **全套体系化高级架构视频;**七大主流技术模块,视频+源码+笔记

需要架构学习笔记导图的可以点击我的GitHub领取

群内有许多技术大牛,高手如云,有任何问题,欢迎广大网友一起来交流,群内还不定期免费分享高阶Android学习视频资料和面试资料包~

[外链图片转存中…(img-ADPtGSFq-1711372632114)]

  • **全套体系化高级架构视频;**七大主流技术模块,视频+源码+笔记

[外链图片转存中…(img-FsoHfmU3-1711372632114)]

需要架构学习笔记导图的可以点击我的GitHub领取

群内有许多技术大牛,高手如云,有任何问题,欢迎广大网友一起来交流,群内还不定期免费分享高阶Android学习视频资料和面试资料包~

  • 21
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值