The
Handler
class is truly fundamental to the Android platform and is used widely throughout, but there are plenty of ways we can get ourselves into trouble if we aren't careful.
The biggest worry when using Handler
within an Activity
is resource leakage which, just as with AsyncTask
, is very easy to do. Here's one of our earlier examples:
final Runnable runnable = new Runnable(){ public void run() { // … do some work } }; handler.postDelayed(runnable, TimeUnit.SECONDS.toMillis(10));
By declaring an anonymous inner Runnable
inside an activity, we have made an implicit reference to that containing Activity
instance. We've then posted the Runnable
to a handler and told it to execute in 10 seconds time.
If the Activity
finishes before the 10 seconds are up, it cannot yet be garbage collected because the implicit reference in our Runnable
means that the Activity
is still reachable by live objects.
So, although it makes for a concise example, it is not a good idea in practice to post non-static Runnables onto the main thread's Handler queue (especially with postDelayed
or postAtTime
) unless we're very careful to clean up after ourselves.
One way to minimize this problem is to avoid using non-static inner classes; for example, by always declaring Runnables as top-level classes in their own file, or as static
classes in an Activity
subclass. This means that references must be explicit, which makes them easier to spot and nullify.
In addition, we can cancel pending tasks during Activity
lifecycle callbacks such as onPause
. This is easiest if we're working with Messages since we can remove them by their what
value, and don't have to keep references as we would with Runnables.
For HandlerThread
instances we've created, we should make sure to quit
when the Activity
is finishing, which will prevent further execution and free up the Runnable
and Message
objects for garbage collection.
If we are to interact with the user interface, we'll at least need a reference to an object in the View
hierarchy, which we might pass into our static or top-level Runnable's constructor.
static class MyRunnable implements Runnable { private View view; public MyRunnable(View view) { this.view = view; } public void run() { // … do something with the view. } }
However, by keeping a strong reference to the View
, we are again subject to potential memory leaks if our Runnable
outlives the View
; for example, if some other part of our code removes this View
from the display before our Runnable
executes.
One solution to this is to use a weak reference, and check for null before using the referenced View
.
static class MyRunnable implements Runnable { private WeakReference<View> view; public MyRunnable(View view) { this.view = new WeakReference<View>(view); } public void run() { View v = view.get(); // might return null if (v != null) { // … do something with the view. } } }
If you haven't used WeakReference
before, what it gives us is a way to refer to an object only for as long as some other live object has a stronger reference to it (for example, a "normal" property reference).
When all strong references are garbage collected, our WeakReference
will also lose its reference to the View
, get()
will return null
, and the View
will be garbage collected.
This fixes the resource leakage problem, but we must always check for null
before using the returned object, to avoid potential NullPointerException's.
If we're sending Messages to our Handler
and expecting it to update the user interface, it will also need a reference to the View
hierarchy. A nice way to manage this is to attach and detach the Handler
from onResume
and onPause
.
private static class MyHandler extends Handler { private TextView view; public void attach(TextView view) { this.view = view; } public void detach() { view = null; } @Override public void handleMessage(Message msg) { //… } }
13.59.61.147