Android : Passing data between main thread and worker threads.
There may be situations where you want to spawn a thread from your Activity or Service to handle long
running (and may be blocking) tasks. In such cases, its sometimes necessary to pass data back and forth
between the main thread and the worker thread(s). E.g. if the worker thread finishes a task and returns the result
to the main activity to display the results OR you want to keep a worker thread around and ask it to switch
between tasks depending on some message you pass to it.
In the past, I faced some problems understanding these concepts associated with different classes in
android.os like the Handler, Looper, HandlerThread etc...
I'll try to explain these concepts in a simple language. Probably most of these explanations are from the
developer documentation, but I thought consolidating these at one place may help to get a good picture.
Ok. So, when an application runs, it runs in a Main thread called as the UI thread. Any other thread can be
created using the standard java.lang.Thread class. As I said in a typical situation you will spawn a thread,
may be pass some data to it OR the thread may pass back some data to the Main thread from time to time
as well as when its done executing.
Let us consider the task where we need to send data to a worker thread. So first, we create a worker thread.
STEP 1: Create a worker thread
class MyThread extends Thread {
@Override
public void run(){
}
}
In our main thread...
MyThread mThread = new MyThread();
mThread.start();
When you have to pass any messages to a thread or get messages from a thread, the receiving thread
needs a MessageQueue. By default, a thread created by using the java.lang.Thread class will not have a
MessageQueue associated with it. Its just a plain old thread as in the Fig. 1 (Yes, I know. What an innovative
diagram !! :D ).
Now, we need to attach a MessageQueue to our thread. The Looper class provides the method prepare() to
create a message queue for a thread. We need to call this method from the receiving thread's run method.
}
running (and may be blocking) tasks. In such cases, its sometimes necessary to pass data back and forth
between the main thread and the worker thread(s). E.g. if the worker thread finishes a task and returns the result
to the main activity to display the results OR you want to keep a worker thread around and ask it to switch
between tasks depending on some message you pass to it.
In the past, I faced some problems understanding these concepts associated with different classes in
android.os like the Handler, Looper, HandlerThread etc...
I'll try to explain these concepts in a simple language. Probably most of these explanations are from the
developer documentation, but I thought consolidating these at one place may help to get a good picture.
Ok. So, when an application runs, it runs in a Main thread called as the UI thread. Any other thread can be
created using the standard java.lang.Thread class. As I said in a typical situation you will spawn a thread,
may be pass some data to it OR the thread may pass back some data to the Main thread from time to time
as well as when its done executing.
Let us consider the task where we need to send data to a worker thread. So first, we create a worker thread.
STEP 1: Create a worker thread
class MyThread extends Thread {
@Override
public void run(){
}
}
In our main thread...
MyThread mThread = new MyThread();
mThread.start();
When you have to pass any messages to a thread or get messages from a thread, the receiving thread
needs a MessageQueue. By default, a thread created by using the java.lang.Thread class will not have a
MessageQueue associated with it. Its just a plain old thread as in the Fig. 1 (Yes, I know. What an innovative
diagram !! :D ).
Now, we need to attach a MessageQueue to our thread. The Looper class provides the method prepare() to
create a message queue for a thread. We need to call this method from the receiving thread's run method.
STEP 2: Call the Looper methods
class MyThread extends Thread {
@Override
public void run(){
Looper.prepare();
Looper.loop();
}
As you see, there is one more Looper method called in the code. This loop() method will start running the
message loop for the current thread. In simple terms, it will start looking at the MessageQueue and processing
the messages. This is how I interpret the Looper as in Fig. 2.
message loop for the current thread. In simple terms, it will start looking at the MessageQueue and processing
the messages. This is how I interpret the Looper as in Fig. 2.
But, who sends the messages to the MessageQueue and how are these processed ? There is a class called
the Handler. The Hander allows us to send and process Messages (as well as Runnable Objects) associated
with the thread's MessageQueue. So, we need to create a Handler. It is important to note that the Handler
is associated with the thread that creates it ! The Handler provides methods to handle (receive) Messages
as well as send and schedule messages. For details, please refer to documentation.
the Handler. The Hander allows us to send and process Messages (as well as Runnable Objects) associated
with the thread's MessageQueue. So, we need to create a Handler. It is important to note that the Handler
is associated with the thread that creates it ! The Handler provides methods to handle (receive) Messages
as well as send and schedule messages. For details, please refer to documentation.
STEP 3: Create a Handler to receive the Messages
class MyThread extends Thread {
public Handler mHandler;
@Override
public void run(){
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// Act on the message
}
};
Looper.loop();
}
}
If you notice, this code is the same that is listed on the Looper documentation page here. Few things to
mention here are. The Handler is created in this thread, hence it is associated with the default Looper
(read MessageQueue) in the current thread. There are constructors for the Handler that allow us to specify
the Looper (again, read MessageQueue). This allows us to write a cleaner code by writing the Handler class
separately and passing on a Looper (again, again, read MessageQueue) when the Handler is created. I'll get
to this in a while. But as I have insisted, it is worth noting that whenever the developer documentation refers
to a Looper, you can assume they are talking about a queue. I'm really not sure why they have surfaced the
Looper class. It creates more confusion (at least for me). Also, when dealing with passing the messages, with
this mechanism, we really need not care of the MessageQueue call. That is the reason I haven't linked it to the
documentation. Anyways... things are what they are ! For me, I like to interpret this whole mechanism as
depicted in Fig. 3.
mention here are. The Handler is created in this thread, hence it is associated with the default Looper
(read MessageQueue) in the current thread. There are constructors for the Handler that allow us to specify
the Looper (again, read MessageQueue). This allows us to write a cleaner code by writing the Handler class
separately and passing on a Looper (again, again, read MessageQueue) when the Handler is created. I'll get
to this in a while. But as I have insisted, it is worth noting that whenever the developer documentation refers
to a Looper, you can assume they are talking about a queue. I'm really not sure why they have surfaced the
Looper class. It creates more confusion (at least for me). Also, when dealing with passing the messages, with
this mechanism, we really need not care of the MessageQueue call. That is the reason I haven't linked it to the
documentation. Anyways... things are what they are ! For me, I like to interpret this whole mechanism as
depicted in Fig. 3.
Let me know if you like PAT or your way of viewing it !
So, now any other thread having the reference to mHandler will be able to call the send or post methods of
the Handler class to send a Message (or a runnable object) to our thread. The code for sending message will
look like:
the Handler class to send a Message (or a runnable object) to our thread. The code for sending message will
look like:
Message msg = Message.obtain();
msg.obj = // Some Arbitrary object
mHandler.sendMessage(msg);
Pretty simple yeah ! Btw, there are various methods to set/get data for the Message object which can be found
in the developer documentation for the Message class.
in the developer documentation for the Message class.
Summary of Steps :
1. Create a Handler in the receiving thread [If it is the main thread, create one in the main thread]. By default the
handler will be associated with the default queue (Looper).
handler will be associated with the default queue (Looper).
2. So, if the receiving thread is created by using java.lang.Thread, then we need to call the Looper.prepare() and
Looper.loop() in order to set up the message queue for the thread.
Looper.loop() in order to set up the message queue for the thread.
3. In the sending thread, prepare a message object (or a Runnable object)
4. Get a reference to the Handler in the sending thread and call the send/post methods on it to send a message.
HandlerThread:
Since by default java.lang.Thread doesn't contain a message queue (Looper), Android provides a class called
as the HandlerThread which already contains a message queue. The only difference in using the HandlerThread
class over the method described above is that you need not call the Looper.* methods.
as the HandlerThread which already contains a message queue. The only difference in using the HandlerThread
class over the method described above is that you need not call the Looper.* methods.
On creation of a HandlerThread, Android will create a thread containing the looper. So, in the main thread the
code will look like:
code will look like:
HandlerThread myThread = new HandlerThread("Worker Thread");
myThread.start();
We separately create a Handler as follows:
class MyHandler extends Handler {
public MyHandler(Looper myLooper) {
super(myLooper);
}
public void handleMessage(Message msg) {
}
public void handleMessage(Message msg) {
}
}
}
Now in the main thread, we get the looper for the HandlerThread and pass it when we create the Handler as follows:
Looper mLooper = myThread.getLooper();
MyHandler mHandler = new MyHandler(mLooper);
Whenever we want to send a message from the main thread, we do it in a similar fashion.
Message msg = mHandler.obtainMessage();
msg.obj = // Some Arbitrary object
mHandler.sendMessage(msg);
No comments:
Post a Comment