【问题标题】:Possible memory leak with rotation and button click旋转和按钮单击可能导致内存泄漏
【发布时间】:2015-07-21 06:08:52
【问题描述】:

我认为我的应用占用了 GC 应该能够重新分配的额外内存。我不知道这些是否会被视为内存泄漏,但我注意到有 2 个地方可能存在问题

  • 从应用程序开始,不断地将我的设备从纵向旋转到横向,再从纵向旋转到横向......

    • +300KB 每次旋转的累积内存使用量
  • 使用 2 个输入,每个按钮都点击

    • +30KB 每次点击的累积内存使用量

这个问题是只要应用还在视图中,内存就永远不会释放。

示例:旋转设备 10 次,点击按钮 50 次 -> 消耗 4.5MB 内存。如果我让应用程序打开并且 1 小时不做任何事情,那么我的应用程序仍然会消耗 4.5MB 的内存;尽管很多内存应该在大约 59 分钟前释放!!

我担心的是为什么在应用始终可见时内存从未释放?

这是我的错误吗?

注意:该应用名为人为的计算器

代码

用户界面

public class Calculator extends AppCompatActivity implements ICalculatorInteraction {

    private EditText txtNumber1, txtNumber2, txtResult;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_calculator);

        Button btnAdd = (Button) findViewById(R.id.btnAddition);
        Button btnSub = (Button) findViewById(R.id.btnSubtract);
        Button btnMul = (Button) findViewById(R.id.btnMultiple);
        Button btnDiv = (Button) findViewById(R.id.btnDivide);

        txtNumber1 = (EditText) findViewById(R.id.txtNumber1);
        txtNumber2 = (EditText) findViewById(R.id.txtNumber2);
        txtResult = (EditText) findViewById(R.id.txtResult);

        btnAdd.setOnClickListener(new OperationClick(Add).listenerOn(this));   
        btnSub.setOnClickListener(new OperationClick(Subtract).listenerOn(this));
        btnMul.setOnClickListener(new OperationClick(Multiply).listenerOn(this));
        btnDiv.setOnClickListener(new OperationClick(Divide).listenerOn(this));

    @Override
    public String getFirstNumber() { return valueOf(this.txtNumber1); }

    @Override
    public String getSecondNumber() { return valueOf(this.txtNumber2); }

    @Override
    public void updateResult(String result) { this.txtResult.setText(result); }

    private String valueOf(EditText textbox) {

        String text = textbox.getText().toString();

        if (text.isEmpty()) {

            textbox.setText("0");
            return "0";

        }

        return text;
    }

// default android activity methods

}

监听器逻辑

public class OperationClick {

    private BinaryOperation operation;   // ENUM - advanced
    private View.OnClickListener listener;

    public OperationClick(final BinaryOperation operation) { this.operation = operation; }

    public View.OnClickListener listenerOn(final ICalculatorInteraction UI) {

        if (listener != null) return listener;
        return listener = new View.OnClickListener() {

            @Override
            public void onClick(View v) {

                double  num1, num2, total;
                String result, sign;

                num1 = Double.parseDouble(UI.getFirstNumber());
                num2 = Double.parseDouble(UI.getSecondNumber());
                total = operation.execute(num1, num2);
                sign = operation.getSymbol();

                result = String.format("%s %s %s = %s", num1, sign, num2, total);

                UI.updateResult(result);

            }

        };

    }

计算逻辑

public enum BinaryOperation {

    Add      ("+") { @Override double execute(final double a, final double b) { return a + b; } },
    Subtract ("-") { @Override double execute(final double a, final double b) { return a - b; } },
    Multiply ("×") { @Override double execute(final double a, final double b) { return a * b; } },
    Divide   ("÷") { @Override double execute(final double a, final double b) { return a / b; } };

    private final String symbol;

    abstract double execute(double a, double b);

    BinaryOperation(String symbol) { this.symbol = symbol; }

    public String getSymbol() { return this.symbol; }

}

【问题讨论】:

  • 强制GC释放内存还是使用量还在?
  • 调试会话 41 分钟后没有其他用途,强制 GC 将删除内存。但是为什么在 Android Studio 内存工具控制台 41 分钟内没有调用 GC 呢?
  • 不要太多使用...如果程序继续运行,它将在内存中创建和删除内容,碎片最终将强制gc。只要它保持低内存使用率,就没有理由

标签: android memory memory-management memory-leaks reference


【解决方案1】:

最好使用Eclipse MAT 跟踪内存泄漏(不用担心名称,它也适用于来自 Android Studio 的内存转储)。该工具的学习曲线相当陡峭,但它是您可以想象的用于跟踪此类内存问题的最强大的工具。

您确实在这一行中造成了内存泄漏:

btnAdd.setOnClickListener(new OperationClick(Add).listenerOn(this));

问题在于OperationClick 的实例持有对活动的引用,因为它是通过.listenerOn(this) 传递的。

当您旋转设备时,您知道Activity 已重新创建。我假设您没有清除 onDestroy() 方法中的侦听器,因此您最终会得到泄漏的 Activity(每次旋转 4 次是正确的)。

顺便说一句,上周 Leak Canary 是由 SquareUp 的优秀兄弟发布的。这是一个用于在 Android 上查找内存泄漏的漂亮库。此类的第一个库,强烈推荐你试试!

编辑:要修复您的泄漏,请勿使用匿名 OperationClick 对象。还要在其中添加一个“清理”方法,您可以在其中删除您在 listenerOn() 中创建的 listener

【讨论】:

  • 实际上,LeakCanary 是我发布此内容的原因之一。我得到一个泄漏,说明匿名点击侦听器正在泄漏。aftrr 看起来我认为这是因为传入的“this”参数。在我确实有一个 'onDestroy' 方法执行此 'btnAdd.setOnClickListener(null);' 之前它被调用 - 通过断点验证 - 但没有释放内存。所以我删除了方法
  • 添加了如何修复内存泄漏的建议 .. 如果您还没有完成 :)
  • 为了快速测试,我创建了 4 个 OperationClick 状态对象。并做到了...'btnAdd.setOnClickListener((add = new OperationClick(Add)).listenerOn(this));'然后我重写了“onDestroy”方法并调用了“add = null;”这仍然没有删除任何内存。所以我必须删除“OperationClick”类中的监听器?不只是使 OperationClick 引用无效?
  • 是的,没错,我的想法是在 OperationClick 类中添加一个“cleanup()”方法。所以在 onDestroy() 中有一些 addListener.cleanup(),在 tern 中应该有 listener = null。
  • 我做到了。 OperationClass 中的清理方法。在 onDestroy 中对每个侦听器引用(addListener 等)调用清理方法。但是,内存使用量仍在增加。
猜你喜欢
  • 1970-01-01
  • 2013-07-23
  • 1970-01-01
  • 2010-12-16
  • 1970-01-01
  • 1970-01-01
  • 2014-04-19
  • 2014-06-20
  • 2015-07-30
相关资源
最近更新 更多