On Android you can do work in a separate Thread
for example by using a Runnable
or AsyncTask
. In both cases you might need to do some work after the work is done, for example by overriding onPostExecute()
in the AsyncTask
. However the user might navigate away or close the app while work is being done in the background.
My question is: What happens if the user navigates away or closes the app while I still have a reference to the Activity
the user just closed in my AsyncTask
?
My guess is that it should be destroyed as soon as the user navigates away, however when I test it out on a device for some reason I can still call methods on the Activity
even though it is already gone! What is going on here?
Simple answer: You have just discovered
As long as some part of the app like an AsyncTask
still holds a reference to the Activity
it will not be destroyed. It will stick around until the AsyncTask
is done or releases its reference in some other way. This can have very bad consequences like your app crashing, but the worst consequences are the ones you don't notice: your app may keep reference to Activities
which should have been released ages ago and each time the user does whatever leaks the Activity
the memory on the device might get more and more full until seemingly out of nowhere Android kills your app for consuming too much memory. Memory leaks are the single most frequent and worst mistakes I see in Android questions on Stack Overflow
Avoiding memory leaks however is very simple: Your AsyncTask
should never have a reference to an Activity
, Service
or any other UI component.
Instead use the listener pattern and always use a WeakReference
. Never hold strong references to something outside the AsyncTask
.
View
in an AsyncTask
A correctly implemented AsyncTask
which uses an ImageView
could look like this:
public class ExampleTask extends AsyncTask<Void, Void, Bitmap> {
private final WeakReference<ImageView> mImageViewReference;
public ExampleTask(ImageView imageView) {
mImageViewReference = new WeakReference<>(imageView);
}
@Override
protected Bitmap doInBackground(Void... params) {
...
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
final ImageView imageView = mImageViewReference.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
This illustrates perfectly what a WeakReference
does. WeakReferences
allow for the Object
they are referencing to be garbage collected. So in this example We create a WeakReference
to an ImageView
in the constructor of the AsyncTask
. Then in onPostExecute()
which might be called 10 seconds later when the ImageView
does not exist anymore we call get()
on the WeakReference
to see if the ImageView
exists. As long as the ImageView
returned by get()
is not null then the ImageView
has not been garbage collected and we can therefore use it without worry! Should in the meantime the user quit the app then the ImageView
becomes eligible for garbage collection immediately and if the AsyncTask
finishes some time later it sees that the ImageView
is already gone. No memory leaks, no problems.
public class ExampleTask extends AsyncTask<Void, Void, Bitmap> {
public interface Listener {
void onResult(Bitmap image);
}
private final WeakReference<Listener> mListenerReference;
public ExampleTask(Listener listener) {
mListenerReference = new WeakReference<>(listener);
}
@Override
protected Bitmap doInBackground(Void... params) {
...
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
final Listener listener = mListenerReference.get();
if (listener != null) {
listener.onResult(bitmap);
}
}
}
This looks quite similar because it actually is quite similar. You can use it like this in an Activity
or Fragment
:
public class ExampleActivty extends AppCompatActivity implements ExampleTask.Listener {
private ImageView mImageView;
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
new ExampleTask(this).execute();
}
@Override
public void onResult(Bitmap image) {
mImageView.setImageBitmap(image);
}
}
Or you can use it like this:
public class ExampleFragment extends Fragment {
private ImageView mImageView;
private final ExampleTask.Listener mListener = new ExampleTask.Listener() {
@Override
public void onResult(Bitmap image) {
mImageView.setImageBitmap(image);
}
};
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
new ExampleTask(mListener).execute();
}
...
}
WeakReference
and its consequences when using a listenerHowever there is another thing you have to be aware of. A consequence of only having a WeakReference
to the listener. Imagine you implement the listener interface like this:
private static class ExampleListener implements ExampleTask.Listener {
private final ImageView mImageView;
private ExampleListener(ImageView imageView) {
mImageView = imageView;
}
@Override
public void onResult(Bitmap image) {
mImageView.setImageBitmap(image);
}
}
public void doSomething() {
final ExampleListener listener = new ExampleListener(someImageView);
new ExampleTask(listener).execute();
}
Quite an unusual way to do this - I know - but something similar might sneak into your code somewhere without you knowing it and the consequences can be difficult to debug. Have you noticed by now what might be wrong with the above example? Try figuring out, otherwise continue reading below.
The problem is simple: You create an instance of the ExampleListener
which contains your reference to the ImageView
. Then you pass it into the ExampleTask
and start the task. And then the doSomething()
method finishes, so all local variables become eligible for garbage collection. There is no strong reference left to the ExampleListener
instance you passed into the ExampleTask
, there is just a WeakReference
. So the ExampleListener
will be garbage collected and when the ExampleTask
finishes nothing will happen. If the ExampleTask
executes fast enough the garbage collector might not have collected the ExampleListener
instance yet, so it may work some of the time or not at all. And debugging issues like this can be a nightmare. So the moral of the story is: Always be aware of your strong and weak references and when objects become eligible for garbage collection.
static
Another thing which probably is the cause of most memory leaks I see on Stack Overflow people using nested classes in the wrong way. Look at the following example and try to spot what causes a memory leak in the following example:
public class ExampleActivty extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
final ImageView imageView = (ImageView) findViewById(R.id.image);
new ExampleTask(imageView).execute();
}
public class ExampleTask extends AsyncTask<Void, Void, Bitmap> {
private final WeakReference<ImageView> mListenerReference;
public ExampleTask(ImageView imageView) {
mListenerReference = new WeakReference<>(imageView);
}
@Override
protected Bitmap doInBackground(Void... params) {
...
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
final ImageView imageView = mListenerReference.get();
if (imageView != null) {
imageView.setImageAlpha(bitmap);
}
}
}
}
Do you see it? Here is another example with the exact same problem, it just looks different:
public class ExampleActivty extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
final ImageView imageView = (ImageView) findViewById(R.id.image);
final Thread thread = new Thread() {
@Override
public void run() {
...
final Bitmap image = doStuff();
imageView.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(image);
}
});
}
};
thread.start();
}
}
Have you figured out what the problem is? I daily see people carelessly implementing stuff like above probably without knowing what they are doing wrong. The problem is a consequence of how Java works based on a fundamental feature of Java - there is no excuse, people who implement things like above are either drunk or don't know anything about Java. Let's simplify the problem:
Imagine you have a nested class like this:
public class A {
private String mSomeText;
public class B {
public void doIt() {
System.out.println(mSomeText);
}
}
}
When you do that you can access members of the class A
from inside the class B
. That's how doIt()
can print mSomeText
, it has access to all the members of A
even private ones.
The reason you can do that is that if you nest classes like that Java implicitly creates a reference to A
inside of B
. It is because of that reference and nothing else that you have access to all members of A
inside of B
. However in the context of memory leaks that again poses a problem if you don't know what you are doing. Consider the first example (I'll strip all the parts that don't matter from the example):
public class ExampleActivty extends AppCompatActivity {
public class ExampleTask extends AsyncTask<Void, Void, Bitmap> {
...
}
}
So we have an AsyncTask
as a nested class inside an Activity
. Since the nested class is not static we can access members of the ExampleActivity
inside the ExampleTask
. It doesn't matter here that ExampleTask
doesn't actually access any members from the Activity
, since it is a non static nested class Java implicitly creates a reference to the Activity
inside the ExampleTask
and so with seemingly no visible cause we have a memory leak. How can we fix this? Very simple actually. We just need to add one word and that is static:
public class ExampleActivty extends AppCompatActivity {
public static class ExampleTask extends AsyncTask<Void, Void, Bitmap> {
...
}
}
Just this one missing keyword on a simple nested class is the difference between a memory leak and completely fine code. Really try to understand the issue here, because it is at the core of how Java works and understanding this is crucial.
And as for the other example with the Thread
? The exactly same issue, anonymous classes like that are also just non static nested classes and immediately a memory leak. However it is actually a million times worse. From every angle you look at it that Thread
example is just terrible code. Avoid at all costs.
So I hope these example helped you understand the problem and how to write code free of memory leaks. If you have any other questions feel free to ask.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With