低内存配置

Android 支持内存为 512MB 的设备。本文档旨在帮助原始设备制造商 (OEM) 优化和配置 Android 内核 4.4,使其能够在低内存设备上运行。在下文所述的优化措施中,有几项非常通用,甚至也可应用于以前的版本。

Android 内核 4.4 平台优化

改善了内存管理

  • 采用了经验证可节省内存的内核配置:交换到 zram。
  • 终止了那些即将被取消缓存且过大的缓存进程。
  • 不允许大型服务自行返回至 A 服务类别(以免导致启动器终止)。
  • 终止了那些处于空闲维护状态中的过大进程(甚至终止当前 IME 等通常不可终止的进程)。
  • 对后台服务的启动进行了序列化。
  • 优化了低内存设备的内存使用方式:采用更严格的内存不足 (OOM) 调整级别、缩减图形缓存大小。

减少了系统内存占用

  • 删减了 system_server 和系统界面进程(节省了几兆的内存)。
  • 在 Dalvik 中预加载 dex 缓存(节省了几兆的内存)。
  • 采用了经验证的 JIT-off 选项(每个进程最多可节省 1.5MB 的内存)。
  • 减少了各进程的字体缓存开销。
  • 引入了占用内存更少的 ArrayMap/ArraySet,并在框架中广泛地使用它来替代 HashMap/HashSet

Procstats

添加了一个开发者选项,以显示内存状态和应用内存使用情况(按照运行频率和所耗内存量排序)。

API

添加了 ActivityManager.isLowRamDevice(),使应用不仅能够检测是否是在低内存设备上运行,还能选择停用那些占用内存较多的功能。

内存跟踪

添加了 memtrack HAL 来跟踪图形内存分配情况、dumpsys meminfo 中的更多信息,以及 meminfo 中的阐明性总结(例如,所报告的可用内存包括缓存进程占用的内存,这样 OEM 就不会搞错优化对象)。

编译时配置

低内存设备标记

ActivityManager.isLowRamDevice() 标记可确定应用是否应关闭在低内存设备上表现非常差的某些内存密集型功能。

对于内存为 512MB 的设备,该标记应返回 true。可以通过在设备 makefile 中使用以下系统属性来启用该 API:

PRODUCT_PROPERTY_OVERRIDES += ro.config.low_ram=true

启动器配置

启动器的默认壁纸设置不应使用动态壁纸。低内存设备不应预装任何动态壁纸。

内核配置

优化内核/ActivityManager 以减少直接回收

当进程或内核尝试分配(直接分配或因新页面中存在故障而分配)内存页面并且内核已用尽所有可用内存时,就会发生直接回收。在这种情况下,内核需要释放一个页面,并在此过程中阻断分配操作。而这通常又需要磁盘 I/O 清理一个有文件支持的脏页或等待 lowmemorykiller 终止一个进程。最终可能会导致任意线程(包括界面线程)中出现额外 I/O。

为避免出现直接回收,内核已配有可触发 kswapd 或后台回收的水印。此线程会尝试释放页面,以便下次分配真实线程时,能够快速顺利启动。

用于触发后台回收的默认阈值相当低,在 2GB 设备上约为 2 MB,在 512 MB 设备上约为 636 KB。内核通过后台回收仅能回收几兆的内存。这意味着,任何快速分配超过几兆内容的进程都会快速导致直接回收。

在 Android-3.4 内核分支中,我们通过补丁程序 92189d47f66c67e5fd92eafaa287e153197a454f(“添加用于扩展可用内存空间的可调选项”)添加了对内核可调选项的支持。如果您选择将该补丁程序添加到设备内核中,ActivityManager 会告知内核尝试保留能容纳 3 个全屏 32 bpp 缓冲区的可用内存空间。

这些阈值可通过框架 config.xml 进行配置。

<!-- Device configuration setting the /proc/sys/vm/extra_free_kbytes tunable
in the kernel (if it exists).  A high value will increase the amount of memory
that the kernel tries to keep free, reducing allocation time and causing the
lowmemorykiller to kill earlier.  A low value allows more memory to be used by
processes but may cause more allocations to block waiting on disk I/O or
lowmemorykiller.  Overrides the default value chosen by ActivityManager based
on screen size.  0 prevents keeping any extra memory over what the kernel keeps
by default.  -1 keeps the default. -->
<integer name="config_extraFreeKbytesAbsolute">-1</integer>
<!-- Device configuration adjusting the /proc/sys/vm/extra_free_kbytes
tunable in the kernel (if it exists).  0 uses the default value chosen by
ActivityManager.  A positive value  will increase the amount of memory that the
kernel tries to keep free, reducing allocation time and causing the
lowmemorykiller to kill earlier.  A negative value allows more memory to be
used by processes but may cause more allocations to block waiting on disk I/O
or lowmemorykiller.  Directly added to the default value chosen by
ActivityManager based on screen size. -->
<integer name="config_extraFreeKbytesAdjust">0</integer>

优化 LowMemoryKiller

ActivityManager 可配置 LowMemoryKiller 的阈值,使其符合它对在每个优先级分段中运行进程时所需的文件支持页面(缓存页面)工作集的预期。如果设备对工作集有很高的要求(例如:如果供应商界面需要更多内存,或者如果添加了更多服务),则可增大阈值。

如果为文件支持页面预留了太多内存,则可减小阈值,以便系统能够在因缓存变得过小而导致磁盘超负荷之前就终止后台进程。

<!-- Device configuration setting the minfree tunable in the lowmemorykiller
in the kernel.  A high value will cause the lowmemorykiller to fire earlier,
keeping more memory in the file cache and preventing I/O thrashing, but
allowing fewer processes to stay in memory.  A low value will keep more
processes in memory but may cause thrashing if set too low.  Overrides the
default value chosen by ActivityManager based on screen size and total memory
for the largest lowmemorykiller bucket, and scaled proportionally to the
smaller buckets.  -1 keeps the default. -->
<integer name="config_lowMemoryKillerMinFreeKbytesAbsolute">-1</integer>
<!-- Device configuration adjusting the minfree tunable in the
lowmemorykiller in the kernel.  A high value will cause the lowmemorykiller to
fire earlier, keeping more memory in the file cache and preventing I/O
thrashing, but allowing fewer processes to stay in memory.  A low value will
keep more processes in memory but may cause thrashing if set too low.  Directly
added to the default value chosen by          ActivityManager based on screen
size and total memory for the largest lowmemorykiller bucket, and scaled
proportionally to the smaller buckets. 0 keeps the default. -->
<integer name="config_lowMemoryKillerMinFreeKbytesAdjust">0</integer>

交换到 zram

zram 交换可通过压缩内存页面并将其放入动态分配的内存交换区来增加系统中的可用内存量。由于这是以牺牲 CPU 时间为代价来增加少量内存,因此您应仔细权衡 zram 交换会对您系统的性能造成的负面影响。

Android 会在多个层面上处理 zram 交换:

  • 首先,必须启用以下内核选项,才能有效地使用 zram 交换:
    • CONFIG_SWAP
    • CONFIG_CGROUP_MEM_RES_CTLR
    • CONFIG_CGROUP_MEM_RES_CTLR_SWAP
    • CONFIG_ZRAM
  • 然后,您应将一行与下列类似的内容添加到 fstab 中:
    /dev/block/zram0 none swap defaults zramsize=<size in bytes>,swapprio=<swap partition priority>
    
    • zramsize 是必要内容,表示您希望 zram 区域占用多少未压缩内存。压缩比通常介于 30-50% 之间。
    • 仅当您没有多个交换区时才需要 swapprio

    在特定于设备的 sepolicy/file_contexts 中将关联的块设备标记为 swap_block_device,以便 SELinux 适当地对其进行处理。

    /dev/block/zram0 u:object_r:swap_block_device:s0
    
  • 默认情况下,Linux 内核每次会换入 8 页内存。当使用 zram 时,每次读取 1 页内存产生的增量开销微乎其微,如果设备承受巨大的内存压力,可能有所助益。要想每次只读取 1 页内存,请将以下内容添加到您的 init.rc 中:
    write /proc/sys/vm/page-cluster 0
    
  • init.rc 中的 mount_all /fstab.X 行后面,添加以下内容:
    swapon_all /fstab.X
    
  • 如果在内核中启用了此功能,系统便会在启动时自动配置内存 cgroups。
  • 如果内存 cgroup 可用,ActivityManager 便会将优先级较低的线程标记为比其他线程更适合交换。在需要内存时,Android 内核会开始将内存页面迁移到 zRAM 交换区,并会优先处理那些 ActivityManager 已标记的内存页面。

Carveout、Ion 和连续内存分配 (CMA)

在低内存设备上,请务必注意 carveout,尤其是那些未得到充分利用的 carveout,例如用于安全地播放视频的 carveout。有几种解决方案可最大限度地减小 carveout 区域的影响,具体取决于硬件的确切要求。

如果硬件允许不连续的内存分配,则可利用 Ion 系统堆从系统内存中分配内存,这样便无需使用 carveout。Ion 还会尝试增大分配的内存空间以消除外围设备上的转译后备缓冲区 (TLB) 压力。如果内存区域必须连续或必须限定在某个特定地址范围内,则可以使用 CMA。

这将创建一个 carveout,系统也可以将其用于处理可移动页面。当需要该区域时,可移动页面就会从中移出,以便系统将处于空闲状态的大型 carveout 用于其他目的。您可以直接将 CMA 与 Ion CMA 堆配合使用。

应用优化提示

  • 查看管理应用内存和下面这些博文:
  • 使用 development/tools/findunused 移除预安装应用中所有未使用的资源(这应该会有助于减小应用所占用的空间)。
  • 针对资源(特别是具有透明区域的资源)使用 PNG 格式。
  • 编写原生代码时,请使用 calloc() 而非 malloc/memset
  • 请勿启用会将 Parcel 数据写入磁盘并在之后读取这些数据的代码。
  • 使用 SSP 过滤,而不要订阅已安装的所有软件包。请添加如下所示的过滤条件:
    <data android:scheme="package" android:ssp="com.android.pkg1" />
    <data android:scheme="package" android:ssp="com.myapp.act1" />
    

了解 Android 中的各种进程状态

状态 含义 详情
SERVICE
SERVICE_RESTARTING
由于与应用相关的原因而在后台运行的应用。 SERVICESERVICE_RESTARTING 是应用过于频繁地在后台运行时会遇到的最常见的问题。使用 %duration * pss 或 %duration 作为“不良”指标。理想情况下,这些应用根本不应该运行。
IMPORTANT_FOREGROUND
RECEIVER
在后台运行的应用(不直接与用户交互)。 这些应用会增加系统的内存负载。使用 (%duration * pss)“不良”值来对这些进程进行排序。不过,很多此类应用都会因合理原因而需要运行。pss 大小将会是它们的内存负载的重要组成部分。
PERSISTENT 持续的系统进程。 跟踪 pss 可监视这些进程是否会变得过大。
TOP 正与用户交互的进程。 pss 在此又成为了重要指标,可显示应用在使用过程中产生的内存负载。
HOME
CACHED_EMPTY
系统保留的备用进程。 这些进程可随时终止,并可根据需要重新创建。内存状态(“正常”、“中等”、“低”、“严重”)是根据系统运行的此类进程的数量进行计算的。这些进程的关键指标为 pss。在这种状态下,这些进程应尽可能地减少其内存占用空间,以便系统能够保留尽可能多的进程。一般来说,与在 TOP 状态下相比,运行状况良好的应用在该状态下的 pss 占用空间明显更小。
CACHED_ACTIVITY
CACHED_ACTIVITY_CLIENT
TOP 相比,这些状态显示了应用将内存释放到后台的程度。 排除 CACHED_EMPTY 状态可改善这些数据,因为这项操作会排除因某些原因(除了与用户互动之外)而启动进程的情况。这样便无需处理 CACHED_EMPTY 在执行与用户相关的 Activity 时产生的界面开销。

分析

分析应用启动时间

要分析应用的启动时间,请运行 $ adb shell am start -P--start-profiler 并启动您的应用。在该进程从 zygote 分岔之后以及任何代码加载到该分支之前,分析器就会启动。

使用错误报告进行分析

错误报告包含一些服务(包括 batterystatsnetstatsprocstatsusagestats),这些服务可用于调试。报告可包括以下行:

------ CHECKIN BATTERYSTATS (dumpsys batterystats --checkin) ------
7,0,h,-2558644,97,1946288161,3,2,0,340,4183
7,0,h,-2553041,97,1946288161,3,2,0,340,4183

检查是否存在任何持续进程

要检查任何持续进程,请重新启动设备并检查进程情况。然后,让设备运行几个小时,然后再次检查进程情况。两次检查之间不应存在任何长时间运行的进程。

运行长时测试

要运行长时测试,请让设备运行较长时间,并跟踪进程的内存占用情况,以确定内存用量增加还是保持不变。然后拟订规范的使用情形,并针对这些情形运行长时测试。