Users like snappy applications. Users do not like applications that feel sluggish.
The way to help your application feel snappy is to use the standard threading capabilities built into Android. This chapter will go through the issues involved with thread management in Android and will walk you through some of the options for keeping the user interface crisp and responsive.
When you call setText()
on a TextView
, you probably think that the screen
is updated with the text you supply, right then and there.
You would be mistaken.
Rather, everything that modifies the widget-based UI goes through a message
queue. Calls to setText()
do not update the screen — they just place a
message on a queue telling the operating system to update the screen. The
operating system pops these messages off of this queue and does what the
messages require.
The queue is processed by one thread, variously called the “main application thread” and the “UI thread”. So long as that thread can keep processing messages, the screen will update, user input will be handled, and so on.
However, the main application thread is also used for nearly all callbacks into
your activity. Your onCreate()
, onClick()
, onListItemClick()
, and similar
methods are all called on the main application thread. While your code is
executing in these methods, Android is not processing messages on the queue,
and so the screen does not update, user input is not handled, and so on.
This, of course, is bad. So bad, that if you take more than a few seconds to do work on the main application thread, Android may display the dreaded “Application Not Responding” dialog (ANR for short), and your activity may be killed off.
Nowadays, though, the bigger concern is jank.
“Jank”, as used in Android, refers to sluggish UI updates, particularly when
something is animating. For example, you may have encountered some apps that
when you scroll a ListView
in the app, the ListView
does not scroll smoothly.
Rather, it scrolls jerkily, interleaving periods of rapid movement with periods
where the animation is frozen. Most of the time, this is caused by the
app’s author doing too much work on the main application thread.
Android 4.1 introduced “Project Butter”, which, among other things, established a baseline for “doing too much work on the main application thread”. We will “drop frames” if we take more than ~16ms per frame (60 frames per second), and dropped frames are the source of jank. Since we may be called many times during a frame, each of our callbacks needs to be very cheap, ideally below 1ms. We will get much more into the issue of jank later in the book, but it is important to understand now that any significant delay in the execution of our code on the main application thread can have visible effects to the user.
Hence, you want to make sure that all of your work on the main application thread happens quickly. This means that anything slow should be done in a background thread, so as not to tie up the main application thread. This includes things like:
Fortunately, Android supports threads using the standard Thread
class from
Java, plus all of the wrappers and control structures you would expect, such as
the java.util.concurrent
class package.
However, there is one big limitation: you cannot modify the UI from a
background thread. You can only modify the UI from the main application thread.
If you call setText()
on a TextView
from a background thread, your application
will crash, with an exception indicating that you are trying to modify the UI
from a “non-UI thread” (i.e., a thread other than the main application thread).
This is a pain.
Hence, you need to get long-running work moved into background threads, but those threads need to do something to arrange to update the UI using the main application thread.
There are various facilities in Android for helping with this.
Some are high-level frameworks for addressing this issue for major functional
areas. One example of this is the Loader
framework for retrieving
information from databases, and we will examine this in a later chapter.
Sometimes, there are asynchronous options built into other Android operations.
For example, when we discuss SharedPreferences
in a later chapter, we will
see that we can persist changes to those preferences synchronously or
asynchronously.
And, there are a handful of low-level solutions for solving this problem, ones that you can apply for your own custom business logic.
One popular approach for handling this threading problem is to use AsyncTask
.
With AsyncTask
, Android will handle all of
the chores of coordinating separate work done on a background thread versus on the UI thread.
Moreover, Android itself allocates and removes that background thread. And, it
maintains a small work queue, further accentuating the “fire and forget” feel
to AsyncTask
.
Theodore Levitt is quoted as saying, with respect to marketing: “People don’t want to buy a quarter-inch drill, they want a quarter-inch hole”. Hardware stores cannot sell holes, so they sell the next-best thing: devices (drills and drill bits) that make creating holes easy.
Similarly, many Android developers who have struggled with background thread
management do not want background threads — they want work to be
done off the UI thread, to avoid jank. And while Android
cannot magically cause work to not consume UI thread time, Android can offer
things that make such background operations easier and more transparent.
AsyncTask
is one such example.
To use AsyncTask
, you must:
AsyncTask
AsyncTask
methods to accomplish the background work,
plus whatever work associated with the task that needs to be done on the UI
thread (e.g., update progress)AsyncTask
subclass and call
execute()
to have it begin doing its workWhat you do not have to do is:
Creating a subclass of AsyncTask
is not quite as easy as, say, implementing
the Runnable
interface. AsyncTask
uses generics, and so you need to specify
three data types:
What makes this all the more confusing is that the first two data types are
actually used as varargs, meaning that an array of these types is used within
your AsyncTask
subclass.
This should become clearer as we work our way towards an example.
There are four methods you can override in AsyncTask
to accomplish your ends.
The one you must override, for the task class to be useful, is
doInBackground()
. This will be called by AsyncTask
on a background thread.
It can run as long as it needs to in order to accomplish whatever work needs to
be done for this specific task. Note, though, that tasks are meant to be finite
– using AsyncTask
for an infinite loop is not recommended.
The doInBackground()
method will receive, as parameters, a varargs array of
the first of the three data types listed above — the data needed to
process the task. So, if your task’s mission is to download a collection of
URLs, doInBackground()
will receive those URLs to process.
The doInBackground()
method must return a value of the third data type listed
above — the result of the background work.
You may wish to override onPreExecute()
. This method is called, from the UI
thread, before the background thread executes doInBackground()
. Here, you
might initialize a ProgressBar
or otherwise indicate that background work is
commencing.
Also, you may wish to override onPostExecute()
. This method is called, from
the UI thread, after doInBackground()
completes. It receives, as a parameter,
the value returned by doInBackground()
(e.g., success or failure flag). Here,
you might dismiss the ProgressBar
and make use of the work done in the
background, such as updating the contents of a list.
In addition, you may wish to override onProgressUpdate()
. If
doInBackground()
calls the task’s publishProgress()
method, the object(s)
passed to that method are provided to onProgressUpdate()
, but in the UI
thread. That way, onProgressUpdate()
can alert the user as to the progress
that has been made on the background work. The onProgressUpdate()
method will receive a varargs
of the second data type from the above list — the data published by
doInBackground()
via publishProgress()
.
As mentioned earlier, implementing an AsyncTask
is not quite as easy as
implementing a Runnable
. However, once you get past the generics and varargs,
it is not too bad.
To see an AsyncTask
in action, this section will examine the
Threads/AsyncTask
sample project.
We have a ListFragment
, named AsyncDemoFragment
:
package com.commonsware.android.async;
import android.app.ListFragment;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import java.util.ArrayList;
public class AsyncDemoFragment extends ListFragment {
private static final String[] items= { "lorem", "ipsum", "dolor",
"sit", "amet", "consectetuer", "adipiscing", "elit", "morbi",
"vel", "ligula", "vitae", "arcu", "aliquet", "mollis", "etiam",
"vel", "erat", "placerat", "ante", "porttitor", "sodales",
"pellentesque", "augue", "purus" };
private ArrayList<String> model=new ArrayList<String>();
private ArrayAdapter<String> adapter=null;
private AddStringTask task=null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
task=new AddStringTask();
task.execute();
adapter=
new ArrayAdapter<String>(getActivity(),
android.R.layout.simple_list_item_1,
model);
}
@Override
public void onViewCreated(View v, Bundle savedInstanceState) {
super.onViewCreated(v, savedInstanceState);
getListView().setScrollbarFadingEnabled(false);
setListAdapter(adapter);
}
@Override
public void onDestroy() {
if (task != null) {
task.cancel(false);
}
super.onDestroy();
}
class AddStringTask extends AsyncTask<Void, String, Void> {
@Override
protected Void doInBackground(Void... unused) {
for (String item : items) {
if (isCancelled())
break;
publishProgress(item);
SystemClock.sleep(400);
}
return(null);
}
@Override
protected void onProgressUpdate(String... item) {
if (!isCancelled()) {
adapter.add(item[0]);
}
}
@Override
protected void onPostExecute(Void unused) {
Toast.makeText(getActivity(), R.string.done, Toast.LENGTH_SHORT)
.show();
task=null;
}
}
}
This is another variation on the lorem ipsum list of words, used frequently
throughout this book. This time, rather than simply hand the list of words to
an ArrayAdapter
, we simulate having to work to create these words in the
background using AddStringTask
, our AsyncTask
implementation.
In onCreate()
, we call setRetainInstance(true)
, so Android
will retain this fragment across configuration changes, such as a screen
rotation. Since our fragment
is being newly created, we initialize
our model
to be an ArrayList
of String
values, plus kick off our AsyncTask
(the AddStringTask
inner class, described below), saving the
AddStringTask
in a task
data member. Then, in onViewCreated()
, we set up
the adapter and attach it to the ListView
, also preventing the
ListView
scrollbars from fading away as is their norm.
In the declaration of AddStringTask
, we use the generics to set up the
specific types of data we are going to leverage. Specifically:
Void
onProgressUpdate()
, so we can add it to our list, so our second type is
String
Void
The doInBackground()
method is invoked in a background thread. Hence, we can
take as long as we like. In a production application, we would be, perhaps,
iterating over a list of URLs and downloading each. Here, we iterate over our
static list of lorem ipsum words, call publishProgress()
for each, and then
sleep 400 milliseconds to simulate real work being done. We also call
isCancelled()
on each pass, to see if our task has been cancelled, skipping
the work if it has so we can clean up this background thread.
Since we elected to have no configuration information, we should not need
parameters to doInBackground()
. However, the contract with AsyncTask
says
we need to accept a varargs of the first data type, which is why our method
parameter is Void...
.
Since we elected to have no results, we should not need to return anything.
Again, though, the contract with AsyncTask
says we have to return an object
of the third data type. Since that data type is Void
, our returned object is
null
.
The onProgressUpdate()
method is called on the UI thread, and we want to do
something to let the user know we are progressing on loading up these strings.
In this case, we simply add the string to the ArrayAdapter
, so it gets
appended to the end of the list. However, we only do this if we have not
already been canceled.
The onProgressUpdate()
method receives a String...
varargs because that is
the second data type in our class declaration. Since we are only passing one
string per call to publishProgress()
, we only need to examine the first entry
in the varargs array.
The onPostExecute()
method is called on the UI thread, and we want to do
something to indicate that the background work is complete. In a real system,
there may be some ProgressBar
to dismiss or some animation to stop. Here, we
simply raise a Toast
and set task
to null
. We do not need to worry
about calling isCancelled()
, because onPostExecute()
will not be invoked
if our task has been cancelled.
Since we elected to have no results, we should not need any parameters. The
contract with AsyncTask
says we have to accept a single value of the third
data type. Since that data type is Void
, our method parameter is Void unused
.
To use AddStringTask
, we simply create an instance and call execute()
on
it. That starts the chain of events eventually leading to the background thread
doing its work.
If AddStringTask
required configuration parameters, we would have not used
Void
as our first data type, and the constructor would accept zero or more
parameters of the defined type. Those values would eventually be passed to
doInBackground()
.
Our fragment also has an onDestroy()
method that calls cancel()
on the
AsyncTask
if it is still outstanding (task
is not null
). This work of
cancelling the task and checking to see if the task is cancelled exists for two
reasons:
Toast
on a destroyed activity, such as the
user launching the activity, then pressing BACK before we complete the
background work and display the Toast
AsyncDemo
is an Activity
with the standard
recipe for kicking off an instance of a dynamic fragment:
package com.commonsware.android.async;
import android.app.Activity;
import android.os.Bundle;
public class AsyncDemo extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getFragmentManager().findFragmentById(android.R.id.content) == null) {
getFragmentManager().beginTransaction()
.add(android.R.id.content,
new AsyncDemoFragment()).commit();
}
}
}
If you build, install, and run this project, you will see the list being
populated in “real time” over a few seconds, followed by a Toast
indicating
completion.
One problem with the default destroy-and-create cycle that activities go
through on a configuration change comes from background threads. If the activity
has started some background work — through an AsyncTask
, for example
– and then the activity is destroyed and re-created, somehow the
AsyncTask
needs to know about this. Otherwise, the AsyncTask
might well
send updates and final results to the old activity, with the new activity
none the wiser. In fact, the new activity might start up the background work
again, wasting resources.
That is why, in the sample above, we are retaining the fragment instance.
The fragment instance holds onto its data model (in this case, the ArrayList
of Latin words) and knows not to kick off a new AsyncTask
just because
the configuration changed. Moreover, we retain that data model, so the
new ListView
created due to the configuration change can work with a new
adapter backed by the old data model, so we do not lose our existing set
of Latin words.
We also have to be very careful not to try referring to the activity (via
getActivity()
on the fragment) from our background thread (doInBackground()
).
Because, suppose that during the middle of the doInBackground()
processing, the
user rotates the screen. The activity we work with will change on the fly,
on the main application thread, independently of the work being done in
the background. The activity returned by getActivity()
may not be in a
useful state for us while this configuration change is going on.
However, it is safe for us to use getActivity()
from onPostExecute()
, and
even from onProgressUpdate()
. For those callbacks, either the configuration
change has not yet happened, or it has been completed — we will not be in the
middle of the change.
AsyncTask
, particularly in conjunction with a dynamic fragment, is a wonderful
solution for most needs for a background thread.
The key word in that sentence is “most”.
AsyncTask
manages a thread pool, from which it pulls the threads to be used
by task instances. Thread pools assume that they will get their threads back
after a reasonable period of time. Hence, AsyncTask
is a poor choice when
you do not know how long you need the thread (e.g., thread listening on a socket
for a chat client, where you need the thread until the user exits the client).
Moreover, the thread pool that AsyncTask
manages has varied in size.
In Android 1.5, it was a single thread.
In Android 1.6, it was expanded to support many parallel threads, probably more than you will ever need.
In Android 3.2, it has shrunk back to a single thread, if your
android:targetSdkVersion
is set to 13 or higher. This was to address concerns
about:
If you wish, starting with API Level 11, you can supply your own Executor
(from the java.util.concurrent
package) that has whatever thread pool you
wish, so you can manage this more yourself. In addition to the serialized,
one-at-a-time Executor
, there is a built-in Executor
that implements the
old thread pool, that you can use rather than rolling your own.
If your minSdkVersion
is 11 or higher, use
executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
if you specifically want
to opt into a multi-thread thread pool. If your minSdkVersion
is below
11, you will still want to do that… but only on API Level 11+ devices, falling
back to execute()
on the older devices. This static utility method handles this
for you:
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
static public <T> void executeAsyncTask(AsyncTask<T, ?, ?> task,
T... params) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
}
else {
task.execute(params);
}
}
To use this, call executeAsyncTask()
, passing in your AsyncTask
instance and the parameters you would ordinarily have passed to execute()
.
An explanation of what we are doing here, in terms of the @TargetApi
annotation
and such, will come later in the book.
Also note that the number of threads in the multiple-thread thread pool has also changed over the years. Originally, that pool could climb to as many as 128 threads, which was far too many. As of Android 4.4, the thread pool will only grow to “the number of CPU cores * 2 + 1”, so on a dual-core device, the thread pool will cap at 5 threads. Further tasks will be queued, up to a maximum of 128 queued tasks.
There are other ways of handling background threads without using AsyncTask
:
Handler
, which has a handleMessage()
method that
will process Message
objects, dispatched from a background thread, on the
main application threadRunnable
to be executed on the main application thread
to post()
on any View
, or to runOnUiThread()
on Activity
Runnable
, plus a delay period in milliseconds, to
postDelayed()
on any View
, to run the Runnable
on the main application
thread after at least that number of millisecond has elapsedOf these, the Runnable
options are the easiest to use.
These can also be used to allow the main application thread to postpone
work, to be done later on the main application thread. For example, you
can use postDelayed()
to set up a lightweight polling “loop” within
an activity, without needing the overhead of an extra thread, such as the
one created by Timer
and TimerTask
. To see how this works, let’s
take a peek at the
Threads/PostDelayed
sample project.
This project contains a single activity, named PostDelayedDemo
:
package com.commonsware.android.post;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
public class PostDelayedDemo extends Activity implements Runnable {
private static final int PERIOD=5000;
private View root=null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
root=findViewById(android.R.id.content);
}
@Override
public void onResume() {
super.onResume();
run();
}
@Override
public void onPause() {
root.removeCallbacks(this);
super.onPause();
}
@Override
public void run() {
Toast.makeText(PostDelayedDemo.this, "Who-hoo!", Toast.LENGTH_SHORT)
.show();
root.postDelayed(this, PERIOD);
}
}
We want to display a Toast
every five seconds. To do this, in onCreate()
, we get
our hands on the container for an activity’s UI, known as android.R.id.content
, via
findViewById()
. Then, in onResume()
, we call a run()
method on our activity,
which displays the Toast
and calls postDelayed()
to schedule itself (as an implementation
of Runnable
) to be run again in PERIOD
milliseconds. While our activity is in the
foreground, the Toast
will appear every PERIOD
milliseconds as a result. Once something
else comes to the foreground — such as by the user pressing BACK — our onPause()
method is called, where we call removeCallbacks()
to “undo” the postDelayed()
call.
Background threads, while eminently possible using AsyncTask
and kin,
are not all happiness and warm puppies. Background threads not only add
complexity, but they have real-world costs in terms of available memory, CPU,
and battery life.
To that end, there is a wide range of scenarios you need to account for with your background thread, including:
java.util.concurrent
package that will help you communicate safely with your
background thread.AsyncTask
or a bare
Thread
, you will wind up using a Service
, such as an IntentService
. This
will be explored in greater detail later in this book.ProgressBar
or other means of letting the user know that something is
happening. Strategically, this means you still need to be efficient at what you
do — background threads are no panacea for sluggish or pointless code.Notification
and shutting down the background thread may be your best option.Event-driven programming has been around for nearly a quarter-century. Much of
Android’s UI model is event-driven, where we find out about these events via
callbacks (e.g., onCreate()
for the “start an activity” event) and registered
listeners (e.g., OnClickListener
for when the user taps on a widget).
However, originally, Android did not have a very fine-grained event or message bus implementation
that we as developers could use. The Intent
system works like a message bus, but
it is aimed at inter-process communication (IPC) as much as in-process communication,
and that comes with some costs.
However, over time, particularly starting in 2012, event buses started to pop up,
and these are very useful for organizing communication within your Android
application and across threads. Used properly, an event bus can eliminate the need
for AsyncTask
and the other solutions for communicating back to the main
application thread, while simultaneously helping you logically decouple independent
pieces of your code.
Whether you consider it an “event bus” (or “message bus”), the “publisher/subscriber” (or “pub/sub”) pattern, or a subset of the “observer” pattern, the programming model where components produce events that others consume is reasonably common in modern software development.
An event bus is designed to decouple the sources of events from the consumers of those events. Or, as one event bus author put it:
I want an easy, centralized way to notify code that’s interested in specific types of events when those events occur without any direct coupling between the code the publishes an event and the code that receives it.
With the traditional Java listener or observer pattern implementation, the
component producing an event needs direct access to consumers of that event.
Sometimes, that list of consumers is limited to a single consumer, as with
many event handlers associated with Android widgets (e.g., just one OnClickListener
).
But this source-holds-the-sinks coding pattern limits flexibility, as it
requires explicit registration by consumers with producers of events, and
it may not be that easy for the consumer to reach the producer. Furthermore, such
direct connections are considered to be a relatively strong coupling between
those components, and often times our objective is to have looser coupling.
An event bus provides a standard communications channel (or “bus”) that event producers and event consumers can hook into. Event producers merely need to hand the event to the bus; the bus will handle directing those events to relevant consumers. This reduces the coupling between the producers and consumers, sometimes even reducing the amount of code needed to source and sink these events.
Later on, we are going to have components other than our activities. In particular, we will have services, which are designed to run briefly in the background to perform some operation. Just as communications between activities tends to be loosely coupled, so too are communications between activities and services. An event bus is a great way for the service to let other pieces of the app know that certain work was done (e.g., “the download is complete, so update the UI”).
In the short term, we will use an event bus to have a model fragment let the app know that some data was loaded. In the tutorials, “some data” will be the book contents; in the sample app illustrated in this chapter, “some data” will be some Latin words.
The event bus implementation that we will be using in the tutorials is greenrobot’s EventBus, an open source implementation based on the Guava project’s event bus. With greenrobot’s EventBus, it is fairly easy to send a message from one part of your app to another disparate part of your app.
To illustrate its use, take a look at
the
EventBus/AsyncDemo
sample project. This is a reworking of a previous example that used an AsyncTask
to pretend to download our list of Latin words, populating a ListView
with those
words as they arrive. This sample replaces the AsyncTask
with a model fragment
that will keep track of the words and a background thread that will “download” the
words. We will use events raised by the model fragment to let the UI fragment
know words as they arrive.
greenrobot’s EventBus is distributed as an artifact that you can
integrate in your project via the dependencies
in your module’s
build.gradle
file:
apply plugin: 'com.android.application'
dependencies {
compile 'de.greenrobot:eventbus:2.2.1'
}
android {
compileSdkVersion 19
buildToolsVersion "21.1.2"
defaultConfig {
targetSdkVersion 17
}
}
Here, we are pulling in version 2.2.1. Newer versions, starting with 3.0, have a different artifact and work a bit differently — this is covered later in this chapter.
With greenrobot’s EventBus, the “events” are objects of arbitrary classes that you define. Each different class represents a different type of event, and you can define as many different event classes as you wish. Those classes do not need to inherit from any special base class, or implement some special interface, or have any magic annotations. They are just classes.
You may wish to put data members, constructors, and accessor methods on
the event classes, for any data you wish to pass around specific to the event
itself. A SearchEvent
, for example, might include the search query string
as part of the event object.
In our case, we have a WordReadyEvent
that contains the new word:
package com.commonsware.android.eventbus;
class WordReadyEvent {
private String word;
WordReadyEvent(String word) {
this.word=word;
}
String getWord() {
return(word);
}
}
To post an event, all you need to do is obtain an instance of an EventBus
–
typically via the getDefault()
method on EventBus
— and call post()
on it,
passing in the event to be delivered to any interested party within your app.
With that in mind, let’s look at the ModelFragment
that will be loading in our
words:
package com.commonsware.android.eventbus;
import android.app.Fragment;
import android.os.Bundle;
import android.os.SystemClock;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import de.greenrobot.event.EventBus;
public class ModelFragment extends Fragment {
private static final String[] items= { "lorem", "ipsum", "dolor",
"sit", "amet", "consectetuer", "adipiscing", "elit", "morbi",
"vel", "ligula", "vitae", "arcu", "aliquet", "mollis", "etiam",
"vel", "erat", "placerat", "ante", "porttitor", "sodales",
"pellentesque", "augue", "purus" };
private List<String> model=
Collections.synchronizedList(new ArrayList<String>());
private boolean isStarted=false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
if (!isStarted) {
isStarted=true;
new LoadWordsThread().start();
}
}
public ArrayList<String> getModel() {
return(new ArrayList<String>(model));
}
class LoadWordsThread extends Thread {
@Override
public void run() {
for (String item : items) {
if (!isInterrupted()) {
model.add(item);
EventBus.getDefault().post(new WordReadyEvent(item));
SystemClock.sleep(400);
}
}
}
}
}
This fragment has no UI — it exists solely to manage a data model on behalf of the
rest of the hosting activity. Hence, there is no onCreateView()
or any other
UI logic directly in this fragment.
In onCreate()
, we call setRetainInstance(true)
, so that if the user rotates
the screen or otherwise triggers a configuration change, our model fragment will
survive the change and be attached to the new activity instance. Then, if we have
not already started the LoadWordsThread
, we do so. LoadWordsThread
iterates
over our list of words, sleeps for 400ms to simulate doing real work, adds each
word to an ArrayList
of words that it manages… and calls post()
to raise
a WordReadyEvent
to let something else know that the model has changed.
To receive posted events, you need to do three things:
register()
on the EventBus
to tell it that you have an object that wants
to receive eventsunregister()
on the EventBus
to tell it to stop delivering events to
a previously-registered objectonEventMainThread()
, or other onEvent()
method flavors, to indicate
the type of event you want to receive (and to actually process those events)This sample app has an AsyncDemoFragment
that performs those three steps:
package com.commonsware.android.eventbus;
import android.app.Activity;
import android.app.ListFragment;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import java.util.ArrayList;
import de.greenrobot.event.EventBus;
public class AsyncDemoFragment extends ListFragment {
private ArrayAdapter<String> adapter=null;
private ArrayList<String> model=null;
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
adapter=
new ArrayAdapter<String>(getActivity(),
android.R.layout.simple_list_item_1,
model);
getListView().setScrollbarFadingEnabled(false);
setListAdapter(adapter);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
EventBus.getDefault().register(this);
}
@Override
public void onDetach() {
EventBus.getDefault().unregister(this);
super.onDetach();
}
public void onEventMainThread(WordReadyEvent event) {
adapter.add(event.getWord());
}
public void setModel(ArrayList<String> model) {
this.model=model;
}
}
The fragment starts by overriding onViewCreated()
, where we create an ArrayAdapter
and use
that to populate the ListView
.
The onAttach()
and onDetach()
methods are where we indicate to the EventBus
that this fragment object wants to receive relevant posted events. onAttach()
calls
register()
; onDetach()
calls unregister()
.
The onEventMainThread()
method, via its parameter, indicates that we are interested
in WordReadyEvent
s as they are raised. Our onEventMainThread()
method will be called
for each WordReadyEvent
passed to post()
on the EventBus
. As the method name
suggests, onEventMainThread()
is called on the main application thread, so it is
safe for us to update our UI. greenrobot’s EventBus is responsible for getting this
event to the main application thread — note that we are posting the event from
the LoadWordsThread
, which is a background thread.
In onEventMainThread()
, we get the newly-added word, which we can add to our ArrayAdapter
.
add()
on ArrayAdapter
appends the word to the end of the list and informs the
attached ListView
that the data changed, so the ListView
can redraw itself.
What is not obvious, though, from the code in this class is how we are getting the
model
that we are using in onViewCreated()
.
AsyncDemoFragment
has its own ArrayList
of words, set via the setModel()
method. Our ArrayAdapter
is wrapped around this model. But the master copy
of the words is being held by the ModelFragment
.
If the ModelFragment
has the model, and the AsyncDemoFragment
needs the model,
how are the two being connected?
That is handled by our hosting activity, as it sets up these two fragments:
package com.commonsware.android.eventbus;
import android.app.Activity;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.os.Bundle;
public class AsyncDemo extends Activity {
private static final String MODEL_TAG="model";
private ModelFragment mFrag=null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FragmentManager mgr=getFragmentManager();
FragmentTransaction trans=mgr.beginTransaction();
mFrag=(ModelFragment)mgr.findFragmentByTag(MODEL_TAG);
if (mFrag == null) {
mFrag=new ModelFragment();
trans.add(mFrag, MODEL_TAG);
}
AsyncDemoFragment demo=
(AsyncDemoFragment)mgr.findFragmentById(android.R.id.content);
if (demo == null) {
demo=new AsyncDemoFragment();
trans.add(android.R.id.content, demo);
}
demo.setModel(mFrag.getModel());
if (!trans.isEmpty()) {
trans.commit();
}
}
}
In onCreate()
, we first see if we already have an instance of our model fragment,
held by the FragmentManager
under a MODEL_TAG
tag. If not, we create an instance
of the ModelFragment
and add it to the FragmentManager
, under that tag, via a
FragmentTransaction
.
We then see if we already have an instance of our AsyncDemoFragment
. If not, we create
one and add it to the FragmentManager
, pouring its UI into android.R.id.content
,
via another FragmentTransaction
.
Then, we connect the two, calling getModel()
on the ModelFragment
and handing the
result to setModel()
on the AsyncDemoFragment
.
When our activity is newly launched, neither fragment exists. Both fragments are created,
and the AsyncDemoFragment
gets its model array from the ModelFragment
. That array
is initially empty. As the ModelFragment
adds elements to the array, it posts
the WordReadyEvent
, which triggers the AsyncDemoFragment
to tell the ArrayAdapter
and ListView
that the model data changed.
If we undergo a configuration change, the ModelFragment
is retained, but the
AsyncDemoFragment
is not. Hence, the activity will always be creating an
AsyncDemoFragment
. But the model we give to the AsyncDemoFragment
may already have
words in it, and those words will appear immediately when the ArrayAdapter
is
wrapped around the model. If the LoadWordsThread
is still running, the new
AsyncDemoFragment
will pick up any new WordReadyEvent
s that are raised, triggering
it to update the ListView
as before.
Some examples in this book use a newer version of greenrobot’s EventBus. Starting with version 3.0, greenrobot’s EventBus has three changes to what we saw in the preceding sections.
First, the artifact has a different package (org.greenrobot
) in addition
to the higher version number:
apply plugin: 'com.android.application'
dependencies {
compile 'org.greenrobot:eventbus:3.0.0'
}
android {
compileSdkVersion 19
buildToolsVersion "21.1.2"
defaultConfig {
targetSdkVersion 17
applicationId "com.commonsware.android.eventbus.greenrobot3"
}
}
Second, the import statements will pull in classes from the
org.greenrobot.eventbus
Java package, instead of de.greenrobot.event
.
But the biggest change is in how you write the methods that receive
events. With EventBus 2.x, you had to use a magic name, like onEventMainThread()
.
Starting with version 3.0, you now use Java annotations to identify
and configure the method, which can have any method name that you want.
In the
EventBus/AsyncDemo3
sample project, we have the same code as with the earlier EventBus
sample, but updated to use the subscriber approach. Now, the method
in AsyncDemoFragment
that receives the words is called onWordReady()
:
@Subscribe(threadMode = ThreadMode.MAIN)
public void onWordReady(WordReadyEvent event) {
adapter.add(event.getWord());
}
We indicate to the EventBus system that this method is eligible to receive
WordReadyEvent
events via the @Subscribe
annotation. Java annotations can
be configured with key-value pairs; greenrobot’s EventBus uses this
for things like the thread mode. So, instead of having to have MainThread
at the end of the method name (as with onEventMainThread()
), we can now
indicate threadMode = ThreadMode.MAIN
.
Otherwise, this sample is unchanged from the 2.x edition of the sample.
Overall, this book is slowly migrating to EventBus 3.x, so you will see a mix of 2.x and 3.x for a while.
We will cover much more about jank, and how to detect and diagnose it, in a later chapter.
There are many more features in the greenrobot EventBus implementation. We will see some of those, plus other event bus implementations, in a later chapter on event bus alternatives.