An Interest In:
Web News this Week
- March 27, 2024
- March 26, 2024
- March 25, 2024
- March 24, 2024
- March 23, 2024
- March 22, 2024
- March 21, 2024
Practical Concurrency on Android With HaMeR
In Understanding Concurrency on Android Using HaMeR, we talked about the basics of the HaMeR (Handler
, Message
, and Runnable
) framework. We covered its options, as well as when and how to use it.
Today, we’ll create a simple application to explore the concepts learned. With a hands-on approach, we’ll see how to apply the different possibilities of HaMeR in managing concurrency on Android.
1. The Sample Application
Let’s get to work and post some Runnable
and send Message
objects on a sample application. To keep it as simple as possible, we’ll explore only the most interesting parts. All resource files and standard activity calls will be ignored here. So I strongly advise you to check out the source code of the sample application with its extensive comments.
The app will consist of:
- Two activities, one for
Runnable
another forMessage
calls - Two
HandlerThread
objects:WorkerThread
to receive and process calls from the UICounterThread
to receiveMessage
calls from theWorkerThread
- Some utility classes (to preserve objects during configuration changes and for layout)
2. Posting and Receiving Runnables
Let's begin experimenting with the Handler.post(Runnable)
method and its variations, which add a runnable to a MessageQueue
associated with a thread. We'll create an activity called RunnableActivity
, which communicates with a background thread called WorkerThread
.
The RunnableActivity
instantiates a background thread called WorkerThread
, passing a Handler
and a WorkerThread.Callback
as parameters. The activity can make calls on WorkerThread
to asynchronously download a bitmap and exhibit a toast at a certain time. The results of the tasks done by the worker thread are passed to RunnableActivity
by runnables posted on the Handler
received by WorkerThread
.
2.1 Preparing a Handler for RunnableActivity
On the RunnableActivity
we'll create a Handler
to be passed to WorkerThread
. The uiHandler
will be associated with the Looper
from the UI thread, since it's being called from that thread.
public class RunnableActivity extends Activity {
// Handler that allows communication between
// the WorkerThread and the Activity
protected Handler uiHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// prepare the UI Handler to send to WorkerThread
uiHandler = new Handler();
}
}
2.2 Declaring WorkerThread
and Its Callback Interface
The WorkerThread
is a background thread where we'll start different kinds of tasks. It communicates with the user interface using the responseHandler
and a callback interface received during its instantiation. The references received from the activities are WeakReference<>
type, since an activity could be destroyed and the reference lost.
The class offers an interface that can be implemented by the UI. It also extends HandlerThread
, a helper class built on top of Thread
that already contains a Looper
, and a MessageQueue
. Hence it has the correct setup to use the HaMeR framework.
public class WorkerThread extends HandlerThread {
/**
* Interface to facilitate calls on the UI.
*/
public interface Callback {
void loadImage(Bitmap image);
void showToast(String msg);
}
// This Handler will be responsible only
// for posting Runnables on this Thread
private Handler postHandler;
// Handler is received from the MessageActivity and RunnableActivity
// responsible for receiving Runnable calls that will be processed
// on the UI. The callback will help this process.
private WeakReference<Handler> responseHandler;
// Callback from the UI
// it is a WeakReference because it can be invalidated
// during "configuration changes" and other events
private WeakReference<Callback> callback;
private final String imageAUrl =
"https://pixabay.com/static/uploads/photo/2016/08/05/18/28/mobile-phone-1572901_960_720.jpg";
/**
* The constructor receives a Handler and a Callback from the UI
* @param responseHandler in charge of posting the Runnable to the UI
* @param callback works together with the responseHandler
* allowing calls directly on the UI
*/
public WorkerThread(Handler responseHandler, Callback callback) {
super(TAG);
this.responseHandler = new WeakReference<>(responseHandler);
this.callback = new WeakReference<>(callback);
}
}
2.3 Initializing WorkerThread
We need to add a method to WorkerThread
to be called by the activities that prepare the thread's postHandler
for use. The method needs to be called only after the thread is started.
public class WorkerThread extends HandlerThread {
/**
* Prepare the postHandler.
* It must be called after the thread has started
*/
public void prepareHandler() {
postHandler = new Handler(getLooper());
}
}
On the RunnableActivity
we must implement WorkerThread.Callback
and initialize the thread so it can be used.
public class RunnableActivity extends Activity
implements WorkerThread.Callback {
// BackgroundThread responsible for downloading the image
protected WorkerThread workerThread;
/**
* Initialize the {@link WorkerThread} instance
* only if it hasn't been initialized yet.
*/
public void initWorkerThread(){
if ( workerThread == null ) {
workerThread = new WorkerThread(uiHandler, this);
workerThread.start();
workerThread.prepareHandler();
}
}
/**
* set the image downloaded on bg thread to the imageView
*/
@Override
public void loadImage(Bitmap image) {
myImage.setImageBitmap(image);
}
@Override
public void showToast(final String msg) {
// to be implemented
}
}
2.4 Using Handler.post()
on the WorkerThread
The WorkerThread.downloadWithRunnable()
method downloads a bitmap and sends it to RunnableActivity
to be displayed in an image View. It illustrates two basic uses of the Handler.post(Runnable run)
command:
To allow a Thread to post a Runnable object to a MessageQueue associated with itself when.post()
is called on a Handler associated with the Thread's Looper.- To allow communication with other Threads, when
.post()
is called on a Handler associated with other Thread's Looper.
- The
WorkerThread.downloadWithRunnable()
method posts aRunnable
to theWorkerThread
'sMessageQueue
using thepostHandler
, aHandler
associated withWorkThread
'sLooper
. - When the runnable is processed, it downloads a
Bitmap
on theWorkerThread
. - After the bitmap is downloaded, the
responseHandler
, a handler associated with the UI thread, is used to post a runnable on theRunnableActivity
containing the bitmap. - The runnable is processed, and the
WorkerThread.Callback.loadImage
is used to exhibit the downloaded image on anImageView
.
public class WorkerThread extends HandlerThread {
/**
* post a Runnable to the WorkerThread
* Download a bitmap and sends the image
* to the UI {@link RunnableActivity}
* using the {@link #responseHandler} with
* help from the {@link #callback}
*/
public void downloadWithRunnable() {
// post Runnable to WorkerThread
postHandler.post(new Runnable() {
@Override
public void run() {
try {
// sleeps for 2 seconds to emulate long running operation
TimeUnit.SECONDS.sleep(2);
// Download image and sends to UI
downloadImage(imageAUrl);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
/**
* Download a bitmap using its url and
* send to the UI the image downloaded
*/
private void downloadImage(String urlStr){
// Create a connection
HttpURLConnection connection = null;
try {
URL url = new URL(urlStr);
connection = (HttpURLConnection) url.openConnection();
// get the stream from the url
InputStream in = new BufferedInputStream(connection.getInputStream());
final Bitmap bitmap = BitmapFactory.decodeStream(in);
if ( bitmap != null ) {
// send the bitmap downloaded and a feedback to the UI
loadImageOnUI( bitmap );
} else {
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if ( connection != null )
connection.disconnect();
}
}
/**
* sends a Bitmap to the ui
* posting a Runnable to the {@link #responseHandler}
* and using the {@link Callback}
*/
private void loadImageOnUI(final Bitmap image){
Log.d(TAG, "loadImageOnUI("+image+")");
if (checkResponse() ) {
responseHandler.get().post(
new Runnable() {
@Override
public void run() {
callback.get().loadImage(image);
}
}
);
}
}
// verify if responseHandler is available
// if not the Activity is passing by some destruction event
private boolean checkResponse(){
return responseHandler.get() != null;
}
}
2.5 Using Handler.postAtTime()
and Activity.runOnUiThread()
The WorkerThread.toastAtTime()
schedules a task to be executed at a certain time, exhibiting a Toast
to the user. The method illustrates the use of the Handler.postAtTime()
and the Activity.runOnUiThread()
.
Handler.postAtTime(Runnable run, long uptimeMillis)
posts a runnable at a given time.Activity.runOnUiThread(Runnable run)
uses the default UI handler to post a runnable to the main thread.
public class WorkerThread extends HandlerThread {
/**
* show a Toast on the UI.
* schedules the task considering the current time.
* It could be scheduled at any time, we're
* using 5 seconds to facilitates the debugging
*/
public void toastAtTime(){
Log.d(TAG, "toastAtTime(): current - " + Calendar.getInstance().toString());
// seconds to add on current time
int delaySeconds = 5;
// testing using a real date
Calendar scheduledDate = Calendar.getInstance();
// setting a future date considering the delay in seconds define
// we're using this approach just to facilitate the testing.
// it could be done using a user defined date also
scheduledDate.set(
scheduledDate.get(Calendar.YEAR),
scheduledDate.get(Calendar.MONTH),
scheduledDate.get(Calendar.DAY_OF_MONTH),
scheduledDate.get(Calendar.HOUR_OF_DAY),
scheduledDate.get(Calendar.MINUTE),
scheduledDate.get(Calendar.SECOND) + delaySeconds
);
Log.d(TAG, "toastAtTime(): scheduling at - " + scheduledDate.toString());
long scheduled = calculateUptimeMillis(scheduledDate);
// posting Runnable at specific time
postHandler.postAtTime(
new Runnable() {
@Override
public void run() {
if ( callback != null ) {
callback.get().showToast(
"Toast called using 'postAtTime()'."
);
}
}
}, scheduled);
}
/**
* Calculates the {@link SystemClock#uptimeMillis()} to
* a given Calendar date.
*/
private long calculateUptimeMillis(Calendar calendar){
long time = calendar.getTimeInMillis();
long currentTime = Calendar.getInstance().getTimeInMillis();
long diff = time - currentTime;
return SystemClock.uptimeMillis() + diff;
}
}
public class RunnableActivity extends Activity
implements WorkerThread.Callback {
/**
* Callback from {@link WorkerThread}
* Uses {@link #runOnUiThread(Runnable)} to illustrate
* such method
*/
@Override
public void showToast(final String msg) {
Log.d(TAG, "showToast("+msg+")");
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG).show();
}
});
}
}
3. Sending Messages With the MessageActivity
& WorkerThread
Next, let's explore some different ways of using MessageActivity
to send and process Message
objects. The MessageActivity
instantiates WorkerThread
, passing a Handler
as a parameter. The WorkerThread
has some public methods with tasks to be called by the activity to download a bitmap, download a random bitmap, or exhibit a Toast
after some delayed time. The results of all those operations are sent back to MessageActivity
using Message
objects sent by the responseHandler
.
3.1 Preparing the Response Handler From MessageActivity
As in the RunnableActivity
, in the MessageActivity
we'll have to instantiate and initialize a WorkerThread
sending a Handler
to receive data from the background thread. However, this time we won't implement WorkerThread.Callback
; instead, we'll receive responses from the WorkerThread
exclusively by Message
objects.
Since most of the MessageActivity
and RunnableActivity
code is basically the same, we'll concentrate only on the uiHandler
preparation, which will be sent to WorkerThread
to receive messages from it.
First, let's provide some int
keys to be used as identifiers to the Message objects.
public class MessageActivity extends Activity {
// Message identifier used on Message.what() field
public static final int KEY_MSG_IMAGE = 2;
public static final int KEY_MSG_PROGRESS = 3;
public static final int KEY_MSG_TOAST = 4;
}
On MessageHandler
implementation, we'll have to extend Handler
and implement the handleMessage(Message)
method, where all messages will be processed. Notice that we're fetching Message.what
to identify the message, and we're also getting different kinds of data from Message.obj
. Let's quickly review the most important Message
properties before diving into the code.
Message.what
:int
identifying theMessage
Message.arg1
:int
arbitrary argumentMessage.arg2
:int
arbitrary argumentMessage.obj
:Object
to store different kinds of data
public class MessageActivity extends Activity {
/**
* Handler responsible to manage communication
* from the {@link WorkerThread}. It sends Messages
* back to the {@link MessageActivity} and handle
* those Messages
*/
public class MessageHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
// handle image
case KEY_MSG_IMAGE:{
Bitmap bmp = (Bitmap) msg.obj;
myImage.setImageBitmap(bmp);
break;
}
// handle progressBar calls
case KEY_MSG_PROGRESS: {
if ( (boolean) msg.obj )
progressBar.setVisibility(View.VISIBLE);
else
progressBar.setVisibility(View.GONE);
break;
}
// handle toast sent with a Message delay
case KEY_MSG_TOAST:{
String msgText = (String)msg.obj;
Toast.makeText(getApplicationContext(), msgText, Toast.LENGTH_LONG ).show();
break;
}
}
}
}
// Handler that allows communication between
// the WorkerThread and the Activity
protected MessageHandler uiHandler;
}
3.2 Sending Messages With WorkerThread
Now let's get back to the WorkerThread
class. We'll add some code to download a specific bitmap and also code to download a random one. To accomplish those tasks, we'll send Message
objects from the WorkerThread
to itself and send the results back to MessageActivity
using exactly the same logic applied earlier for the RunnableActivity
.
First we need to extend the Handler
to process the downloaded messages.
public class WorkerThread extends HandlerThread {
// send and processes download Messages on the WorkerThread
private HandlerMsgImgDownloader handlerMsgImgDownloader;
/**
* Keys to identify the keys of {@link Message#what}
* from Messages sent by the {@link #handlerMsgImgDownloader}
*/
private final int MSG_DOWNLOAD_IMG = 0; // msg that download a single img
private final int MSG_DOWNLOAD_RANDOM_IMG = 1; // msg that download random img
/**
* Handler responsible for managing the image download
* It send and handle Messages identifying then using
* the {@link Message#what}
* {@link #MSG_DOWNLOAD_IMG} : single image
* {@link #MSG_DOWNLOAD_RANDOM_IMG} : random image
*/
private class HandlerMsgImgDownloader extends Handler {
private HandlerMsgImgDownloader(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
showProgressMSG(true);
switch ( msg.what ) {
case MSG_DOWNLOAD_IMG: {
// receives a single url and downloads it
String url = (String) msg.obj;
downloadImageMSG(url);
break;
}
case MSG_DOWNLOAD_RANDOM_IMG: {
// receives a String[] with multiple urls
// downloads a image randomly
String[] urls = (String[]) msg.obj;
Random random = new Random();
String url = urls[random.nextInt(urls.length)];
downloadImageMSG(url);
}
}
showProgressMSG(false);
}
}
}
The downloadImageMSG(String url)
method is basically the same as the downloadImage(String url)
method. The only difference is that the first sends the downloaded bitmap back to the UI by sending a message using the responseHandler
.
public class WorkerThread extends HandlerThread {
/**
* Download a bitmap using its url and
* display it to the UI.
* The only difference with {@link #downloadImage(String)}
* is that it sends the image back to the UI
* using a Message
*/
private void downloadImageMSG(String urlStr){
// Create a connection
HttpURLConnection connection = null;
try {
URL url = new URL(urlStr);
connection = (HttpURLConnection) url.openConnection();
// get the stream from the url
InputStream in = new BufferedInputStream(connection.getInputStream());
final Bitmap bitmap = BitmapFactory.decodeStream(in);
if ( bitmap != null ) {
// send the bitmap downloaded and a feedback to the UI
loadImageOnUIMSG( bitmap );
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if ( connection != null )
connection.disconnect();
}
}
}
The loadImageOnUIMSG(Bitmap image)
is responsible for sending a message with the downloaded bitmap to MessageActivity
.
/**
* sends a Bitmap to the ui
* sending a Message to the {@link #responseHandler}
*/
private void loadImageOnUIMSG(final Bitmap image){
if (checkResponse() ) {
sendMsgToUI(
responseHandler.get().obtainMessage(MessageActivity.KEY_MSG_IMAGE, image)
);
}
}
/**
* Show/Hide progressBar on the UI.
* It uses the {@link #responseHandler} to
* send a Message on the UI
*/
private void showProgressMSG(boolean show){
Log.d(TAG, "showProgressMSG()");
if ( checkResponse() ) {
sendMsgToUI(
responseHandler.get().obtainMessage(MessageActivity.KEY_MSG_PROGRESS, show)
);
}
}
Notice that instead of creating a Message
object from scratch, we're using the Handler.obtainMessage(int what, Object obj)
method to retrieve a Message
from the global pool, saving some resources. It's also important to note that we're calling the obtainMessage()
on the responseHandler
, obtaining a Message
associated with MessageActivity
's Looper
. There are two ways to retrieve a Message
from the global pool: Message.obtain()
and Handler.obtainMessage()
.
The only thing left to do on the image download task is to provide the methods to send a Message
to WorkerThread
to start the download process. Notice that this time we'll call Message.obtain(Handler handler, int what, Object obj)
on handlerMsgImgDownloader
, associating the message with WorkerThread
's looper.
/**
* sends a Message to the current Thread
* using the {@link #handlerMsgImgDownloader}
* to download a single image.
*/
public void downloadWithMessage(){
Log.d(TAG, "downloadWithMessage()");
showOperationOnUIMSG("Sending Message...");
if ( handlerMsgImgDownloader == null )
handlerMsgImgDownloader = new HandlerMsgImgDownloader(getLooper());
Message message = Message.obtain(handlerMsgImgDownloader, MSG_DOWNLOAD_IMG,imageBUrl);
handlerMsgImgDownloader.sendMessage(message);
}
/**
* sends a Message to the current Thread
* using the {@link #handlerMsgImgDownloader}
* to download a random image.
*/
public void downloadRandomWithMessage(){
Log.d(TAG, "downloadRandomWithMessage()");
showOperationOnUIMSG("Sending Message...");
if ( handlerMsgImgDownloader == null )
handlerMsgImgDownloader = new HandlerMsgImgDownloader(getLooper());
Message message = Message.obtain(handlerMsgImgDownloader, MSG_DOWNLOAD_RANDOM_IMG, imagesUrls);
handlerMsgImgDownloader.sendMessage(message);
}
Another interesting possibility is sending Message
objects to be processed at a later time with the command Message.sendMessageDelayed(Message msg, long timeMillis)
.
/**
* Show a Toast after a delayed time.
*
* send a Message with delayed time on the WorkerThread
* and sends a new Message to {@link MessageActivity}
* with a text after the message is processed
*/
public void startMessageDelay(){
// message delay
long delay = 5000;
String msgText = "Hello from WorkerThread!";
// Handler responsible for sending Message to WorkerThread
// using Handler.Callback() to avoid the need to extend the Handler class
Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
responseHandler.get().sendMessage(
responseHandler.get().obtainMessage(MessageActivity.KEY_MSG_TOAST, msg.obj)
);
return true;
}
});
// sending message
handler.sendMessageDelayed(
handler.obtainMessage(0,msgText),
delay
);
}
We created a Handler
expressly for sending the delayed message. Instead of extending the Handler
class, we took the route of instantiating a Handler
by using the Handler.Callback
interface, for which we implemented the handleMessage(Message msg)
method to process the delayed Message
.
4. Conclusion
You've seen enough code by now to understand how to apply the basic HaMeR framework concepts to manage concurrency on Android. There are some other interesting features of the final project stored on GitHub, and I strongly advise you to check it out.
Finally, I have some last considerations that you should keep in mind:
Don't forget to consider Android's Activity lifecycle when working with HaMeR and Threads in general. Otherwise, your app may fail when the thread tries to access activities that have been destroyed due to configuration changes or for other reasons. A common solution is to use aRetainedFragment
to store the thread and populate the background thread with the activity's reference every time the activity is destroyed. Take a look at the solution in the final project on GitHub.- Tasks that run due to
Runnable
andMessage
objects processed onHandlers
don't run asynchronously. They'll run synchronously on the thread associated with the handler. To make it asynchronous, you'll need to create another thread, send/post theMessage
/Runnable
object on it, and receive the results at the appropriate time.
As you can see, the HaMeR framework has lots of different possibilities, and it's a fairly open solution with a lot of options for managing concurrency on Android. These characteristics can be advantages over AsyncTask
, depending on your needs. Explore more of the framework and read the documentation, and you'll create great things with it.
See you soon!
Original Link:
TutsPlus - Code
Tuts+ is a site aimed at web developers and designers offering tutorials and articles on technologies, skills and techniques to improve how you design and build websites.More About this Source Visit TutsPlus - Code