由于ReceiverDispatcher和Activity实例的数量导致的robolectric内存泄漏

时间:2016-08-08 15:53:52

标签: android memory-leaks robolectric

我正在为我公司的项目编写单元测试。最近,我们的Jenkins构建在单元测试gradle任务期间开始失败,出现OutOfMemory异常(由超出GC overhead限制或Java heap space引起)。在我的本地机器单元上进行测试,但有时甚至使用3.5 GB的内存。项目中有超过1000个测试,其中数百个测试创建Activity实例。

我已经进行了堆转储(在大约1000次测试通过的时刻)并使用VisualVM进行了检查。由于内存使用量和堆大小都在不断增加,我怀疑某处发生了内存泄漏。

我使用堆转储运行Eclipse Memory Analyzer,#1泄漏可疑是非常多ReceiverDispatcher个实例,大约255k个(使用> 500 MB内存)。此外,堆包含相同数量的IntentReceiverLeaked个实例,稍多(258k)shadowBroadcastReceiver个实例,以及两倍(约127k)AccessibilityManagerServiceLockPatternUtils个实例。

这对我来说很奇怪,因为我发现只有一个BroadcastReceiver在项目中动态注册(MainActivity),并且它在Activity的onDestroy()方法中正确注销。

由于转储中有大约300个Activity实例,我怀疑泄漏活动是导致泄漏的主要原因。此外,Eclipse Memory Analyzer将ShadowContextImpl列为#2问题嫌疑人,实例略多于活动(但使用的内存大约为500 MB)。

使用Robolectric.setupActivity()创建测试中的活动,并在tearDown()上调用相关的生命周期方法

//Android Annotations are in use
protected ActivityController<MainActivity_> activityController;
protected MainActivity_ mainActivity;

@Before
public void setUp() throws Exception {
    activityController = Robolectric.buildActivity(MainActivity_.class).setup();
    mainActivity = activityController.get();
}

@After
public void tearDown() throws Exception {
    try {
        if (activityController != null) {
            activityController.pause()
                    .stop()
                    .destroy();
        }
    } catch (Throwable th){}

    mainActivity = null;
    activityController = null;
}

对于与Fragment相关的测试,Fragment以常规方式创建并附加到Activity(而不是使用FragmentTestUtil)

public static void startFragment(Fragment fragment, FragmentActivity activity) {
    shadowOf(Looper.getMainLooper()).pause();
    FragmentManager fragmentManager = activity.getSupportFragmentManager();
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.add(fragment, null);
    fragmentTransaction.commitAllowingStateLoss();
    shadowOf(Looper.getMainLooper()).runToEndOfTasks();
}

(...)    
Activity activity = Robolectric.setupActivity(MainActivity_.class);
MyFragment fragment = MyFragment_.builder().build();
startFragment(fragment, activity);
(...) 

这些泄漏的原因(以及如何修复)可能是什么原因?活动和碎片的启动方式是否与泄漏有关?

1 个答案:

答案 0 :(得分:0)

对于它的价值-在尝试将我们的应用程序迁移到RoboElectric 4.3和AndroidX时,我一直在尝试修复OOM错误-在进行并重构2000测试以确保它们完成并处置了我可以观看的片段和活动之前VisualVM中的gradleRunner进程会最大程度地利用它的可用堆,并且测试运行的速度越来越慢,直到它们停止或因OOM错误而崩溃

这使我重新工作,将其添加到应用程序build.grade中:

 testOptions {
        unitTests {
            all {
                maxParallelForks = Runtime.runtime.availableProcessors()
                forkEvery = 100
            }
        }
}

意味着在一个进程中将仅运行100个测试,因此它们永远不会达到堆大小。