Android应用内存管理机制

引言

有个客户统计了APPs 统计的数据(我司的产品是Android 机顶盒),跟实际每个APK 统计的数据对不上,需要我们给出解释。客户的问题总结起来有三个:

  • App总内存和所有Apk内存之和不对应:是否有系统APK是隐藏没有出现了Apps 列表的. 如果有,是否有方法/命令可以查询到盒子上安装的所有的APK?
  • Data 和 Cache不对应:手动清除了APK 的data和cache之后,APK 显示的storage used 数据减少与data 减少不匹配。
  • 低内存的情况:除此之外,针对这个cache 不断增加, 内存一直减少的情况, Android有没有做什么应对机制

为了查这个问题,就需要去看源码:
Note: 以下所有源码基于Android 10.0,UI部分源码和手机有不同。如果只对问题答案感兴趣,而不关系具体源码分析过程,可直接跳到问题总结。

源码解析

本文中涉及到的代码有:
packages\apps\TvSettings\Settings\src\com\android\tv\settings\device\apps\AllAppsFragment.java
packages\apps\TvSettings\Settings\src\com\android\tv\settings\device\storage\StorageFragment.java
packages\apps\TvSettings\Settings\src\com\android\tv\settings\device\apps\AppManagementFragment.java
packages\apps\TvSettings\Settings\src\com\android\tv\settings\device\apps\AppStoragePreference.java
packages\apps\TvSettings\Settings\src\com\android\tv\settings\device\apps\AllAppsFragment.java
frameworks\base\packages\SettingsLib\src\com\android\settingslib\deviceinfo\StorageMeasurement.java
frameworks\base\packages\SettingsLib\src\com\android\settingslib\applications\ApplicationsState.java
frameworks\base\core\java\android\app\usage\StorageStatsManager.java
frameworks\base\services\usage\java\com\android\server\usage\StorageStatsService.java
frameworks\base\services\core\java\com\android\server\am\ActivityManagerService.java
frameworks\base\services\core\java\com\android\server\pm\PackageManagerService.java
frameworks\base\services\core\java\com\android\server\storage\DeviceStorageMonitorService.java
frameworks\base\services\core\java\com\android\server\pm\Installer.java
frameworks\native\cmds\installd\InstalldNativeService.cpp

Apps列表的显示

Apps 列表对应的是AllAppsFragment,三种不同的APP分别是 InstalledPreferenceGroup,代表已安装的app;DisabledPreferenceGroup,代表被disable的app;OtherPreferenceGroup,除以上两者之外的app。
获取方式是用过ApplicationsState.AppFilter来筛选不同的App,其中OtherPreferenceGroup的规则是:
图片

很显然,不属于是 InstalledPreferenceGroup和DisabledPreferenceGroup的App都被划分到OtherPreferenceGroup中了,因此并不存在有三种App之外的App,即所有App都显示了。

Internal shared storage计算

Internal shared storage 对应的java类是StorageFragment,在StorageFragment中,Apps数据对应的设置语句是updateDetails中的:
在这里插入图片描述

Details是StorageFragment通过StorageMeasurement计算内存,StorageMeasurement调用了内存的MeasureTask这个AsyncTask,异步执行measureExactStorage方法得到的:
measureExactStorage方法中先调用StorageStatsManager#queryExternalStatsForUser来获得music,movies,pictures等文件的大小,这里和本文目的无关,我没有深究。
在这里插入图片描述
接下来是调用StorageStatsManager#queryStatsForUser获取到StorageStats,计算得出details.appsSize,即最终显示的Apps大小。
在这里插入图片描述
接下来看stats是怎么得到的:
queryStatsForUser的实现在StorageStatsManager对应的系统服务StorageStatsService的同名方法中。其中通过Installer#getUserSize方法获取到PackageStats。
在这里插入图片描述
translate方法的实现如下:
在这里插入图片描述
因此,接下来要看PackageStats是如何计算出来的,Installer类是IInstalld类的代理。getUserSize的实现中就是调用了IInstalld的getUserSize方法。IInstalld的实现是native服务InstalldNativeService。在InstalldNativeService的getUserSize中,就是最终APPS内存计算的终点。
getUserSize中有两种实现方式:
先尝试通过Quota(Quota参考资料)计算,如果不支持Quota,则手动遍历计算。我们的盒子会使用Quota,但是这两者的计算结果是相同的,所以我们可以看手动遍历计算的实现。
手动遍历的逻辑大同小异,以codeSize为例,
在这里插入图片描述
计算/data/app/的总大小,将值赋给codeSize。
相应的,每个属性和分别对应的目录为:
codeSize = /data/app/ + /data/misc/profiles/ref/ + /data/dalvik-cache
dataSize = data/data/ + /data/user_de/0/ + /data/misc/profiles/cur/0
cacheSize = data/data/ 和/data/user_de/0/ 下的cache 和code_cache
extStats.codeSize = /data/media/obb/
extStats.dataSize = /sdcard/Android/data/ + /sdcard/Android/media/
extStats.cacheSize = /sdcard/Android/data/ 下的cache 和code_cache

至此,可以得出结论,Internal shared storage 显示的是codeSize 与dataSize ,即 /data/app/ ,/data/misc/profiles/ref/,data/data/,/data/user_de/0,/data/misc/profiles/cur/0,/data/media/obb/ ,/sdcard/Android/data/ , /sdcard/Android/media/ 这几个目录下的内存。

Storage used的计算

App详情页对应的java类是AppManagementFragment,通过ApplicationsState#getEntry获取App内存信息,Storage used项对应的java类是 AppStoragePreference,对应的内存大小是Entry#sizeStr
在这里插入图片描述
最终来自于StorageStatsManager#queryStatsForPackage,再通过回调onGetStatsCompleted返回,
在这里插入图片描述
先看queryStatsForPackage方法,这个方法的最终实现是在StorageStatsService类的queryStatsForPackage方法中,经过一系列的权限检查后,最终调用了Installer的getAppSize方法。
在这里插入图片描述
同样的,getAppSize方法最终实现在InstalldNativeService::getAppSize。getAppSize代码结构和前面提到的getUserSize函数相似,因此我省去了代码分析直接说结果,
codeSize = /sdcard/Android/obb/packageName + /data/dalvik-cache/gid
dataSize = /data/data/packageName + /data/user_de/0/packageName +
cacheSize = /data/data/packageName 和/data/user_de/0/packageName 下的cache 和code_cache
extStats.codeSize = /data/media/obb/packageName
extStats.dataSize = /sdcard/Android/data/packageName + /sdcard/Android/media/packageName
extStats.cacheSize = /sdcard/Android/data/packageName 下的cache 和code_cache

getAppSize和getUserSize的区别只在于getUserSize直接读取了所有app的内存,getAppSize读取了特定app的内存。但是涉及的内存区域是基本一致的,所以区别至少不应该有客户客户测试出来的这么大。
所以区别来自于onGetStatsCompleted回调中:onGetStatsCompleted中size通过getTotalInternalSize方法计算得出:
在这里插入图片描述
在这里插入图片描述
而Internal shared storage 显示的是codeSize 与dataSize,所以可以得出结论:

Internal shared storage下App总内存和所有Apk内存之和不对应的原因是,Apk内存计算时减去了cache。

Clear data的计算

Clear data 按钮点击后,会调用AppManagementFragment的clearData方法
clearData有两个逻辑:
1:通过spaceManagementActivityName启动app自定义的方式清除data,
2:默认的方式:调用ActivityManager#clearApplicationUserData
spaceManagementActivityName是App在Manifest中可修改的属性,,clearData方法会调用App设置的Activity,让App执行自己的清除逻辑。默认此值为空。
一般的App走的是第二条逻辑,
clearApplicationUserData方法会会先停止App的运行,再继续调用PackageManagerService的clearApplicationUserData方法,并且在清除了内存之后删除此前申请的权限和显示的notification。
在这里插入图片描述
PackageManagerService#clearApplicationUserData中用异步的方式,调用clearApplicationUserDataLIF方法。在这里PackageManagerService会清除掉App的一些状态,然后调用clearAppDataLIF方法,最后调用Installer的clearAppData方法,实现在InstalldNativeService::clearAppData,
在这里插入图片描述
代码比较长,简单来说就是根据flag,删除掉App各个data目录下的特定内容,以下是一部分代码:
在这里插入图片描述
由clearAppDataLIF的参数可知,clear data和之前计算dataSize的作用目录相似,最终会清除掉data/data/packageName, /data/user_de/0/packageName ,/sdcard/Android/data/packageName 等目录下的内容。

Clear cache的计算

Clear cache的逻辑和Clear data相似,点击事件会调用AppManagementFragment的clearCache方法,然后调用PackageManagerService的deleteApplicationCacheFiles方法,至deleteApplicationCacheFilesAsUser方法,在这个方法中进行权限检查之后,同样异步调用clearAppDataLIF方法。
在这里插入图片描述
因此clear cache和之前计算cacheSize的作用目录同样相似。

低内存的情况

Android通过framework服务DeviceStorageMonitorService在后台监测内存变化,
DeviceStorageMonitorService会每隔1min调用一次check方法检查内存情况,如果内存过低,就通过广播和通知的形式进行提示。
在这里插入图片描述
首先,DeviceStorageMonitorService获取内存已满的标准fullBytes和内存过低的标准lowBytes,其中fullBytes的默认值是1MB,lowBytes的默认值是取总内存 5% 和 500MB的最小值,对于我们的盒子,按4GB来算,lowBytes应该为200MB,
获取到fullBytes和lowBytes后,DeviceStorageMonitorService会先检查当前可用内存是否大于 1.5倍的lowBytes,如果小于,则调用PackageManagerService的freeStorage方法,尝试清理cache到可用内存回到2倍lowBytes,这个方法逻辑比较复杂,会按优先级轮流尝试清理cache,最后也会调到InstalldNativeService清理cache。
清理完cache后,重新计算可用内存。
在这里插入图片描述
计算出newLevel之后,根据是否内存满了或不足了,或者原本不足了,但现在充足了,来发出或取消通知和广播
在这里插入图片描述

问题总结

原始问题的总结

App总内存和所有Apk内存之和不对应

是因为Internal shared storage下App总内存包括cache,而Apk内存计算时减去了cache。

手动清除了APK 的data和cache之后,APK 显示的storage used 数据减少与data 减少不匹配。

是因为storage used 等于codeSize + dataSize - cacheSize,其中dataSize包含cacheSize,因此clear cache不会影响storage used,clear data后storage used减少量不是dataSize,而是dataSize - cacheSize。

针对这个cache 不断增加,内存一直减少的情况, 安卓是不是做了什么应对机制?

Android有一个系统服务在后台不断检查内存可用空间,当内存达到临界点后,就去尝试清理各种缓存来使内存重新达到安全区。如果清理缓存仍然不能达到目的的话,就会通过广播和通知的方式来提醒系统和用户尝试其他手段来清理内存。

对Android的应用内存管理机制的总结:

Android系统管理应用内存的手段十分简单,没有基于Android机制,而是基于Linux机制,直接操作特定目录来达到目的。
进行内存的统计和清理等操作的直接类是InstalldNativeService,它是一个native服务,通过SystemServer与系统服务Installer建立绑定关系,Installer提供了细颗粒的方法,AMS,PMS等系统服务通过Installer这个代理来调用InstalldNativeService的方法,再向应用层和其他系统服务提供封装的接口,并在其中进行必要的权限检查。而应用层的App,如TvSettings,则通过AMS,PMS等服务提供的API,来以package或user为单位执行需要的操作。

一个遗留的问题

这篇博客发出后,coco0912私信我,提出了一个问题:
在这里插入图片描述
coco0912提出,显示的大小要大于这几个目录的大小。而从之前的分析我们可以知道,android计算内存大小现在是通过Quota,那么这是不是说明,Quota和统计目录这两种方式计算得到的结果是有差异的呢?
coco0912对Quota机制进行了研究,成果在Android11 app存储大小显示源码解析

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值