游戏闪退不能只看Crash

游戏闪退不能只看Crash,因为“低内存被杀”导致的闪退现在还无法监测。

作者:敏华,腾讯前台开发工程师

商业转载请联系腾讯WeTest获得授权,非商业转载请注明出处。

| 导语 公司应用,现在说的闪退率一般根据bugly或者TQM上报的crash数据来计算,TDR标准中也会要求游戏的闪退率低于2%。但是,真正的闪退数据可能远不止当前统计的这个数据,因为“低内存被杀”导致的闪退现在还无法监测。“低内存被杀”并不是bug,但是对用户而言就是闪退,与bug引起的闪退并没有任何区别。从WeTest测试统计结果来看,低内存被杀占比约30%。

1 低内存闪退

1.1 被系统杀死原因

Android系统中,应用在退出时,其实并没有完全从内存中完全清除。你退出应用之后,还能在任务窗口里面看到你的应用,点击能够重新启动这个应用。启动运行一个程序需要较大的开销,Android这么做的主要目的就是为了再次启动的时候减少开销。
Android 系统将尽量长时间地保持应用进程,但为了新建进程或运行更重要的进程,最终需要清除旧进程来回收内存。 为了确定保留或终止哪些进程,系统会根据进程中正在运行的组件以及这些组件的状态,将每个进程放入“重要性层次结构”中。 必要时,系统中的LowMemoryKiller会首先消除重要性最低的进程,然后是重要性略逊的进程,依此类推,以回收系统资源。

 

  1. 前台进程
    用户当前操作所必需的进程,如正在玩的游戏。通常,在任意给定时间前台进程都为数不多。只有在内在不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。 此时,设备往往已达到内存分页状态,因此需要终止一些前台进程来确保用户界面正常响应。
  2. 可见进程
    可在屏幕上显示但不在前台运行,比如一个前台进程以对话框的形式显示在该进程前面。典型的如输入法。
  3. 服务进程
    正在运行已使用 startService() 方法启动的服务且不属于上述两个更高类别进程的进程。尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。
  4. 后台进程
    包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。这些进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。
  5. 空进程
    不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。

LowMemoryKiller主要通过进程的oom_adj(/proc/(pid)/oom_adj)和oom_score_adj(/proc/(pid)/oom_score_adj)来确定回收进程。oom_adj与oom_score_adj其实是相关的,所以准确来说只需要关注oom_adj即可。LowMemoryKiller会查看当前系统空闲内容

///lowmemorykiller.c
static unsigned long lowmem_scan(struct shrinker *s, struct shrink_control *sc){
    .....
    step 1:获取当前空闲内存
    //基本对应/proc/meminfo 中的 free size
    int other_free = global_page_state(NR_FREE_PAGES) - totalreserve_pages;
    //基本对应/proc/meminfo 中的 cache size
    int other_file = global_page_state(NR_FILE_PAGES) -
                        global_page_state(NR_SHMEM) -
                        total_swapcache_pages();

    ....
    //step 2:找出阀值以上最大的最小min_score_adj
    for (i = 0; i < array_size; i++) {
        minfree = lowmem_minfree[i];
        if (other_free < minfree && other_file < minfree) {
            min_score_adj = lowmem_adj[i];
            break;
        }
    }


    //step 3: oom_score_adj最小,且内存占用最大的进程优先清除
    for_each_process(tsk) {
        oom_score_adj = p->signal->oom_score_adj;
        if (oom_score_adj < min_score_adj) {
            task_unlock(p);
            continue;
        }
        tasksize = get_mm_rss(p->mm);
        task_unlock(p);
        if (tasksize <= 0)
            continue;
        if (selected) {
            if (oom_score_adj < selected_oom_score_adj)
                continue;
            if (oom_score_adj == selected_oom_score_adj &&
                tasksize <= selected_tasksize)
                continue;
        }
        selected = p;
    }

step 1:获取当前系统空闲内存,adb shell cat /proc/meminfo可查看

step 2:最小的阀值oom_adj,adb shell cat /sys/module/lowmemorykiller/parameters/adj查看oom_adj划分,adb shell /sys/module/lowmemorykiller/parameters/minfree查看oom_adj对应的最小内存阀值。如果当前内存只有50M了,那么oom_adj>=1的进程就有可能会被清除。

step 3:选择优先级最低oom_adj最小,且内存占用最大的进程优先清除。adb shell /proc/(pid)/oom_adj可查看当前进程adj。

 

1.2 游戏最危险时刻

腾讯的游戏在运行过程中,可能会经历哪些adj呢?
1、正常运行,前台应用,adj=0

 

2、登录时(按home键、来电话时),前一个应用,adj=7

 

1.3 wetest定位low_memory_kill

AMS管理进程,在初始化的时候会创建一个应用进程讣告接收对象。当应用进程退出时,讣告对象AppDeathRecipient的binderDied方法会被调用。AMS会查看本次进程的退出时因为LowMemoryKiller引起的,还是其他原因。如果是LMK引起的,对在events里面打印am_low_memory:剩余进程数。wetest通过日志检测的方式,被测试应用进程退出后是否立马紧跟am_low_memeory来判断,进程退出由内存过低引起的。

///ActivityManagerService.java
final void appDiedLocked(ProcessRecord app, int pid, IApplicationThread thread) {
    .........
            boolean doLowMem = app.instrumentationClass == null;
            boolean doOomAdj = doLowMem;
            if (!app.killedByAm) {
                Slog.i(TAG, "Process " + app.processName + " (pid " + pid
                        + ") has died");
                mAllowLowerMemLevel = true;
            } else {
                // Note that we always want to do oom adj to update our state with the
                // new number of procs.
                mAllowLowerMemLevel = false;
                doLowMem = false;
            }
            EventLog.writeEvent(EventLogTags.AM_PROC_DIED, app.userId, app.pid, app.processName);
            if (DEBUG_CLEANUP) Slog.v(
                TAG, "Dying app: " + app + ", pid: " + pid
                + ", thread: " + thread.asBinder());
            handleAppDiedLocked(app, false, true);
            if (doOomAdj) {
                updateOomAdjLocked();
            }
            if (doLowMem) {
                //判断是不是LMK
                doLowMemReportIfNeededLocked(app);
            }
......

2 解决低内存被杀

其实低内存被杀对于游戏这种不能被中断,长期在作为前台应用的app来说,除了降低内存是没有比较好的途径的。降低内存是根本,但是内存很多时候并不是很好降低的。这个时候,我们需要其他措施来做预防。
另外,在QQ和微信登陆过程中游戏特别容易被杀。但是,手Q与微信在登陆成功之后,如果发现游戏挂了会重新拉起游戏。

2.1 预估游戏最低配置手机

这里说的最低值撇开流畅度,单值因为低内存闪退的情况。通过查看wetest平台几百台手机,最后/sys/module/lowmemorykiller/parameters/minfree在非root情况下有权限读取的为165台手机。前台应用最大阀值80MB,最低阀值8MB,平均阀值43.6MB。值得注意的是大部分新出的手机,均没有读取minfree的权限。

绝大多数手机集中在48MB:占比超过64.8%
最低手机为Vivo Y13:自身内存只有463M
最高手机为荣耀3X等:自身内存为2G

 

PS:对于内存过少的手机,可以在游戏开始时给用户提醒,降低用户这方面的预期。如果游戏在运行的最高峰时物理内存占比,会达到前台应用被杀的阀值,可能就会存在闪退风险。系统最大空闲内存-游戏最高内存<被杀阀值,即游戏的危险性。系统最大空闲内存>当前系统空闲内存,因为在运行过程中,低优先级的应用还会被杀,还会继续释放一部分内存。后续,可以对wetest平台所有手机做统计,看低优先级应用被杀后,Android系统内存空闲情况。

 

2.2 巧用onTrimMemory和onLowMemory

Application.onLowMemory是在4.0之前的一个接口,后台进程被杀完后回调一次。Application.onTrimMemory(int level)是在4.0之后提供的一个接口,任何实现了ComponentCallbacks2接口的类都可以重写实现这个回调方法。OnTrimMemory的主要作用就是 指导应用程序在不同的情况下进行自身的内存释放,以避免被系统直接杀掉,提高应用程序的用户体验。
Application.onTrimMemory总共有7个等级,其中在运行过程中可能会收到的等级为3个:

  • TRIM_MEMORY_RUNNING_MODERATE
    应用正在运行,并且不会被杀死,但系统已经处于低内存状态,并且开始杀死LRU缓存里的进程。
  • TRIM_MEMORY_RUNNING_LOW
    应用正在运行,并且不会被杀死,但系统处于内存更低的状态,所以应该释放无用资源以提高系统性能,当前的低内存已经影响到你的app体验了。
  • TRIM_MEMORY_RUNNING_CRITICAL
    应用还在运行,但系统已经杀死了LRU缓存里的大多数进程,所以应该在此时释放所有非关键的资源。如果内存还是无法降低,高优先级保持的应用也存在被杀的风险。
  • TRIM_MEMORY_UI_HIDDEN 
    表示应用程序的所有UI界面被隐藏了,即用户点击了Home键或者Back键导致应用的UI界面不可见。可以释放资源,也可以保存游戏状态。
    当onTrimMemory被调用的时候,应该考虑降低游戏的内存资源,如降低图片质量等方式。当onTrimMemory被调用,且处于TRIM_MEMORY_RUNNING_CRITICAL时应该保持游戏状态,避免游戏玩家过程丢失,降低玩家的时间损失。
  •  

PS:针对手游的性能优化,腾讯WeTest平台的Cube工具提供了基本所有相关指标的检测,为手游进行最高效和准确的测试服务,不断改善玩家的体验。目前功能还在免费开放中。

点击地址:http://wetest.qq.com/cube立即体验!

3 引用

https://testerhome.com/topics/4097
https://developer.android.com/guide/components/processes-and-threads.html?hl=zh-cn
http://wiki.jikexueyuan.com/project/deep-android-v2/activity.html
http://www.codeceo.com/article/android-ontrimmemory-mem.html
https://developer.android.com/reference/android/content/ComponentCallbacks2.html

 


双11活动马上开启,我们准备了百万Q币等你来领,据说每一个单身技术男都缺一桶Q币去勾搭美术妹子!

 

活动详情:http://wetest.qq.com/notice/view/37.html

最新文章
1自查小程序4大安全隐患!文末免费赠送小程序安全扫描专业版! 腾讯WeTest现面向小程序开发者开放免费申请使用小程序安全扫描专业版,助您提前发现全面的安全漏洞。扫描文中问卷二维码或点击问卷链接,即可报名参与免费领取活动。
2浅谈渗透测试服务在泛互行业带来的价值 在泛互联网行业中,渗透测试服务对于保障企业的网络安全至关重要。
3云手机卡顿/无特定设备/商店登录受限怎么办?WeTest专有云帮您解决! 公有云满足了大量小微企业、个人的测试需求;随着客户深入使用,也遇到了一系列新问题。本篇将对几个常见问题予以解答
4小程序安全相关标准和规章制度 针对小程序安全相关标准及规章制度的调研
5浅谈渗透测试及红蓝攻防对抗中的差异 渗透测试和红蓝攻防对抗已经成为企业保障网络安全的重要手段。
购买
客服
反馈