RN 容器的内存泄露排查手记

DosLin  |  2018. 11. 09   |  阅读 520 次
react native 无线开发

背景

我们线上的混合 App(此指非纯原生集成了 RN 业务的应用) 一直存在这么一个问题:在反复进入退出一个 RN 页面时,RN 容器大概率崩溃,adb log 没有可见的异常日志。进出次数取决于手机内存大小,几十次到上百次不等。这个现象表现为内存越大的手机意味着对 RN 容器的容忍性越高,很容易据此表象推测出产生了内存泄露,导致应用申请的内存超过了虚拟机的配额引起了崩溃。

排查过程

解决问题的第一步是先能完整还原现场,这样我们之后才能验证问题是否得到了妥善的解决。根据上面的描述我们需要至少重复操作几十次退出与进入来重现问题,这对正处于周五打算迎接周末的码农是不可接受的。我们需要脚本要解决这种重复性的机械劳动。

使用 UI 自动化测试的方式来实现是一个很好的思路,但引入自动化测试框架或者临时搭建 Appium 之类的测试化平台带来的负重感太高,不符合我们肥宅小步快跑的原则。

在这里就要推荐一下猪厂的 Airtest 工具了,不需要额外的配置,一个安装包就能解决我们的问题。其他高级用法请参考官方文档,这里我们只是用到了点击与系统按键的两个简单事件。脚本编写如下:

# -*- encoding=utf8 -*-
__author__ = "doslin"

from airtest.core.api import *

auto_setup(__file__)

from poco.drivers.android.uiautomation import AndroidUiautomationPoco  
poco = AndroidUiautomationPoco(use_airtest_input=True, screenshot_each_action=False)

count = 0  
while True:  
    poco(text="RNDetection").click()
    sleep(5)
    keyevent("BACK")
    count += 1
    print(count)
    sleep(5)

我们点击了一个 RN 页面后,等待了 5s 后确保屏幕内容切换完毕后进行了元素识别和操作。之后进行页面出栈操作,同时输出我们的操作次数。

Airtest

解决过程

打开 Android Studio 的 Profiler 的监测内存使用状态,可以发现即便是 RN 页面退出之后部分内存也没有得到很好的释放。随着不断反复的进出动作,内存的使用量不断攀升就会引起上述的问题。翻过这些规律性出现的小山峰,我们就可以听到应用崩溃的故事。

profile

Airtest2

中断页面操作,点击 Dump Java Heap,按包名排列,找到 SCRNActivity (这是我们 RN 容器的活动视图),它的引用一直被持有没有被释放。点击右侧的列表可以查看详情,定位到底是哪些对象持有它的强引用没有被释放。

heap1

查看列表中的引用都被我们继承 ReactRootView 的 SCReactRootView 的对象一直持有。但明明父类中在视图退出的 onDestroy 方法中已经对 RootView 进行了释放,为什么没有生效呢?

heap2

让我们在 Destory 方法上加个断点再跑一遍到底发生了什么。

destroy

哦豁,ReactRootView 为空难怪没有可以执行释放对象的操作。肿么肥事呢?一句话总结:父类中为空的原因是我们在自定义的 RootView 中有定义了同名的成员对象,我们需要把这个销毁方法实现同步到子类中。

看看改完之后的引用,实例列表干净多了。按这个思路我们再把剩下的两个持有解决了就可以收(xia)工(ban)了。

那么留个思考题,不妨思考下:Activity 被持有 this 对象引起的内存泄露该如何处理呢?

heap4

加餐

有同学反应上面这些排查操作,我AS版本低处理不了,最重要的是看着容易营养跟不上该怎么办?不要紧,Leakcanary 了解一下。 leakcanary

分享到

   
Nara: 大搜车的注解收集器