java - StrictMode Activity 实例计数违规(2 个实例,1 个预期)在完全空 Activity 轮换时

标签 java android memory-leaks android-activity

相关性仅在于它促使从严格模式中消除任何误报,因为 any 的持续存在使死刑不切实际

在过去的几天里,我一直在追查和修复应用程序中的内存泄漏。到了我相信我已经修复了所有问题的地步,我实现了一个类似于 Android StrictMode and heap dumps 中描述的故障响亮机制。 (启用带有死刑的实例跟踪,拦截关闭错误消息,转储堆,下次应用程序启动时发送求救信号)。当然,所有这些都只是在调试版本中。

言归正传

相信我已经修复了所有 Activity 泄漏,但某些 Activity 仍然会导致屏幕旋转时出现严格模式实例违规警告。奇怪的是,只有应用程序的一些 Activity ,而不是所有的 Activity 都会这样做。

我查看了在发生此类违规行为时进行的堆转储,并查看了相关 Activity 的代码以查找泄漏,但没有得到任何结果。

所以在这一点上,我尝试制作尽可能小的测试用例。我创建了一个完全空白的 Activity (甚至没有布局),如下所示:

package com.example.app;

import android.app.Activity;
import android.os.Bundle;
import android.os.StrictMode;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        StrictMode.setVmPolicy(
                new StrictMode.VmPolicy.Builder()
                        .detectAll()
                        .penaltyLog()
                        .build());
        StrictMode.setThreadPolicy(
                new StrictMode.ThreadPolicy.Builder()
                        .detectAll()
                        .penaltyDeath()
                        .penaltyLog()
                        .build());
        super.onCreate(savedInstanceState);
    }

}

为了完整起见, list :

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.app" >

    <application
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >
    <activity
        android:name="com.example.app.MainActivity"
        android:label="@string/app_name" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    </application>

</manifest>

我打开 Activity (设备手持肖像)。我旋转到横向,然后又回到纵向,此时我在 LogCat 中从 StrictMode 看到这个:

01-15 17:24:23.248: E/StrictMode(13867): class com.example.app.MainActivity; instances=2; limit=1 01-15 17:24:23.248: E/StrictMode(13867): android.os.StrictMode$InstanceCountViolation: class com.example.app.MainActivity; instances=2; limit=1 01-15 17:24:23.248: E/StrictMode(13867): at android.os.StrictMode.setClassInstanceLimit(StrictMode.java:1)

堆转储

此时我使用 DDMS 手动获取堆转储。以下是 MAT 中的两个实例:

1

这是泄露的,从它到 GC 根的路径的第一部分:

2

请注意,无论我在纵向和横向之间旋转多少次,实际实例的数量永远不会超过两个,而预期的实例数量永远不会超过一个。

任何人都可以理解泄漏吗?它甚至是真正的泄漏吗?如果是这样,它可能一定是一个 Android 错误。如果不是,我能看到的唯一其他可能是 StrictMode 中的错误。

好的答案可能包括

  1. 如果这是一个真正的泄密,解释它是如何发生的,以及如果可以采取任何行动来修复它或阻止 StrictMode 对此类情况启动死刑(回想一下,误报/中立会导致死刑)不切实际)。
  2. 如果这不是真正的泄密,解释为什么 StrictMode 不这么认为,以及如果可以采取任何行动来修复它或阻止 StrictMode 对此类案件启动死刑(请记住,误报/中立会使死刑不切实际)。
  3. 在这两种情况下,假设并非所有 Activity 都发生这种情况(我正在处理的应用程序中的大多数 Activity 在轮换时不会产生此类违规行为)。

我已经查看了 StrictMode 源,并没有发现任何明显错误 - 正如预期的那样,它会在将额外实例视为违规之前强制执行 GC。

阅读 StrictMode 源代码的良好入口点是:

  1. http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/os/StrictMode.java#StrictMode.trackActivity%28java.lang.Object%29
  2. http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/os/StrictMode.java#StrictMode.InstanceTracker.finalize%28%29
  3. http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/os/StrictMode.java#StrictMode.incrementExpectedActivityCount%28java.lang.Class%29
  4. http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/os/StrictMode.java#StrictMode.decrementExpectedActivityCount(java.lang.Class)

全面披露

我只在一台设备上完成了这些测试,一台运行 CyanogenMod 11 快照 M2 (http://download.cyanogenmod.org/get/jenkins/53833/cm-11-20140104-SNAPSHOT-M2-jfltexx.zip) 的 Galaxy S4,但我无法想象 CyanogenMod 在 Activity 管理方面会偏离 KitKat。

其他 StrictMode 来源:

  1. Activity 的 instanceTracker 实例只是一个最终的实例字段:http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/app/Activity.java#Activity.0mInstanceTracker
  2. 预期 Activity 实例计数增加的地方:http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/app/ActivityThread.java#2095
  3. 预期 Activity 实例计数减少的地方:http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/app/ActivityThread.java#3485

最佳答案

我选择 2 号门:这不是真正的泄漏。

更具体地说,它只是与垃圾收集有关。三个线索:

  1. GC 根路径结束于 FinalizerReference ,这与GC密切相关。它基本上处理对符合 GC 条件的对象调用 finalize() 方法——这里有一个,即 ViewRootImpl.WindowInputEventReceiver 实例,它扩展了 InputEventReceiver,它确实有一个 finalize() 方法。如果这是“真正的”内存泄漏,那么该对象将符合 GC 条件,并且应该至少有一个其他路径到 GC 根。

  2. 至少在我的测试用例中,如果我 拍摄堆快照之前强制 GC,那么只有 一个MainActivity(而如果我不这样做拍摄快照,则有两个)。看起来从 DDMS 强制 GC 实际上包括调用所有终结器(很可能通过调用应该释放所有这些引用的 FinalizerReference.finalizeAllEnqueued()

  3. 我可以在装有 Android 4.4.4 的设备中重现它,但不能在具有新 GC 算法的 Android L 中重现(诚然,这最多只是间接证据,但与其他证据一致)。

为什么某些 Activity 会发生这种情况,而其他 Activity 则不会?虽然我不能肯定地说,构造一个“更复杂”的 Activity 很可能会触发 GC(仅仅是因为它需要分配更多的内存),而像这样的简单 Activity “通常”不会。但这应该是可变的。

为什么 StrictMode 不这样认为?

StrictMode 中有大量关于此案例的注释,请查看decrementExpectedActivityCount() 的源代码。然而,它看起来并没有完全按照他们的预期工作。

    // Note: adding 1 here to give some breathing room during
    // orientation changes.  (shouldn't be necessary, though?)
    limit = newExpected + 1;

    ...

    // Quick check.
    int actual = InstanceTracker.getInstanceCount(klass);
    if (actual <= limit) {
        return;
    }

    // Do a GC and explicit count to double-check.
    // This is the work that we are trying to avoid by tracking the object instances
    // explicity.  Running an explicit GC can be expensive (80ms) and so can walking
    // the heap to count instance (30ms).  This extra work can make the system feel
    // noticeably less responsive during orientation changes when activities are
    // being restarted.  Granted, it is only a problem when StrictMode is enabled
    // but it is annoying.
    Runtime.getRuntime().gc();

    long instances = VMDebug.countInstancesOfClass(klass, false);
    if (instances > limit) {
        Throwable tr = new InstanceCountViolation(klass, instances, limit);
        onVmPolicyViolation(tr.getMessage(), tr);
    }

更新

其实我已经进行了更多的测试,使用反射调用StrictMode.incrementExpectedActivityCount(),我发现了一个非常奇怪的结果,它并没有改变答案(它仍然是#2)但我认为提供了一个额外的线索。如果您增加 Activity 的“预期”实例数(例如,增加到 4 个),那么仍然会发生严格模式违规(声称存在 5 个实例),每 4 次轮换一次。 p>

由此我得出结论,对 Runtime.getRuntime().gc() 的调用是实际释放这些可终结对象的原因,并且该代码仅在超出设置限制后才运行.

如果可以采取任何措施来修复它怎么办?

虽然它不是 100% 万无一失,但在 Activity 的 onCreate() 中调用 System.gc() 可能会解决这个问题(它确实在我的测试)。然而,Java 的规范清楚地表明垃圾收集不能被强制(这只是一个提示)所以我不确定我是否会相信死刑,即使有这个“修复”...

您可以将它与通过调用反射手动增加 Activity 实例计数的限制相结合。但这似乎是一个非常粗暴的 hack:

Method m = StrictMode.class.getMethod("incrementExpectedActivityCount", Class.class);
m.invoke(null, MainActivity.class);

(注意:确保只在应用程序启动时执行一次)。

关于java - StrictMode Activity 实例计数违规(2 个实例,1 个预期)在完全空 Activity 轮换时,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21145261/

相关文章:

android - 如何在 build.gradle 中指定 admob 库?

c++ - 扩展类时内存泄漏

java 程序,具有三个按钮,每个按钮显示不同的文本,按下时将在文本框中显示按钮上的文本

java - 在 Windows 上将 Ctags 与 Gvim 结合使用

java - 我无法关闭扫描仪

c - 如何使用leaks命令行工具查找内存泄漏?

android - 线程对象在完成后未被垃圾回收

java - 替换字符串中的所有反斜杠

android - Kotlin:将 'cascade if' 替换为 'when' 与其他变量进行比较

java - Google Play 'Open Testing'(= 抢先体验): 'Release not live' error message