1

I want to use a WeakReference in my AsyncTask to not leak memory. With the log statements I can see, that the activity reference I get returned from the get method is indeed null if I leave the activity over the back button. However, when I rotate the device, I get returned a working reference, which means I leak memory. Any idea why this happens?

Update: It starts showing null as soon as I leave the activity AFTER rotating the device and then open a different app. Could it be that the system just does not see a necessity in cleaning up because my activity is so simple?

public class MainActivity extends AppCompatActivity {
    private ProgressBar progressBar;

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

        progressBar = findViewById(R.id.progress_bar);
    }

    public void startAsyncTask(View v) {
        ExampleAsyncTask exampleAsyncTask = new ExampleAsyncTask(this);
        exampleAsyncTask.execute(210);
    }

    private static class ExampleAsyncTask extends AsyncTask<Integer, Integer, String> {
        private WeakReference<MainActivity> activityReference;

        ExampleAsyncTask(MainActivity context) {
            activityReference = new WeakReference<>(context);
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();

            MainActivity activity = activityReference.get();
            if (activity == null || activity.isFinishing()) {
                return;
            }

            activity.progressBar.setVisibility(View.VISIBLE);
        }

        @Override
        protected String doInBackground(Integer... integers) {
            for (int i = 0; i < integers[0]; i++) {
                publishProgress((i * 100) / integers[0]);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            return "Finished";
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);

            MainActivity activity = activityReference.get();
            if (activity == null || activity.isFinishing()) {
                Log.i("activity", "activity = null");
                return;
            } else
                Log.i("activity", "activity not null");
            activity.progressBar.setProgress(values[0]);
        }

        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);

            MainActivity activity = activityReference.get();
            if (activity == null || activity.isFinishing()) {
                return;
            }

            activity.progressBar.setProgress(0);
            activity.progressBar.setVisibility(View.INVISIBLE);
            Toast.makeText(activity, s, Toast.LENGTH_SHORT).show();
        }
    }
}
lelloman
  • 13,883
  • 5
  • 63
  • 85
Florian Walther
  • 6,237
  • 5
  • 46
  • 104
  • Can we see your Activity code and manifest declaration of that activity? – Saeed Entezari Mar 11 '18 at 12:19
  • the activity is created and destroyed on screen-rotation. The java object of the activity remains the same. – Rahul Mar 11 '18 at 12:22
  • The purpose of WeakReference is to let go off the reference when the activity is destroyed – Florian Walther Mar 11 '18 at 12:23
  • 1
    The statement made by @RahulKumar is not correct: after orientation change a new java object is being created for the new activity. – azizbekian Mar 11 '18 at 12:35
  • 1
    @azizbekian: thanks for pointing that out. – Rahul Mar 11 '18 at 12:38
  • @azizbekian: can this be happening because of https://stackoverflow.com/a/7048279/1042124 here the op is doing some task in onProgressUpdate – Rahul Mar 11 '18 at 12:41
  • No, it returns null properly if I leave the activity over the back button. This is how it should be because I only have the strong reference in the scope of the onProgressUpdate method. For some reason it does not work when I rotate the device, which makes no sense to me – Florian Walther Mar 11 '18 at 12:43
  • No, I create and execute it on button click – Florian Walther Mar 11 '18 at 12:52
  • No, the system destroys the activity – Florian Walther Mar 11 '18 at 12:57
  • Update: It shows null if I leave the activity after rotating it and then open a different app. Could it be that the system just doesnt clean up because its not necessary in my simple activity? I tested it with a strong reference and then the same behavior does NOT show null. – Florian Walther Mar 11 '18 at 13:04
  • The interesting thing is, that it starts showing null as soon as I start another app – Florian Walther Mar 11 '18 at 13:16

1 Answers1

1

When the activity is being destroyed, the activity object should become eligible for garbage collection. This means, that some of the next GC events would free up memory hold by the activity.

You are making an assumption, that GC have to occur immediately after you perform orientation change, which is not true. What WeakReference ensures, is that it will not be a reason to hold the object when next time GC will try to clear up memory. Sooner or later the activity object would be cleared by GC, but it will not take place instantly as you expect.

You can utilize System.gc() which will initiate a synchronous GC event to take place, but it's not advised to rely on this method in normal scenario.


    System.gc();
    MainActivity activity = activityReference.get();
    // now `activity` should be null

azizbekian
  • 60,783
  • 13
  • 169
  • 249
  • Even when I try it like this it isn't null. I don't understand that. It works when I leave over the back button. I also tried running it for 30 seconds – Florian Walther Mar 11 '18 at 12:45
  • That means, that you are keeping a reference to the activity from somewhere else. Use [LeakCanary](https://github.com/square/leakcanary) to find out where from. – azizbekian Mar 11 '18 at 12:47
  • As soon as a I added LeakCanary, it started working (instead of showing a notification). When I remove it, it stops working. I don't understand what is happening – Florian Walther Mar 11 '18 at 12:52
  • I've added the complete activity code. I don't see any spot where I hold a reference – Florian Walther Mar 11 '18 at 12:58
  • Update: It shows null if I leave the activity after rotating it and then open a different app. Could it be that the system just doesnt clean up because its not necessary in my simple activity? I tested it with a strong reference and then the same behavior does NOT show null. – Florian Walther Mar 11 '18 at 13:04
  • The interesting thing is, that it starts showing null as soon as I start another app – Florian Walther Mar 11 '18 at 13:16
  • @FlorianWalther This is the answer, the only thing is that calling `System.gc()` doesn't work, I'm not sure why, but if you trigger the garbage collector from the memory profile in Android Studio you see the value becoming null right away – lelloman Mar 19 '18 at 18:32