Search code examples
androidmultithreadingmemory-leakshandler

How can I resolve warnings about memory leaks when using android Handler?


I'm a beginner studying android. I was using a Handler while learning Thread. By the way, I used Handler to warn memory leak in android studio. I searched a lot of different questions, but I do not have a section that corresponds to my case. How can I resolve the warning about memory leaks when using handlers?

public class HandlerActivity extends AppCompatActivity implements Runnable {

    ProgressBar pb;
    TextView txtRate;
    Button btnStart;
    static int value;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.progress);

        pb = findViewById(R.id.pb);
        txtRate = findViewById(R.id.txtRate);
        btnStart = findViewById(R.id.btnStart);

        btnStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Thread th = new Thread(HandlerActivity.this);
                th.start();
            }
        });
    }

        Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            pb.setProgress(value);
            txtRate.setText("Process : " + value + "%");
        }
    };

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            value = i;

            handler.sendEmptyMessage(0);

            try {
                Thread.sleep(100);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(HandlerActivity.this, "Progress Done !", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

Solution

  • Basically TextView or any other Views hold a reference of a Context object representing the corresponding Activity. When you keep a strong reference of any View in a Thread you are not only storing the View object but also a Context object representing the Activity that created it. Now as Threads are not tied with the Activity life cycle, they will continue to run even after the Activity is destroyed. If this is the case the Thread will hold a destroyed Activity's reference through that View object thus create a memory leak.

    The above mentioned problem can be solved by storing a weak reference of the View object so that GC can garbage collect it when necessary. In the following way you can get rid of memory leak:

    public class HandlerActivity extends AppCompatActivity implements Runnable {
        WeakReference<ProgressBar> pb;
        WeakReference<TextView> txtRate;
        Button btnStart;
        static int value;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            ...
            pb = new WeakReference<>(findViewById(R.id.pb)); // you may require to cast findViewById() to ProgressBar
            txtRate = new WeakReference<>(findViewById(R.id.txtRate));  // you may require to cast findViewById() to TextView
            ...
        }
    
        Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if(pb.get()!=null) pb.get().setProgress(value);
                if(txtRate.get()!=null) txtRate.get().setText("Process : " + value + "%");
            }
        };
    
        @Override
        public void run() {
            for (int i = 1; i <= 100; i++) {
                // Its always recommended to check if activity is running and stop the thread if not running
                if(isFinishing() || isDestroyed()) {
                    return;
                }
            }
        }
    }