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.
}
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.
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 processMessages (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.
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:
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.
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).
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.
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.
On creation of a HandlerThread, Android will create a thread containing the looper. So, in the main thread the 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);
I like to visualize this as shown below in Fig. 4 where we write the Handler separately and then pass a looper to it on its creation.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
public void run() {
Handler handler = new Handler();
}
}).start();
The above code will have error:
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
Because the thread you're trying to attach the Handler on, doesn't have a looper. And so the constructor of Handler class is throwing exception. You could have used a HandlerThread class instead, This is just a handy class provided by the Android framework.
Please read below for whats happening under the hood.
Lets first try to discuss all parts individually.
- Thread:
a. A thread is just a execution flow. A thread by default is suppose to just execute its runnable(if provided) or call its run method. Upon calling new Thread.start(). A thread just dies and Gc'd when the run method executes all the statement written inside the run(){ ---- }.
b. There is a concept of Looper in Android. Which basically makes the thread a blocking thread. Putting simply it just doesn't let the thread die. It goes in a blocking state and waiting for more messages to again resume its execution.
Below is how you'd setup a blocking thread i.e a thread with a looper.
new Thread(new Runnable() {
public void run() {
Looper.prepare();
Looper.loop();
}
}).start();
Here a thread is created which doesn't just die after executing the Looper.loop() statement. Instead it loops and goes in a blocking state. Now you must be asking what is the meaning of blocking state, how the thread will come out of the blocking state ? how do i finally release this thread now ? This is where Handler comes in
- Handler:
a. It always attaches itself to the Looper of the thread, on which its instance is created.
b. It then processes those messages of the thread it attaches to.
Putting together thread and handlers.
new Thread(new Runnable() {
public void run() {
Looper.prepare();
handler = new Handler();
Looper.loop();
}
}).start();
a. How the handler gets attached to this newly created thread. b. The thread is setup as a blocking looper thread. So this is not going to die.
now, we can 1. Send message on this handler. 2. Post runnable on this handler.
Both of them will be executed on the attached thread.
You have an option to either extend the Handler class and implement method handleMessage(Message msg). or you just provide implementation of the Handler.Callback in the constructor of the handler class. In either way the handleMessage(Messages msg) will be called on the attached thread.
To quit the thread you can send a specific type of message type, and upon receiving that message you'd just call Looper.myLooper().quit()
class LooperThread extends Thread { public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
if(msg.what == -1){
Looper.myLooper().quit();
}
}
};
Looper.loop();
}
}