Android 分区和内存监控
Android 分区和内存监控
Andorid之所以是分区,是因为各自有对应的功能和用途的考量,可以进行单独读写和格式化。
Android 设备包含两类分区:
- 一类是启动分区,对启动过程至关重要。
- 一类是用户分区,用于存储与启动无关的信息。
启动分区
boot
分区一般的嵌入式Linux的设备中.bootloader,内核,根文件系统被分为三个不同分区。在Android做得比较复杂,从这个手机分区和来看,这里boot分区是把内核和[ramdisk file](https://www.zhihu.com/search?q=ramdisk file&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra={"sourceType"%3A"answer"%2C"sourceId"%3A196267472})的根文件系统打包在一起了,是编译生成boot.img来烧录的。
如果没有这个分区,手机通常无法启动到安卓系统。只有必要的时候,才去通过Recovery软件擦除(format)这个分区,一旦擦除,设备只有再重新安装一个新的boot分区,可以通过安装一个包含boot分区的ROM来实现,否则无法启动安卓系统。
标准分区
system
分区。此分区包含 Android 框架。这里是挂载到/system目录下的分区。这里有 /system/bin 和 /system/sbin 保存很多系统命令。它是由编译出来的system.img来烧入。
相当于你电脑的C盘,用来放系统。这个分区基本包含了整个安卓操作系统,除了内核(kernel)和ramdisk。包括安卓用户界面、和所有预装的系统应用程序。擦除这个分区,会删除整个安卓系统。你可以通过进入Recovery程序或者bootloader程序中,安装一个新ROM,也就是新安卓系统。
odm
分区此分区包含原始设计制造商 (ODM) 对系统芯片 (SoC) 供应商板级支持包 (BSP) 的自定义设置。利用此类自定义设置,ODM 可以替换或自定义 SoC 组件,并在硬件抽象层 (HAL) 上为板级组件、守护程序和 ODM 特定的功能实现内核模块。此分区是可选的;通常情况下,它用于存储自定义设置,以便设备可以针对多个硬件 SKU 使用单个供应商映像。如需了解详情,请参阅 ODM 分区。
recovery
分区。recovery 分区即恢复分区,在正常分区被破坏后,仍可以进入这一分区进行备份和恢复.我的理解是这个分区保存一个简单的OS或底层软件,在Android的内核被破坏后可以用bootloader从这个分区引导进行操作。
这个分区可以认为是一个boot分区的替代品,可以是你的手机进入Recovery程序,进行高级恢复或安卓系统维护工作。
cache
分区。它将挂载到 /cache 目录下。这个分区是安卓系统缓存区,保存系统最常访问的数据和应用程序。擦除这个分区,不会影响个人数据,只是删除了这个分区中已经保存的缓存内容,缓存内容会在后续手机使用过程中重新自动生成。
userdata
分区此分区包含用户安装的应用和数据,包括自定义数据。它将挂载到 /data 目录下, 它是由编译出来的userdata.img来烧入。
这个分区也叫用户数据区,包含了用户的数据:联系人、短信、设置、用户安装的程序。擦除这个分区,本质上等同于手机恢复出厂设置,也就是手机系统第一次启动时的状态,或者是最后一次安装官方或第三方ROM后的状态。在Recovery程序中进行的“data/factory reset ”操作就是在擦除这个分区。
vendor
分区此分区包含所有无法分发给 AOSP 的二进制文件。如果设备不包含专有信息,则可以忽略此分区。
radio
分区此分区包含无线装置映像,只有包含无线装置且在专用分区中存储无线装置专用软件的设备才需要此分区。
查看分区大小
cat /proc/partitions
major minor #blocks name
179 0 153672 mmcblk0
179 1 104 mmcblk0p1
179 2 124 mmcblk0p2
179 3 140 mmcblk0p3
179 4 208 mmcblk0p4
179 5 892 mmcblk0p5
259 4 2040 mmcblk0p12
259 5 124 mmcblk0p13
259 6 30700 mmcblk0p14
259 7 4060 mmcblk0p15
259 8 8900 mmcblk0p16
259 9 8100 mmcblk0p17
259 10 13186048 mmcblk0p18
179 16 406 mmcblk0boot1
179 8 496 mmcblk0boot0
第一个mmcblk0为emmc的块设备,大小为15388672 KB。
emmc : Embedded Multi Media Card : 内嵌式存储器标准规格。
是MMC协会订立、主要针对手机或平板电脑等产品的内嵌式存储器标准规格。NAND Flash+闪存控制芯片+标准接口封装。内置存储器的基础上,又额外加了一个控制芯片,最后再以统一的方式封装,并预留一个标准接口,以便手机客户拿来直接使用。
ls -l /dev/block/platform/soc/by-name
lrwxrwxrwx root root 2019-03-13 16:38 baseparam -> /dev/block/mmcblk0p5
lrwxrwxrwx root root 2019-03-13 16:38 bootargs -> /dev/block/mmcblk0p2
lrwxrwxrwx root root 2019-03-13 16:38 cache -> /dev/block/mmcblk0p17
lrwxrwxrwx root root 2019-03-13 16:38 deviceinfo -> /dev/block/mmcblk0p4
lrwxrwxrwx root root 2019-03-13 16:38 fastboot -> /dev/block/mmcblk0p1
lrwxrwxrwx root root 2019-03-13 16:38 fastplay -> /dev/block/mmcblk0p9
lrwxrwxrwx root root 2019-03-13 16:38 fastplaybak -> /dev/block/mmcblk0p10
lrwxrwxrwx root root 2019-03-13 16:38 kernel -> /dev/block/mmcblk0p11
lrwxrwxrwx root root 2019-03-13 16:38 logo -> /dev/block/mmcblk0p7
lrwxrwxrwx root root 2019-03-13 16:38 logobak -> /dev/block/mmcblk0p8
lrwxrwxrwx root root 2019-03-13 16:38 misc -> /dev/block/mmcblk0p12
lrwxrwxrwx root root 2019-03-13 16:38 pqparam -> /dev/block/mmcblk0p6
lrwxrwxrwx root root 2019-03-13 16:38 qbboot -> /dev/block/mmcblk0p13
lrwxrwxrwx root root 2019-03-13 16:38 qbdata -> /dev/block/mmcblk0p14
lrwxrwxrwx root root 2019-03-13 16:38 recovery -> /dev/block/mmcblk0p3
lrwxrwxrwx root root 2019-03-13 16:38 system -> /dev/block/mmcblk0p16
lrwxrwxrwx root root 2019-03-13 16:38 trustedcore -> /dev/block/mmcblk0p15
lrwxrwxrwx root root 2019-03-13 16:38 userdata -> /dev/block/mmcblk0p18
可以看到 UserData 对应的分区是 mmcblk0p18 大小为 13186048KB = 12877 M = 12.5 G
在android中,device里面的BroadConfig.mk
中可以修改userdata的大小,注意这里面的单位为字节。
TARGET_USERIMAGES_USE_EXT4 := true
BOARD_SYSTEMIMAGE_PARTITION_SIZE := 838860800
BOARD_USERDATAIMAGE_PARTITION_SIZE := 135013152
BOARD_CACHEIMAGE_PARTITION_SIZE := 838860800
BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE := ext4
BOARD_FLASH_BLOCK_SIZE := 4096
BOARD_HAVE_BLUETOOTH := true
StorageManager
在Android系统中,常用的存储介质是Nand Flash。系统的二进制镜像、Android的文件系统等通常都保存在Nand Flash 中。
Nand-flash存储器是flash存储器的一种,Nand-flash存储器具有容量较大,改写速度快等优点,适用于大量数据的存储,因而在业界得到了越来越广泛的应用,如嵌入式产品中包括数码相机、MP3随身听等。
StorageManager用来管理外部存储上的数据安全,
android.os.storage.StorageManager.getSystemService(Contxt.STORAGE_SERVICE)
们 可以通过这个服务获取Android设备上的所有存储设备。 系统提供了 StorageManager 类,它有一个方法叫getVolumeList(),这个方法的返回值是一个StorageVolume数组,StorageVolume类中封装了挂载路径,挂载状态,以及是否可以移除等信息。
DeviceStorageMonitorService
DeviceStorageMonitorService和DeviceStorageManagerService是一个东西,只是在5.0以后,名字改为了DeviceStorageMonitorService。
简介
Device storage monitor module is composed of device monitor service (Google default)The purposes of device storage monitor service are monitoring device storage status and handling low storage conditions.
服务的添加
// Create the system service manager.
mSystemServiceManager = new SystemServiceManager(mSystemContext);
...
mSystemServiceManager.startService(DeviceStorageMonitorService.class);
通过SystemServiceManager的startService方法启动了DSMS,看一下这个startService方法做了什么:
public SystemService startService(String className) {
final Class<SystemService> serviceClass;
try {
serviceClass = (Class<SystemService>)Class.forName(className);
}
...
return startService(serviceClass);
}
public <T extends SystemService> T startService(Class<T> serviceClass) {
...
final T service;
try {
Constructor<T> constructor = serviceClass.getConstructor(Context.class);
service = constructor.newInstance
...
// 注册到ServiceManager中
mServices.add(service);
try {
service.onStart();//启动服务
}
...
}
其实就是用过反射获取实例,然后将Service注册添加到ServiceManager中, 最后调用了DSMS的onStart方法,那接下来就看看DSMS的构造方法 以及 onStart方法。
public DeviceStorageMonitorService(Context context) {
super(context);
// 初始化HandlerThread后台线程,做check()
mHandlerThread = new HandlerThread(TAG, android.os.Process.THREAD_PRIORITY_BACKGROUND);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_CHECK:
check();
return;
}
}
};
}
@Override
public void onStart() {
final Context context = getContext();
//获取通知服务,发送通知
mNotifManager = context.getSystemService(NotificationManager.class);
//cacheFile通知
mCacheFileDeletedObserver = new CacheFileDeletedObserver();
mCacheFileDeletedObserver.startWatching();
// Ensure that the notification channel is set up
PackageManager packageManager = context.getPackageManager();
//addService到BinderService,也添加到LocalService
publishBinderService(SERVICE, mRemoteService);
publishLocalService(DeviceStorageMonitorInternal.class, mLocalService);
//开始check()
// Kick off pass to examine storage state
mHandler.removeMessages(MSG_CHECK);
mHandler.obtainMessage(MSG_CHECK).sendToTarget();
}
DeviceStorageMonitorInternal提供的接口
//内部服务提供check():检测接口
//isMemoryLow:是否LowStorage
//getMemoryLowThreshold:data的低存储值
private final DeviceStorageMonitorInternal mLocalService = new DeviceStorageMonitorInternal() {
@Override
//发送Msg触发Handler,check()
public void checkMemory() {
// Kick off pass to examine storage state
mHandler.removeMessages(MSG_CHECK);
mHandler.obtainMessage(MSG_CHECK).sendToTarget();
}
//data分区可使用空间<500M
@Override
public boolean isMemoryLow() {
return Environment.getDataDirectory().getUsableSpace() < getMemoryLowThreshold();
}
//500M,具体项目在看
@Override
public long getMemoryLowThreshold() {
return getContext().getSystemService(StorageManager.class)
.getStorageLowBytes(Environment.getDataDirectory());
}
};
check() /data分区
@WorkerThread
private void check() {
final StorageManager storage = getContext().getSystemService(StorageManager.class);
final int seq = mSeq.get();
//本地打印只有/data目录
// Check every mounted private volume to see if they're low on space
for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
final File file = vol.getPath();
final long fullBytes = storage.getStorageFullBytes(file);
final long lowBytes = storage.getStorageLowBytes(file);//500M
// Automatically trim cached data when nearing the low threshold;
// when it's within 150% of the threshold, we try trimming usage
// back to 200% of the threshold.
if (file.getUsableSpace() < (lowBytes * 3) / 2) {
final PackageManagerService pms = (PackageManagerService) ServiceManager
.getService("package");
//lowBytes的1.5倍容量时触发freeStorage
try {
pms.freeStorage(vol.getFsUuid(), lowBytes * 2, 0);
} catch (IOException e) {
Slog.w(TAG, e);
}
}
// Send relevant broadcasts and show notifications based on any
// recently noticed state transitions.
final UUID uuid = StorageManager.convert(vol.getFsUuid());
final State state = findOrCreateState(uuid);
final long totalBytes = file.getTotalSpace();//data总大小
final long usableBytes = file.getUsableSpace();//可使用大小
int oldLevel = state.level;
int newLevel;
//判断是LEVEL_LOW,LEVEL_FULL还是LEVEL_NORMAL
if (mForceLevel != State.LEVEL_UNKNOWN) {
// When in testing mode, use unknown old level to force sending
// of any relevant broadcasts.
oldLevel = State.LEVEL_UNKNOWN;
newLevel = mForceLevel;
} else if (usableBytes <= fullBytes) {
newLevel = State.LEVEL_FULL;
} else if (usableBytes <= lowBytes) {
newLevel = State.LEVEL_LOW;
} else if (StorageManager.UUID_DEFAULT.equals(uuid) && !isBootImageOnDisk()
&& usableBytes < BOOT_IMAGE_STORAGE_REQUIREMENT) {
newLevel = State.LEVEL_LOW;
} else {
newLevel = State.LEVEL_NORMAL;
}
// Log whenever we notice drastic storage changes
if ((Math.abs(state.lastUsableBytes - usableBytes) > DEFAULT_LOG_DELTA_BYTES)
|| oldLevel != newLevel) {
//log
EventLogTags.writeStorageState(uuid.toString(), oldLevel, newLevel,
usableBytes, totalBytes);
state.lastUsableBytes = usableBytes;
}
//发送通知
updateNotifications(vol, oldLevel, newLevel);
//发送广播
updateBroadcasts(vol, oldLevel, newLevel, seq);
state.level = newLevel;
}
//没有check消息,继续30s检测
// Loop around to check again in future; we don't remove messages since
// there might be an immediate request pending.
if (!mHandler.hasMessages(MSG_CHECK)) {
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CHECK),
DEFAULT_CHECK_INTERVAL);
}
}
发送广播
private void updateBroadcasts(VolumeInfo vol, int oldLevel, int newLevel, int seq) {
if (!Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, vol.getFsUuid())) {
// We don't currently send broadcasts for secondary volumes
return;
}
//lowStorage广播action ACTION_DEVICE_STORAGE_LOW
final Intent lowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_LOW)
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS)
.putExtra(EXTRA_SEQUENCE, seq);
//正常Storage广播action ACTION_DEVICE_STORAGE_OK
final Intent notLowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK)
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS)
.putExtra(EXTRA_SEQUENCE, seq);
if (State.isEntering(State.LEVEL_LOW, oldLevel, newLevel)) {
// 内存正在变得越来越小,只发送一次广播ACTION_DEVICE_STORAGE_LOW,粘性广播,进程注册肯定会收到广播
getContext().sendStickyBroadcastAsUser(lowIntent, UserHandle.ALL);
} else if (State.isLeaving(State.LEVEL_LOW, oldLevel, newLevel)) {
//内存正在变得越来越大,恢复正常移除lowIntent粘性广播,发送normal的普通广播
getContext().removeStickyBroadcastAsUser(lowIntent, UserHandle.ALL);
getContext().sendBroadcastAsUser(notLowIntent, UserHandle.ALL);
}
final Intent fullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_FULL)
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT)
.putExtra(EXTRA_SEQUENCE, seq);
final Intent notFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL)
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT)
.putExtra(EXTRA_SEQUENCE, seq);
//发送FULL Storage广播ACTION_DEVICE_STORAGE_FULL
if (State.isEntering(State.LEVEL_FULL, oldLevel, newLevel)) {
getContext().sendStickyBroadcastAsUser(fullIntent, UserHandle.ALL);
} else if (State.isLeaving(State.LEVEL_FULL, oldLevel, newLevel)) {
getContext().removeStickyBroadcastAsUser(fullIntent, UserHandle.ALL);
getContext().sendBroadcastAsUser(notFullIntent, UserHandle.ALL);
}
}
APP 监听 lowStorage广播
public void registerLowStorageBroadcast() {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
mContext.registerReceiver(mReceiver, filter);
}
/** Receives events that might indicate a need to clean up files. */
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
Log.i(TAG, "handleStorageLow storage Low");
} else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
Log.i(TAG, "handleStorageLow storage Ok");
}
}
};
解决实际问题
目前Android 11 系统有存储满了的提示和界面, 现在提供兜底方案,防止Android Userdata分区被写满,导致Android无法启动。
1、监控磁盘使用状况,剩余分区容量低于警戒值,则下次开机时格式化分区
2、监控磁盘使用状况,剩余分区容量低于危险值,则立即格式化分区
3、警戒值和危险值大小评估
经过上述内容可以了解到, Android 11 是存在自己的内存检查机制的,当内存解决低阈值时自动修剪缓存数据;
当它在阈值的150%以内时,我们尝试调整清理缓存返回阈值到的200%,同时发送广播。这里有两个疑问:
- Android 的原生阈值是多少呢?
- Android 原声的 内存检查机制能满足上述问题吗?
Android 的原生阈值是多少呢?
private static final int DEFAULT_THRESHOLD_PERCENTAGE = 5;
private static final long DEFAULT_THRESHOLD_MAX_BYTES = DataUnit.MEBIBYTES.toBytes(500);
public long getStorageLowBytes(File path) {
final long lowPercent = Settings.Global.getInt(mResolver,
Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE);
final long lowBytes = (path.getTotalSpace() * lowPercent) / 100;
final long maxLowBytes = Settings.Global.getLong(mResolver,
Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES, DEFAULT_THRESHOLD_MAX_BYTES);
return Math.min(lowBytes, maxLowBytes);
}
默认总内存的 5% 和 500M 选择最小的(可配置)
项目上 UserData 对应的分区是 mmcblk0p18 大小为 13186048KB = 12877 M = 12.5 G
12877 * 5% = 643.84 。 所以选500M 为阈值。
解答疑问:
阈值 = 500M
Android 原声的内存检查机制能满足上述问题吗?
释放内存
try {
pms.freeStorage(vol.getFsUuid(), lowBytes * 2, 0);
} catch (IOException e) {
// 捕获异常未做处理
Slog.w(TAG, e);
}
PackageManagerService.java
/**
* Blocking call to clear various types of cached data across the system
* until the requested bytes are available.
*/
public void freeStorage(String volumeUuid, long bytes, int storageFlags) throws IOException {
final StorageManager storage = mInjector.getStorageManager();
final File file = storage.findPathForUuid(volumeUuid);
if (file.getUsableSpace() >= bytes) return;
if (ENABLE_FREE_CACHE_V2) {
final boolean internalVolume = Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL,volumeUuid);
final boolean aggressive = (storageFlags
& StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0;
final long reservedBytes = storage.getStorageCacheBytes(file, storageFlags);
// 1. Pre-flight to determine if we have any chance to succeed
// 确定我们是否有机会成功
// 2. Consider preloaded data (after 1w honeymoon, unless aggressive)
if (internalVolume && (aggressive || SystemProperties
.getBoolean("persist.sys.preloads.file_cache_expired", false))) {
// 删除预加载的文件
deletePreloadsFileCache();
if (file.getUsableSpace() >= bytes) return;
}
// 3. Consider parsed APK data (aggressive only)
// 已解析的APK数据
if (internalVolume && aggressive) {
FileUtils.deleteContents(mCacheDir);
if (file.getUsableSpace() >= bytes) return;
}
// 4. Consider cached app data (above quotas)
// 缓存的应用数据
try {
mInstaller.freeCache(volumeUuid, bytes, reservedBytes,
Installer.FLAG_FREE_CACHE_V2);
} catch (InstallerException ignored) {
}
if (file.getUsableSpace() >= bytes) return;
// 5. Consider shared libraries with refcount=0 and age>min cache period
// 共享库
if (internalVolume && pruneUnusedStaticSharedLibraries(bytes,
android.provider.Settings.Global.getLong(mContext.getContentResolver(),
Global.UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD,
DEFAULT_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD))) {
return;
}
// 6. Consider dexopt output (aggressive only)
// TODO: Implement
// 7. Consider installed instant apps unused longer than min cache period
// 考虑已安装的即时应用未使用时间超过最小缓存时间
if (internalVolume && mInstantAppRegistry.pruneInstalledInstantApps(bytes,
android.provider.Settings.Global.getLong(mContext.getContentResolver(),
Global.INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
InstantAppRegistry.DEFAULT_INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD))) {
return;
}
// 8. Consider cached app data (below quotas)
// 缓存的应用数据
try {
mInstaller.freeCache(volumeUuid, bytes, reservedBytes,
Installer.FLAG_FREE_CACHE_V2 | Installer.FLAG_FREE_CACHE_V2_DEFY_QUOTA);
} catch (InstallerException ignored) {
}
if (file.getUsableSpace() >= bytes) return;
// 9. Consider DropBox entries
// TODO: Implement
// 10. Consider instant meta-data (uninstalled apps) older that min cache period
// 未安装的应用程序
if (internalVolume && mInstantAppRegistry.pruneUninstalledInstantApps(bytes,
android.provider.Settings.Global.getLong(mContext.getContentResolver(),
Global.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
InstantAppRegistry.DEFAULT_UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD))) {
return;
}
} else {
try {
mInstaller.freeCache(volumeUuid, bytes, 0, 0);
} catch (InstallerException ignored) {
}
if (file.getUsableSpace() >= bytes) return;
}
// 如果清除了所有缓存, 还不满足最, 抛出异常
throw new IOException("Failed to free " + bytes + " on storage device at " + file);
}
删除预加载的文件
@Override
public void deletePreloadsFileCache() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CLEAR_APP_CACHE,
"deletePreloadsFileCache");
File dir = Environment.getDataPreloadsFileCacheDirectory();
Slog.i(TAG, "Deleting preloaded file cache " + dir);
FileUtils.deleteContents(dir);
}
FileUtils.java
public static boolean deleteContents(File dir) {
File[] files = dir.listFiles();
boolean success = true;
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
// 递归
success &= deleteContents(file);
}
if (!file.delete()) {
Log.w(TAG, "Failed to delete " + file);
success = false;
}
}
}
return success;
}
删除缓存的应用数据 缓存
系统缓存」由所有已安装应用的 /data/data/packagename/cache 文件夹和 /sdcard/Android/data/packagename/cache 文件夹组成。
Installer.java
public void freeCache(String uuid, long targetFreeBytes, long cacheReservedBytes, int flags)
throws InstallerException {
if (!checkBeforeRemote()) return;
try {
mInstalld.freeCache(uuid, targetFreeBytes, cacheReservedBytes, flags);
} catch (Exception e) {
throw InstallerException.from(e);
}
}
private boolean checkBeforeRemote() {
if (mWarnIfHeld != null && Thread.holdsLock(mWarnIfHeld)) {
Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding 0x"
+ Integer.toHexString(System.identityHashCode(mWarnIfHeld)), new Throwable());
}
if (mIsolated) {
Slog.i(TAG, "Ignoring request because this installer is isolated");
return false;
} else {
return true;
}
}
mInstalld.freeCache(uuid, targetFreeBytes, cacheReservedBytes, flags);
这个实际是调用
frameworks/native/cmds/installd/binder/android/os/IInstalld.aidl 中定义的方法。最后调用的是 InstalldNativeService.cpp
binder::Status InstalldNativeService::freeCache(const std::unique_ptr<std::string>& uuid,
int64_t targetFreeBytes, int64_t cacheReservedBytes, int32_t flags) {
ENFORCE_UID(AID_SYSTEM);
// 检查UUID
CHECK_ARGUMENT_UUID(uuid);
std::lock_guard<std::recursive_mutex> lock(mLock);
auto uuidString = uuid ? *uuid : "";
const char* uuid_ = uuid ? uuid->c_str() : nullptr;
auto data_path = create_data_path(uuid_);
auto noop = (flags & FLAG_FREE_CACHE_NOOP);
// 确定的可释放的空间 = 还需要释放的空间
int64_t free = data_disk_free(data_path);
if (free < 0) {
return error("Failed to determine free space for " + data_path);
}
int64_t cleared = 0;
// 目标释放的目标空间 - 确定的可释放的空间 = 还需要释放的空间
int64_t needed = targetFreeBytes - free;
LOG(DEBUG) << "Device " << data_path << " has " << free << " free; requested "
<< targetFreeBytes << "; needed " << needed;
// 确定的可释放的空间 > 目标释放的目标空间 return ok
if (free >= targetFreeBytes) {
return ok();
}
if (flags & FLAG_FREE_CACHE_V2) {
// This new cache strategy fairly removes files from UIDs by deleting
// files from the UIDs which are most over their allocated quota
// 这种新的缓存策略通过从UID中删除超出其分配配额最多的文件,从UID中公平地删除文件
// 1. Create trackers for every known UID
ATRACE_BEGIN("create");
std::unordered_map<uid_t, std::shared_ptr<CacheTracker>> trackers;
for (auto user : get_known_users(uuid_)) {
FTS *fts;
FTSENT *p;
auto ce_path = create_data_user_ce_path(uuid_, user);
auto de_path = create_data_user_de_path(uuid_, user);
auto media_path = findDataMediaPath(uuid, user) + "/Android/data/";
char *argv[] = { (char*) ce_path.c_str(), (char*) de_path.c_str(),
(char*) media_path.c_str(), nullptr };
if (!(fts = fts_open(argv, FTS_PHYSICAL | FTS_NOCHDIR | FTS_XDEV, nullptr))) {
return error("Failed to fts_open");
}
while ((p = fts_read(fts)) != nullptr) {
if (p->fts_info == FTS_D && p->fts_level == 1) {
uid_t uid = p->fts_statp->st_uid;
if (multiuser_get_app_id(uid) == AID_MEDIA_RW) {
uid = (multiuser_get_app_id(p->fts_statp->st_gid) - AID_EXT_GID_START)
+ AID_APP_START;
}
auto search = trackers.find(uid);
if (search != trackers.end()) {
search->second->addDataPath(p->fts_path);
} else {
auto tracker = std::shared_ptr<CacheTracker>(new CacheTracker(
multiuser_get_user_id(uid), multiuser_get_app_id(uid), uuidString));
tracker->addDataPath(p->fts_path);
{
std::lock_guard<std::recursive_mutex> lock(mQuotasLock);
tracker->cacheQuota = mCacheQuotas[uid];
}
if (tracker->cacheQuota == 0) {
#if MEASURE_DEBUG
LOG(WARNING) << "UID " << uid << " has no cache quota; assuming 64MB";
#endif
tracker-> cacheQuota = 67108864;
}
trackers[uid] = tracker;
}
fts_set(fts, p, FTS_SKIP);
}
}
fts_close(fts);
}
ATRACE_END();
// 2. Populate tracker stats and insert into priority queue
ATRACE_BEGIN("populate");
int64_t cacheTotal = 0;
auto cmp = [](std::shared_ptr<CacheTracker> left, std::shared_ptr<CacheTracker> right) {
return (left->getCacheRatio() < right->getCacheRatio());
};
std::priority_queue<std::shared_ptr<CacheTracker>,
std::vector<std::shared_ptr<CacheTracker>>, decltype(cmp)> queue(cmp);
for (const auto& it : trackers) {
it.second->loadStats();
queue.push(it.second);
cacheTotal += it.second->cacheUsed;
}
ATRACE_END();
// 3. Bounce across the queue, freeing items from whichever tracker is
// the most over their assigned quota
// 在队列中跳跃,从超出其分配配额最多的跟踪器中释放项目
ATRACE_BEGIN("bounce");
std::shared_ptr<CacheTracker> active;
while (active || !queue.empty()) {
// Only look at apps under quota when explicitly requested
// 仅在明确请求时查看配额下的应用
if (active && (active->getCacheRatio() < 10000)
&& !(flags & FLAG_FREE_CACHE_V2_DEFY_QUOTA)) {
LOG(DEBUG) << "Active ratio " << active->getCacheRatio()
<< " isn't over quota, and defy not requested";
break;
}
// Only keep clearing when we haven't pushed into reserved area
if (cacheReservedBytes > 0 && cleared >= (cacheTotal - cacheReservedBytes)) {
LOG(DEBUG) << "Refusing to clear cached data in reserved space";
break;
}
// Find the best tracker to work with; this might involve swapping
// if the active tracker is no longer the most over quota
// 找到最佳的跟踪器;这可能涉及交换 如果活动跟踪器不再超出配额
bool nextBetter = active && !queue.empty()
&& active->getCacheRatio() < queue.top()->getCacheRatio();
if (!active || nextBetter) {
if (active) {
// Current tracker still has items, so we'll consider it
// again later once it bubbles up to surface
queue.push(active);
}
active = queue.top(); queue.pop();
active->ensureItems();
continue;
}
// If no items remain, go find another tracker
// 如果没有剩余项目,请查找另一个跟踪器
if (active->items.empty()) {
active = nullptr;
continue;
} else {
auto item = active->items.back();
active->items.pop_back();
LOG(DEBUG) << "Purging " << item->toString() << " from " << active->toString();
if (!noop) {
item->purge();
}
active->cacheUsed -= item->size;
needed -= item->size;
cleared += item->size;
}
// Verify that we're actually done before bailing, since sneaky
// apps might be using hardlinks
// 验证我们在保释之前是否已经完成,因为偷偷摸摸的应用程序可能正在使用硬链接
if (needed <= 0) {
free = data_disk_free(data_path);
needed = targetFreeBytes - free;
if (needed <= 0) {
break;
} else {
LOG(WARNING) << "Expected to be done but still need " << needed;
}
}
}
ATRACE_END();
} else {
return error("Legacy cache logic no longer supported");
}
free = data_disk_free(data_path);
if (free >= targetFreeBytes) {
return ok();
} else {
return error(StringPrintf("Failed to free up %" PRId64 " on %s; final free space %" PRId64,
targetFreeBytes, data_path.c_str(), free));
}
}
什么是Cache Tracker
单个UID的缓存跟踪器。
每个跟踪器有两种模式:
第一种模式 加载轻量级的“统计数据”,
第二种模式 加载详细的“项目”然后可以清除这些项目以释放空间
6
17 #ifndef ANDROID_INSTALLD_CACHE_TRACKER_H
18 #define ANDROID_INSTALLD_CACHE_TRACKER_H
19
20 #include <memory>
21 #include <string>
22 #include <queue>
23
24 #include <sys/types.h>
25 #include <sys/stat.h>
26
27 #include <android-base/macros.h>
28 #include <cutils/multiuser.h>
29
30 #include "CacheItem.h"
31
32 namespace android {
33 namespace installd {
34
35 /**
36 * Cache tracker for a single UID. Each tracker is used in two modes: first
37 * for loading lightweight "stats", and then by loading detailed "items"
38 * which can then be purged to free up space.
39 */
40 class CacheTracker {
41 public:
42 CacheTracker(userid_t userId, appid_t appId, const std::string& uuid);
43 ~CacheTracker();
44
45 std::string toString();
46
47 void addDataPath(const std::string& dataPath);
48
49 void loadStats();
50 void loadItems();
51
52 void ensureItems();
53
54 int getCacheRatio();
55
56 int64_t cacheUsed;
57 int64_t cacheQuota;
58
59 std::vector<std::shared_ptr<CacheItem>> items;
60
61 private:
62 userid_t mUserId;
63 appid_t mAppId;
64 bool mItemsLoaded;
65 const std::string& mUuid;
66
67 std::vector<std::string> mDataPaths;
68
69 bool loadQuotaStats();
70 void loadItemsFrom(const std::string& path);
71
72 DISALLOW_COPY_AND_ASSIGN(CacheTracker);
73 };
74
75 } // namespace installd
76 } // namespace android
77
78 #endif // ANDROID_INSTALLD_CACHE_TRACKER_H
留下疑问:Cache Tracker的内存释放策略是什么样的,目前还未找到答案。
解答疑问:
通过上述源码,了解到Android 会从以上空间帮我们释放内存:
- 删除预加载的文件
- 已解析的APK数据
- 缓存的应用数据
- 共享库
- 已安装的即时应用未使用时间超过最小缓存时间
- 缓存的应用数据
- 未安装的应用程序
但是如果清除了上述所有的地方 可用的内存空间 还是很小, 不满足 2 倍的阈值, 抛出
throw new IOException("Failed to free " + bytes + " on storage device at " + file);
try {
pms.freeStorage(vol.getFsUuid(), lowBytes * 2, 0);
} catch (IOException e) {
// 捕获异常未做处理
Slog.w(TAG, e);
}
我们看到 Android 在捕获到异常后未做任何处理。那内存极限紧张的情况下,Android是怎么处理的呢?
内存极限紧张的情况下,Android是怎么处理的呢?
使用模拟器实验验证:
开机前 Data分区容量
Total | Used | Available | 阈值 |
---|---|---|---|
774 M | 378 M | 364 M | 774 * 5% = 38 M |
目前还是大于阈值, 开始写入 380M 文件
dd if=/dev/block/dm-0 of=13gbFile bs=1024 count=68640
Total | Used | Available | 阈值 |
---|---|---|---|
774 M | 758 M | 16M | 774 * 5% = 38 M |
现在已经小于阈值了, 重启后:
Data分区还是满的,系统还是可以起来。查看Log
81253170 = 76M (阈值)= 38 * 2
我们可以看到当前已经没有办法清除到 2 倍的阈值, 并打印了Android 的原生log。 但是还是不影响系统启动。但是为了防止内存极端紧张的情况下系统,还是增加兜底方案。
在系统原有机制中,定制空间不足时,小于 lowBytes / 2 写一个属性persist.vendor.data.lowspace ,下次重启执行wipe data; 小于lowBytes / 3 M 直接清理 wipe dated。
try {
pms.freeStorage(vol.getFsUuid(), lowBytes * 2, 0);
} catch (IOException e) {
Slog.w(TAG, e);
try {
if (file.getUsableSpace() < lowBytes / 2) { // only 500M
if (vol.id != null)
Slog.w(TAG, "id = " + vol.id);
SystemProperties.set("persist.vendor.data.lowspace", "true");
}
if (file.getUsableSpace() < lowBytes / 3) { // only 166M, wipedata directly
SystemProperties.set("persist.vendor.data.lowspace", "false");
Runtime.getRuntime().exec("reboot wipedata");
}
} catch (Exception e2) {
Slog.w(TAG, e2);
}
}
}
开机 - 双清
static Result<Success> do_load_persist_props(const BuiltinArguments& args) {
load_persist_props();
if (::property_get_bool("persist.vendor.data.lowspace", 0)) {
property_set("persist.vendor.data.lowspace", "false"); // reset to false
property_set(ANDROID_RB_PROPERTY, "reboot,wipedata");
}
return Success();
}
双清后:
内存回复到出厂设置。
Android 分区和内存监控的更多相关文章
- 【Android端 APP 内存分析】使用工具进行APP的内存分析
Android端可以通过adb 命令直接获取内存信息,当然Android studio也提供了对内存的监控分析工具,并且后续可以结合MAT做分析 今天介绍的是通过Android studio和MAT工 ...
- 系统剖析Android中的内存泄漏
[转发]作为Android开发人员,我们或多或少都听说过内存泄漏.那么何为内存泄漏,Android中的内存泄漏又是什么样子的呢,本文将简单概括的进行一些总结. 关于内存泄露的定义,我可以理解成这样 没 ...
- 查找并修复Android中的内存泄露—OutOfMemoryError
[编者按]本文作者为来自南非约翰内斯堡的女程序员 Rebecca Franks,Rebecca 热衷于安卓开发,拥有4年安卓应用开发经验.有点完美主义者,喜爱美食. 本文系国内ITOM管理平台 One ...
- Android性能测试-内存
前言: 近阶段都在探索android性能测试方面的东西,其中一个很重要的指标就是内存.对于内存,主要是一些gc是不是及时,或者说一些引用有没有及时释放,有没有导致oom或者内存持续增加导致卡顿,有没有 ...
- Linux 内存监控
1.按照内存使用方式排序 top 之后使用 shift + m 那么top按照内存使用从大到小进行排列,使用 shift + P 表示按照CPU进行排序. bigfish 1118m 438m 30m ...
- 微信团队原创分享:iOS版微信的内存监控系统技术实践
本文来自微信开发团队yangyang的技术分享. 一.前言 FOOM(Foreground Out Of Memory),是指App在前台因消耗内存过多引起系统强杀.对用户而言,表现跟crash一样. ...
- 使用新版Android Studio检测内存泄露和性能
内存泄露,是Android开发者最头疼的事.可能一处小小的内存泄露,都可能是毁于千里之堤的蚁穴. 怎么才能检测内存泄露呢?网上教程非常多,不过很多都是使用Eclipse检测的, 其实1.3版本以后的 ...
- Android DDMS检测内存泄露
Android DDMS检测内存泄露 DDMS是Android开发包中自带工具,可以测试app性能,用于发现内存问题. 1.环境搭建 参考之前发的Android测试环境搭建相关文章,这里不再复述: 2 ...
- android 图片占用内存与什么有关
android 图片占用内存与什么有关 原文链接:http://blog.csdn.net/zjl5211314/article/details/7041813 在开发手机应用的时候,内存是有限的,那 ...
- Android -- 系统信息(内存、cpu、sd卡、电量、版本)获取
内存(ram) android的总内存大小信息 ...
随机推荐
- 关于linux上实现arp攻击截取密码
前言 这几天简单的研究了一下arp攻击,有一些进展,记录一下 环境准备 这里我是利用arpspoof 这个软件简单实现arp攻击,这个命令是属于dsniff 软件包中的 所以首先安装软件 sudo a ...
- 【每日一题】【快速排序过程、循环过程无=、递归参数】2022年1月16日-NC140 排序
快速排序 对时间复杂度和空间复杂度有要求 方法1:快速排序-递归 import java.util.*; public class Solution { /** * 代码中的类名.方法名.参数名已经指 ...
- 快速学会慢查询SQL排查
转载请注明出处️ 作者:测试蔡坨坨 原文链接:caituotuo.top/c56bd0c5.html 你好,我是测试蔡坨坨. 在往期文章中,我们聊过数据库基础知识,可参考「数据库基础,看完这篇就够了! ...
- 自研ORM框架 实现类似EF Core Include 拆分查询 支持自定义条件、排序、选择
Baozi, I'm Mr.Zhong I like to brush TikTok, I know that anchors like to call it that, haha!Recently, ...
- 架构设计(四):CDN
架构设计(四):CDN 作者:Grey 原文地址: 博客园:架构设计(四):CDN CSDN:架构设计(四):CDN CDN 全称 Content delivery network ,即:内容分发网络 ...
- 修改数据时,一直提示"具有 XXX 的 字典管理 已存在(即数据已存在)
原代码: class DictConfig(models.Model): """ 字典表 """ id = models.AutoField ...
- C语言 根据掩码计算网段的起止ip
原文地址:https://www.yuque.com/docs/share/85a26263-484a-42f6-880b-2b511ae1bd20?# 根据ipv4掩码计算 #include < ...
- python之路25 面向对象 封装(隐藏、伪装)、多态、反射
派生方法实战演练 import json import datetime d = { 't1': datetime.date.today(), 't2': datetime.datetime.toda ...
- [cocos2d-x]TextureCache和spriteFramecache
SpriteFrameCache 主要服务于多张碎图合并出来的纹理图片.这种纹理在一张大图中包含了多张小图,直接通过TextureCache引用会有诸多不便,因而衍生出来精灵框帧的处理方式,即把截取好 ...
- Python 跨模块使用全局变量(自定义类型)
gol.py def _init():#初始化 global _global_dict _global_dict = {} def set_value(key,value): "" ...