这是评测方法的优化。
RAM作为手机运行时所有内容所容纳加载的地方,重要性不言而喻。
了解手机在开机后的RAM剩余大小,能够帮助我们清楚手机能加载多大的程序,运载应用的能力有高。
Android是一个不断升级演变的系统,新的版本对RAM的需求也越来越多,了解当前系统在正常使用时,系统耗费RAM大小,也能够帮助开发工程师、开发管理者从底至上地清楚系统本身的状态。对于开发或者决策,提供十分必要的前提基础。
RAM(random access memory)随机存取存储器。就是平时所说的内存。
内存耗用名词解析:
术语 | 全拼 | 解释 |
---|---|---|
VSS | Virtual Set Size | 虚拟耗用内存(包含共享库占用的内存) |
RSS | Resident Set Size | 实际使用物理内存(包含共享库占用的内存) |
PSS | Proportional Set Size | 实际使用的物理内存(比例分配共享库占用的内存) |
USS | Unique Set Size | 进程独自占用的物理内存(不包含共享库占用的内存) |
一般来说内存占用大小有如下规律:VSS >= RSS >= PSS >= USS。
观察和计算系统内存使用情况,Android主要提供给我们的两个工具procstats,meminfo。前者侧重于后台的内存使用,后者是运行时的内存使用。还有其他配置文件、linux命令也可以获取一些有帮助的参数信息。
procstats是Android 4.4 KitKat 提出的一个新的系统服务。它能够帮助我们更好地理解app在后台(background)时的内存使用情况。
Procstats可以去监视app在一段时间的行为,包括在后台运行了多久,并在此段时间使用了多少内存。从而帮助你快速地找到应用中不效率和不规范的地方去避免影响其performs,尤其是在低内存的设备上运行时。
我们可以通过adb shell命令去使用procstats(adb shell dumpsys procstats –hours 3),或者更方便的方式是运行Process Stats开发者工具(在4.4版本的手机中点击Settings > Developer options > Process Stats),点击单个条目还可以查看详细信息。
Android还提供了一个工具叫做meminfo。它是根据PSS标准计算每个进程的内存使用并且按照重要程度排序。
我们可以通过命令行去执行它:(adb shell dumpsys meminfo)或者使用在设备上点击Settings > Apps > Running(与Procstats不同,它也可以在老版本上运行)。
它计算每个进程的内存使用并按照重要程度排序。
我们可以通过命令去执行它:(adb shell procrank)。
这里的meminfo与3.2中的并不相同,这里只能得到内存的大致使用情况。
我们可以通过命令去执行它:(adb shell cat /proc/meminfo)。
它显示当前Android系统所有进程信息,包含进程的RSS大小。
我们可以通过命令去执行它:(adb shell ps)。
方法一:ps 进程PID
方法二:showmap 进程PID
方法三:dumpsys meminfo 进程PID或者进程名
一个手机要能够正常跑起来,从软件层面大致分为如下一些部分:
Modem:手机通信所要用的软件。
Kernel:系统核心,也就是Android使用的特定版本的Linux核心库,提供设备驱动等。
System Service:系统服务,也就是系统运行库,可供上层框架共同使用。
Frameworks:应用程序框架,由Android提供标准的API,供应用运行和调用。
Applications:应用程序,这是用户接触到的可以实际使用的,能够提供各种手机功能的软件。
从内存层面来看,以上这些内容均要在系统上电开机后,加载到内存中去。因此,我们希望了解系统正常开机后这些部分到底消耗了多少内存空间。
但是,Android运行机制需要另一块没有提到的内存空间,就是Frame Buffer。除此之外,我们认为没有使用的就是RAM的可用空间。RAM的剩余空间衡量了系统在运行时能够继续装载程序并正常运行的能力,虽然剩余空间和运行速度并不成正相关关系,但是从程序稳定性来讲,剩余空间越大越不容易导致内存回收,程序被系统强制清理导致卡顿、不稳定、反应过慢的概率便越低。
Frame Buffer:系统运行所要使用的空间(不是软件,只是一块内存空间)。
**Free:**RAM剩余空间。
综上,RAM测评的目标,便是更加真实精确地取得下表中的各项数据大小。
Product | Perso | Modem | Frame Buffer | Kernel | System Service | Frameworks | Applications | Free |
---|---|---|---|---|---|---|---|---|
一个项目,其Modem占用大小、Frame Buffer都是由系统工程师预先设置好大小的,设置为多大,就会固定占用多大内存(就算实际Modem没有达到设置大小,由于这块空间已经划分给Modem使用,也是无法被系统临时作为它用的,如果实际值和设置值相差过大,则是可以进行优化的一个地方)。
在MTK平台中,这两个值可以通过一个叫做mtk_memcfg的配置文件中读取。使用下面命令(手机需要root):
adb shell cat /proc/ mtk_memcfg/memory_layout
得到的返回结果如下:
通过读取这个文件,我们可以得到关于内存的诸多配置信息。
我们重点关注以下几个信息,我一边解释一边说明。
available DRAM size:就是内存的大小。
FB:就是Frame Buffer的大小。
mtk_wcn_consys_memory_reserve:就是WIFI所需大小。
ccci_md_mem_reserve:就是Modem所需大小。
kernel:内核所需大小,但并不准确,我们会发现有两行,而且大小并不一样。因此,Kernel的真正大小,我们需要通过其他方式获得。这里需要特别注意。
以截图中的例子,这一步我们可以得到4个特定的值,计入下表:
数据 标签 | 十六进制大小(Byte) | 十进制大小(MB) |
---|---|---|
Total | available DRAM size | 0x20000000 |
FB | FB | 0xb00000 |
Modem | ccci_md_mem_reserve | 0x01800000 |
这里需要说明的是,截图中最后的那个十六进制表示的即是占用内存大小,单位是字节。这个数值前有两个用短连接符分隔的数值,表示内存地址空间的起始和截止位。我们将十六进制转换成十进制,然后将单位再换算成兆,就得到了最终想要的数据。
接下来获取Kernel的大小。
这里需要提另一个可以读取内存信息的地方,使用下面命令:
adb shell cat /proc/meminfo
返回结果如下面截图:
我们重点关注以下几个信息,我继续一边解释一边说明:
MemTotal:可供系统和用户使用的总内存大小 (它比实际的物理内存要小,因为还有些内存要用于radio,DMA buffers等)。
MemFree:剩余的可用内存大小。一般Android system 的该值通常都很小,因为我们尽量让进程都保持运行,这样会耗掉大量内存。
Cached:这个是系统用于文件缓冲等的内存。
这里,我们主要是为了得到MemTotal的大小,换算后是449MB。
这样,我们就可以使用下面公式计算出Kernel的大小:
Kernel = Total-FB-Modem-MemTotal
通过之前得到的数据,可以计算得到Kernel的大小为27MB。
由于Android系统后,在不特定的时段会在后台启动不同的进程,这导致我们使用meminfo工具进行实时统计的结果并不十分准确。例如,使用开机后等待5分钟进行监测的方法,每次的操作并不能得到一个特别稳定的值,有时候甚至有多达几十兆的差别。
因此,我们使用procstats这个历史统计值,使用下面命令:
adb shell dumpsys procstats --hours 1
统计手机在正常待机1小时内的内存使用情况。
通过这个命令,我们可以得到系统在最后1小时内所有加载进内存中的进程和对应的信息。
下面我重点解读一下对上面内容应该如何理解:
百分比:表示在总的时间内,进程在各种状态下的消耗。例如,100%,就指在这段时间内,这个进程是一直处于运行当中的。
TOTAL:表示了进程的综合占用情况。
**Imp Fg:**Fg是Foreground的缩写。这个我猜测应该是指加载到前台的意思。
Service:标识了是否是服务。
Persistent:标识了是否一直驻留在内存当中,与Service一样,表示一种内存进驻的级别。
Top:标识了是否是顶层进程。
Receiver:标识了是否是广播进程。
(Cached):标识了是否是缓存。这个得重点说明一下,由于Android继承了Linux的思想,认为内存不用白不用,因此会将一定的内容缓存进内存当中,以便需要使用的时候可以快速读取。因此,一些可能并非当前所需的进程内容也会加载进内容当中,如果内存吃紧则会释放掉。根据这个原理,我们将Cached的空间也当做Free来对待。
以MB为单位的数据组:表示占用的内存值大小,以下面顺序进行排列:
minUss-avgUss-maxUss / minPss-avgPss-maxPss
over 数字:指的是内存值的计算一共取了多少次样,这是由系统自己来进行取样的。
我们的目的,是从这些数据中筛选出属于System Service、Frameworks、Applications的内存占用。
方法如下:
我们通过Excel工具,将得到的数据进行整理,筛选出TOTAL对应的内存值不为空的进程。
然后依次将这些进程按照System Service、Frameworks、Applications三种进行分类。具体哪个进程属于哪个,需要根据个人经验来判断,Applications相对容易分辨,System Service和Frameworks可能存在较难区分的情况。
我们提供一份参考分类,基本囊括了经常遇到的进程,参照之可以保证数据的准确和唯一性。参考分类表如下:
System Service: |
---|
system |
后缀为service的非第三方应用进程 |
Framework: |
---|
com.android.systemui |
com.android.phone |
android.process.media |
zygote |
mediaserver |
drmserver |
surfaceflinger |
servicemanager |
App: |
---|
< com.android.inputmethod.latin> |
< com.android.keyguard> |
< com.android.launcher3> |
< com.android.nfc> |
< com.dolby> |
< com.mediatek.bluetooth> |
< com.mediatek.hotknot.service> |
< com.mediatek.mtklogger> |
< com.mediatek.voicecommand> |
< com.mediatek.voiceextension> |
…… |
每一个内存值,我们都选取avgPSS,也即以MB为单位的数据组的倒数第二个值,根据上面分类,就可以得到System Service和Applications的值。
对于Frameworks的值,还需要做一些特殊处理,因为我们从procstats中一般只能得到com.android.systemui、com.android.phone、android.process.media几个进程的大小,而Framework在系统运行时,不止这几个进程,因此,我们根据经验,还添加额外的几个,诸如zygote、mediaserver、drmserver、surfaceflinger、servicemanager在运行时的大小。
这些额外添加的几个进程的大小如何获取呢?答案是从meminfo中获取,因为meminfo获取的是实时的大小,而对于系统来说,一般这几个framework的进程变动不会很大。但是我们也不能直接使用adb shell dumpsys meminfo来取得,因为从返回的结果来看,往往偏大很多,从其结果我们可以看出刚才所提到的进程都是十分重要,比重排名靠前的进程。
这里,我们使用下面命令:
user@swd2:~$ watch -n 2 ' adb shell dumpsys meminfo zygote'
user@swd2:~$ watch -n 2 ' adb shell dumpsys meminfo mediaserver'
user@swd2:~$ watch -n 2 ' adb shell dumpsys meminfo drmserver'
user@swd2:~$ watch -n 2 ' adb shell dumpsys meminfo surfaceflinger'
user@swd2:~$ watch -n 2 ' adb shell dumpsys meminfo servicemanager''
即每隔两秒检测一次,指定进程的内存信息。以zygote为例,结果返回如下面截图:
我们重点关注Pss对应的TOTAL值,这里1628kB即zygote的大小。
同样方法,可以得到mediaserver、drmserver、surfaceflinger、servicemanager的大小,将它们全部计算并入Frameworks中,就能使得Framework的值更加准确和接近实际真实值。
本例中,通过计算,获取的所有结果如下表:
Product | Perso | Modem | Frame Buffer | Kernel | System Service | Frameworks | Applications | Free |
---|---|---|---|---|---|---|---|---|
项目X | ZZ | 24 | 11 | 27 | 34 | 36.6 | 77.1 | 302.3 |
至此,我们的测评就算完成了。但是,这其实只是对RAM当前状况的一个整体把握,而具体应用的状态等,还需要具体分析对待。
简明的一句话,就是本核心算法和测评手法,准确度更高,更加稳定可靠,贴近真实情况。
准确度,是因为本方法使用一段时间内的统计值,优于实时状态下的浮动值。
稳定可靠,是因为统计值能有效避免浮动值的巨大误差,使得每次计算结果都保持在较小的波动范围内。
更贴近真实情况,是因为对Frameworks值做了校优。全部统一使用PSS值也更加符合计算要求。
下面用传统meminfo方法的对比进行说明:
可以看出,使用meminfo方法所测结果,离散度非常高;而使用本优化测评方案,离散度显著降低。
本核心思想和算法,也能够对RAM管理工具的实现提供有益思考。
Android开发框架中也提供了统计内存值的若干方法,这种方式需要进行代码修改,也可以作为未来拓展的一个方向。