Warning to the nested class of AsyncTask defined in Activity "This'AsyncTask' class should be static or leaks might occur"

This article This is a continuation of the post entitled "Implicit reference to enclosing objects held by non-static nested classes". Therefore, I would appreciate it if you could take a look there first. That article is, to put it bluntly, "Add the static modifier to nested classes. "

I tried to make an app that suddenly starts counting up ...

As soon as you start it, the count-up will start in 1 second increments. If you start from START !, count up to "9" and you're done. (This animated GIF is an animation that repeats START! When it reaches 3, but I'm sorry because there is no way to stop the animated GIF pasted on this Qiita)

countup.gif

The environment is as follows.

Android Studio 3.6.1 Build #AI-192.7142.36.36.6241897, built on February 27, 2020 Runtime version: 1.8.0_212-release-1586-b04 amd64 VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o Windows 10 10.0 GC: ParNew, ConcurrentMarkSweep Memory: 1237M Cores: 8 Registry: ide.new.welcome.screen.force=true Non-Bundled Plugins:

Not developed in Kotlin. I will do it in Java.

I get a warning on the Activity code

The activity of this app is as follows.

MainActivity.java


public class MainActivity extends AppCompatActivity {

    private TextView textView;

    private MyTask task;

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

        textView = findViewById(R.id.text_view);
        task = new MyTask();
        task.execute();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        task.cancel(true);
    }

    class MyTask extends AsyncTask<Void, String, Void> {
        @Override
        protected Void doInBackground(Void... voids) {
            for (int i = 0; i < 10; i++) {                if (isCancelled()) {
                    return null; // break;But maybe
                }
                try {
                    Thread.sleep(1000);
                    publishProgress(String.valueOf(i));
                } catch (InterruptedException e) {
                    Log.e("MyTask", e.getMessage(), e);
                }
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(String... values) {
            textView.setText(values[0]);
        }
    }
}

Then Android Studio gives this warning.

thisasynctaskclassshouldbestaticorleaksmightoccur.png

This 'AsyncTask' class should be static or leaks might occur

I'm blaming the nested class named MyTask for not having the static modifier.

The purpose of this article is to avoid issuing this warning.

This seems to be the best (mainstream) solution

First of all, I will post the code of the conclusion. We decide to keep the ** weak reference ** of the enclosing class MainActicity object in the static nested class object. So we use java.lang.ref.WeakReference.

The best fix


public class MainActivityGood extends AppCompatActivity {

    //Stop the TextView field.

    private MyTask task;

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

        textView = findViewById(R.id.text_view);
        //Are you passing this? why? That's because I have a constructor in MyTask.
        task = new MyTask(this);
        task.execute(textView);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        task.cancel(true);
    }

    static class MyTask extends AsyncTask<Void, String, Void> {

        WeakReference<Activity> activityReference; //Attention here!

        //I have a constructor.
        public MyTask(Activity activity) {
            //We'll keep the object of the enclosing class with a weak reference.
            activityReference= new WeakReference<>(activity);
        }

        @Override
        protected Void doInBackground(Void... voids) {
            for (int i = 0; i < 10; i++) {                if (isCancelled()) {
                    return null; // break;But maybe
                }
                try {
                    Thread.sleep(1000);
                    publishProgress(String.valueOf(i));
                } catch (InterruptedException e) {
                    Log.e("MyTask", e.getMessage(), e);
                }
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(String... values) {
            //Weak references to objects in the enclosing class can be obtained with the get method.
            Activity activity = activityReference.get();
            //And let's check if the obtained weak reference is null.
            if (activity == null || activity.isFinishing()) {
                return; // ※
            }

            TextView textView = activity.findViewById(R.id.text_view);
            textView.setText(values[0]);
        }
    }
}

The conclusion is like this, but since it's a big deal, I'd like to have a big circle at the end while I'm on the way to reach this conclusion.

Add static modifier to nested class

As the Android Studio warning said, I blindly (laughs) added the static qualifier to the ʻAsyncTask subclass named MyTask`.

However, if you take such measures, you will get angry when accessing the non-static field of the enclosing class as shown in the image below. Why. That's because static nested classes can only access static members of enclosing.

Non-static field.png

Then

Isn't it good to modify MainActivity like this? With an easy feeling


public class MainActivity extends AppCompatActivity {

    private static TextView textView;

    //The following is omitted
}

No, isn't it a little different? I blame you.

I wrote terrible code

It seems to be intentional, but please take a look. But that's why the Android Studio example warning disappears.

MainActivityDasai.java


public class MainActivityDasai extends AppCompatActivity {

    private MyTask task;

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

        TextView textView = findViewById(R.id.text_view);
        task = new MyTask();
        task.execute(textView);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        task.cancel(true);
    }

    static class MyTask extends AsyncTask<TextView, Object, Void> {
        @Override
        protected Void doInBackground(TextView... textViews) {
            for (int i = 0; i < 100000; i++) {
                if (isCancelled()) {
                    return null; // break;But maybe
                }
                try {
                    Thread.sleep(1000);
                    publishProgress(textViews[0], String.valueOf(i));
                } catch (InterruptedException e) {
                    Log.e("MyTask", e.getMessage(), e);
                }
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(Object... values) {
            ((TextView)values[0]).setText((String)values[1]);
        }
    }
}

My patrol is straying my body and soul.

--This MyTask is a class that is used only in this MainActivity, so I want to make it a nested class. --But the IDE warns you to make it a static nested class. --However, I don't want to make a field of type TextTview a static field. --So, is this MyTask and this MainActivity divided into different classes? ** But if you do that, it's okay because there is only one TextTview now, but when it comes to handling multiple other views, passing references is troublesome. ** ** ――Then, after all, do you want to make it a nested class?

This my crawling came to the idea that "* Then, let's stop making TextView a field of the enclosing class! * ", And it became such a crappy program.

So I wanted to blame myself for writing this crappy program. What I don't like is that ** I'm sick of specifying ʻObject for generics **. Thanks to that, the part of " ((TextView) values [0]). setText ((String) values [1]); " is cast, and which is the index of the array? Is confusing. In addition, I don't like the fact that the argument of the doInBackground method and the first generic of ʻAsyncTask are TextView. What if the number of views other than TextView increases?

Use WeakReference to treat the received Activity as a "weak reference"

I decided to use java.lang.ref.WeakReference and treat it as a ** weak reference **. Please see MainActivityGood.java above.

The point is

--Define a constructor in MyTask which is a static nested class. --Define a WeakReference type field with the enclosing class specified in the generics. --And do it new using the arguments of the constructor. This keeps a weak reference to MainActivityGood! --After that, if necessary, get MainActivityGood from WeakReference (method name is also get ()) and use it.

Unlike the crappy way above, this makes it easier to deal with views other than TextView, and you don't have to do the stupid thing of specifying ʻObject` in the generics.

that's all.

reference

This is a story similar to this article by Qiita "This Handler class should be static or leaks might occur".

AsyncTask has been deprecated since API level R

ʻAndroid.os.AsyncTask` has been deprecated since API level R [^ 1]. This article is also destined to be obsolete ...

[^ 1]: The API level is indicated by a number, but at the time of writing this article, it is "R".

The alternative is

--Use java.util.concurrent package as standard or -Use Kotlin concurrency utilities.

... apparently ... The latter is basically Kotlin's Coroutines.

Recommended Posts

Warning to the nested class of AsyncTask defined in Activity "This'AsyncTask' class should be static or leaks might occur"
In SpringBoot 2.3.0 or later, the placeholder used for the value of @PropertySource fails to be resolved.
[Rails] Where to be careful in the description of validation