旋转和按钮单击可能导致内存泄漏

时间:2015-05-10 19:31:16

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

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

  • 从App开始,将我的设备从纵向连续旋转到横向到纵向到横向....

    • + 300KB 每次轮播的累计内存使用量
  • 有2个输入,每个按钮点击

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

这个问题是,只要应用程序仍处于查看状态,内存就永远不会被释放。

示例:将设备旋转10次,然后单击按钮50次 - >消耗4.5MB内存。如果我将应用程序保持打开并且1小时内没有做任何事情,那么我的应用程序仍将消耗4.5MB的内存;即使很多记忆应该在59分钟前发布 !!

我关注的是为什么在应用程序始终处于可见状态时永远不会释放内存?

我的错误是如何运作的?

注意:该应用程序称为 Contrived 计算器

代码

UI

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; }

}

1 个答案:

答案 0 :(得分:1)

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

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

OperationClick

问题是.listenerOn(this)的实例包含对活动的引用,因为它与Activity一起传递。

旋转设备时,您知道重新创建了onDestroy()。我假设您未使用Activity方法清除听众,因此您最终会泄露OperationClick(每次轮播4次,以便更正)。

Btw上周Leak Canary被来自SquareUp的很棒的家伙释放了。它是一个在Android上查找内存泄漏的漂亮库。这是第一个这样的库,我强烈建议你试试吧!

编辑:要修复泄漏,请不要使用匿名listener个对象。还要添加"清理"在其中摆脱listenerOn()中创建的{{1}}的方法。