Implementing a Basic Handler for Intra-Process Communication
A handler is primarily used for intra-process communication.
First, let's create a handler:
public class Handler {
public void handleMessage(Message msg) {
}
public void sendMessage(Message msg) {
msg.setHandler(this);
Looper.messageQueue.enqueueMessage(msg);
}
}
These are the two main methods: one for sending and one for receiving.
public class Message {
String content;
Handler target;
Message next;
public Message(String content) {
this.content = content;
}
public void setHandler(Handler handler) {
this.target = handler;
}
public Handler getHandler() {
return this.target;
}
@Override
public String toString() {
return "Message{" +
"content='" + content + '\'' +
'}';
}
}
This is a simplified Message class.
When sending and receiving data, if we only send one piece of data, we can directly use the handler's methods. However, with large amounts of data, using send repeatedly could block the thread. To avoid this, we add a queue to store data to be sent and process it gradually.
public class MessageQueue {
Message head = null;
public void enqueueMessage(Message msg) {
Message current = head;
if (current == null) {
msg.next = current;
head = msg;
} else {
Message previous;
for (;;) {
previous = current;
current = current.next;
if (current == null) {
break;
}
}
msg.next = current;
previous.next = msg;
}
Log.d("HandlerDemo", "enqueueMessage: " + msg.toString());
Log.d("HandlerDemo", "enqueueMessage: " + head.toString());
}
public Message next() {
synchronized (this) {
if (head == null) {
return null;
}
Message msg = head;
head = msg.next;
msg.next = null;
return msg;
}
}
}
With messages added to the queue, we need a class to execute them, which leads to the Looper.
A question arises: can there be multiple loopers? Consider this method:
public static void loop() {
for (;;) {
final Looper me = threadLocal.get();
Message msg = me.messageQueue.next();
if (msg == null) {
break;
}
msg.target.handleMessage(msg);
}
}
This is a simplified loop method with an infinite for-loop. If there were multiple loopers, the first loop might not finish, causing subsequent ones to block, rendering the queue ineffective. To ensure only one looper exists, we use ThreadLocal. Look at its set method:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
The key is the current thread, and we can store a looper as the value, ensuring a unique looper per thread.
Here's the implementation:
public class Looper {
public static MessageQueue messageQueue = new MessageQueue();
static final ThreadLocal<Looper> threadLocal = new ThreadLocal<Looper>();
public static void prepare() {
if (threadLocal.get() != null) {
// Handle existing looper
}
threadLocal.set(new Looper());
}
public static void loop() {
for (;;) {
final Looper me = threadLocal.get();
Message msg = me.messageQueue.next();
if (msg == null) {
break;
}
msg.target.handleMessage(msg);
}
}
}
In prepare(), we ensure only one looper exists, and loop() executes it.
Finally, here's the usage example:
public class MainActivity extends Activity {
Handler handler = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d("HandlerDemo", "onCreate: activity_main");
Looper.prepare();
new Thread(new Runnable() {
@Override
public void run() {
Log.d("HandlerDemo", "thread");
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.d("HandlerDemo", "handleMessage: " + msg.toString());
}
};
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
Log.d("HandlerDemo", "onCreate: send");
handler.sendMessage(new Message("message1"));
handler.sendMessage(new Message("message2"));
handler.sendMessage(new Message("message3"));
Looper.loop();
}
}
This implements a basic handler communication system.
Note: Memory leaks can occur due to long-lived objects holding references to short-lived ones (e.g., ThreadLocal -> Queue -> Message -> Handler -> Activity). A simple solution is to use static methods to mitigate this.