Possible memory leak when rotating and pressing the button

I think my application is grabbing additional memory that the GC should be able to reallocate. I don't know if they will count as memory leaks, but there are 2 places that I noticed possible problems.

  • From app launch, constantly rotate my device from portrait to landscape to portrait to landscape ....

    • + 300 KB Cumulative memory usage per revolution
  • With two inputs each button

    • + 30KB Cumulative memory usage per click

This problem is that the memory is never opened while the application is still in view.

Example. Rotate the device 10 times and press the buttons 50 times → consumes 4.5 MB of memory. If I leave the application open and do nothing for 1 hour, my application will still consume 4.5 MB of memory; although most of the memory should have been released about 59 minutes earlier!

My concern is why the memory is never released while the app is always displayed?

Am I wrong about how it works?

NOTE. The app is called Proprietary Calculator

Code

User interface

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

}

      

Listener logic

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

            }

        };

    }

      

Calculation logic

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

}

      

+3


source to share


1 answer


Tracking memory leaks is best done with the Eclipse MAT (don't worry about the name, it works great with memory dumps from Android Studio as Well). The tool has a pretty steep learning curve, but it's the most powerful thing you can imagine for tracking down memory problems like this.

Indeed, you are causing a memory leak on this line:

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

      

The problem is that the instance OperationClick

contains a link to the activity as it passed through .listenerOn(this)

.



When you rotate your device you know you are Activity

recreated. I am assuming that you are not clearing the listeners in the method onDestroy()

so that you end up losing the leak Activity

(4 times per turn, to be right).

Btw last week Leak Canary was released by the awesome guys at SquareUp. It is a great library for detecting memory leaks on Android. The first library of its kind, I highly recommend you give it a try!

EDIT: For fixing the leak, don't use anonymous objects OperationClick

. Also add a "cleanup" method to them where you get rid of the listener

one you created in listenerOn()

.

+1


source







All Articles