I am getting a warning in my code that states:
This AsyncTask class should be static or leaks might occur (anonymous android.os.AsyncTask)
The complete warning is:
This AsyncTask class should be static or leaks might occur (anonymous android.os.AsyncTask) A static field will leak contexts. Non-static inner classes have an implicit reference to their outer class. If that outer class is for example a Fragment or Activity, then this reference means that the long-running handler/loader/task will hold a reference to the activity which prevents it from getting garbage collected. Similarly, direct field references to activities and fragments from these longer running instances can cause leaks. ViewModel classes should never point to Views or non-application Contexts.
This is my code:
new AsyncTask<Void,Void,Void>(){
@Override
protected Void doInBackground(Void... params) {
runOnUiThread(new Runnable() {
@Override
public void run() {
mAdapter.notifyDataSetChanged();
}
});
return null;
}
}.execute();
How do I correct this?
myActivity.getApplication()
into the private constructor for the Singleton, in order to initialize RoomDB classes and other classes). My ViewModels get the Singleton instance as a private reference to perform some operations on the DB. So, the ViewModels import the Singleton package, as well as android.app.Application
, one of them even android.app.Activity
. Since "the Singleton" doesn't need to import those ViewModels to work, even so, might memory leaks occurr?
How to use a static inner AsyncTask class
To prevent leaks, you can make the inner class static. The problem with that, though, is that you no longer have access to the Activity's UI views or member variables. You can pass in a reference to the Context
but then you run the same risk of a memory leak. (Android can't garbage collect the Activity after it closes if the AsyncTask class has a strong reference to it.) The solution is to make a weak reference to the Activity (or whatever Context
you need).
public class MyActivity extends AppCompatActivity {
int mSomeMemberVariable = 123;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// start the AsyncTask, passing the Activity context
// in to a custom constructor
new MyTask(this).execute();
}
private static class MyTask extends AsyncTask<Void, Void, String> {
private WeakReference<MyActivity> activityReference;
// only retain a weak reference to the activity
MyTask(MyActivity context) {
activityReference = new WeakReference<>(context);
}
@Override
protected String doInBackground(Void... params) {
// do some long running task...
return "task finished";
}
@Override
protected void onPostExecute(String result) {
// get a reference to the activity if it is still there
MyActivity activity = activityReference.get();
if (activity == null || activity.isFinishing()) return;
// modify the activity's UI
TextView textView = activity.findViewById(R.id.textview);
textView.setText(result);
// access Activity member variables
activity.mSomeMemberVariable = 321;
}
}
}
Notes
As far as I know, this type of memory leak danger has always been true, but I only started seeing the warning in Android Studio 3.0. A lot of the main AsyncTask tutorials out there still don't deal with it (see here, here, here, and here).
You would also follow a similar procedure if your AsyncTask were a top-level class. A static inner class is basically the same as a top-level class in Java.
If you don't need the Activity itself but still want the Context (for example, to display a Toast), you can pass in a reference to the app context. In this case the AsyncTask constructor would look like this: private WeakReference
There are some arguments out there for ignoring this warning and just using the non-static class. After all, the AsyncTask is intended to be very short lived (a couple seconds at the longest), and it will release its reference to the Activity when it finishes anyway. See this and this.
Excellent article: How to Leak a Context: Handlers & Inner Classes
Kotlin
In Kotlin just don't include the inner
keyword for the inner class. This makes it static by default.
class MyActivity : AppCompatActivity() {
internal var mSomeMemberVariable = 123
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// start the AsyncTask, passing the Activity context
// in to a custom constructor
MyTask(this).execute()
}
private class MyTask
internal constructor(context: MyActivity) : AsyncTask<Void, Void, String>() {
private val activityReference: WeakReference<MyActivity> = WeakReference(context)
override fun doInBackground(vararg params: Void): String {
// do some long running task...
return "task finished"
}
override fun onPostExecute(result: String) {
// get a reference to the activity if it is still there
val activity = activityReference.get()
if (activity == null || activity.isFinishing) return
// modify the activity's UI
val textView = activity.findViewById(R.id.textview)
textView.setText(result)
// access Activity member variables
activity.mSomeMemberVariable = 321
}
}
}
Non-static inner classes holds a reference to the containing class. When you declare AsyncTask
as an inner class, it might live longer than the containing Activity
class. This is because of the implicit reference to the containing class. This will prevent the activity from being garbage collected, hence the memory leak.
To solve your problem, either use static nested class instead of anonymous, local, and inner class or use top-level class.
This AsyncTask
class should be static or leaks might occur because
When Activity is destroyed, AsyncTask (both static or non-static) still running
If inner class is non-static (AsyncTask) class, it will have reference to the outer class (Activity).
If a object has no references point to it, Garbage Collected will release it. If a object is unused and Garbage Collected can not release it => leak memory
=> If AsyncTask
is non-static
, Activity
won't release event it is destroyed => leak
Solution for update UI after make AsyncTask as static class without leak
1) Use WeakReference
like @Suragch answer
2) Send and remove Activity
reference to (from) AsyncTask
public class NoLeakAsyncTaskActivity extends AppCompatActivity {
private ExampleAsyncTask asyncTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// START AsyncTask
asyncTask = new ExampleAsyncTask();
asyncTask.setListener(new ExampleAsyncTask.ExampleAsyncTaskListener() {
@Override
public void onExampleAsyncTaskFinished(Integer value) {
// update UI in Activity here
}
});
asyncTask.execute();
}
@Override
protected void onDestroy() {
asyncTask.setListener(null); // PREVENT LEAK AFTER ACTIVITY DESTROYED
super.onDestroy();
}
static class ExampleAsyncTask extends AsyncTask<Void, Void, Integer> {
private ExampleAsyncTaskListener listener;
@Override
protected Integer doInBackground(Void... voids) {
...
return null;
}
@Override
protected void onPostExecute(Integer value) {
super.onPostExecute(value);
if (listener != null) {
listener.onExampleAsyncTaskFinished(value);
}
}
public void setListener(ExampleAsyncTaskListener listener) {
this.listener = listener;
}
public interface ExampleAsyncTaskListener {
void onExampleAsyncTaskFinished(Integer value);
}
}
}
Success story sharing
onPostExecute
method again in the code above. You can see that I updated the UITextView
there. Just useactivity.findViewById
to get a reference to whatever UI element you need to update.activity.isFinishing()
check and possibly replace it with afragment.isRemoving()
check. I haven't worked much with fragments recently, though.AsyncTask
constructor you pass in a reference to your outer class. And indoInBackground()
you can get a reference to the outer class withMyOuterClass ref = classReference.get()
. Check fornull
. (2) InonPostExecute()
you are only updating the UI with the results from the background task. It is just like any other time you that you update the UI. The check foractivity.isFinishing()
is just to make sure that the activity has not already started finishing, in which case it would be pointless to update the UI.