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. "
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)
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.
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.
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.
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.
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.
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.
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?
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.
This is a story similar to this article by Qiita "This Handler class should be static or leaks might occur".
ʻ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