Android开发之Handler消息机制
程序员文章站
2022-07-14 15:06:00
...
概述
Android的消息机制主要指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程,这三者实际上是一个整体。
Handler的作用是将一个任务切换到某个制定的线程中去执行;MessageQueue是一个用单链表的数据结构实现的消息队列,用来来存储消通过Handler发送的消息;Looper是一个消息循环处理类,Looper会以无限循环的形式去查找是否有新消息,有的话就处理,否则就一直等待。
handler的使用
每次你新创建一个Handle对象,它会绑定于创建它的线程(也就是UI线程)以及该线程的消息队列,Handler可以把一个Message对象或者Runnable对象压入到消息队列中,进而在UI线程中获取Message或者执行Runnable对象,Handler把压入消息队列有两类方式
(1)post方式
对于Handler的Post方式来说,它会传递一个Runnable对象到消息队列中,在这个Runnable对象中,重写run()方法。一般在这个run()方法中写入需要在UI线程上的操作。
public class MainActivity extends AppCompatActivity {
private Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView textView = (TextView) findViewById(R.id.textView);
new Thread(){
@Override
public void run() {
//在此执行耗时操作,如网络请求,文件读写等
//...
//耗时操作执行完毕后,更新UI
handler.post(new Runnable() {
@Override
public void run() {
textView.setText("耗时任务执行完毕,在主线程更新UI");
textView.setText("耗时任务执行完毕,在主线程更新U2");
textView.setText("耗时任务执行完毕,在主线程更新U3");
}
});
super.run();
}
}.start();
}
}
(2)sendMessage方式
handler类需在主线程中重写handleMessage方法,用于获取工作线程传递过来的数据。在工作线程中,耗时操作执行完毕后,会实例化一个Message对象,然后调用handler.sendMessage(Message)方法将消息传输到主线程。
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView textView = (TextView) findViewById(R.id.textView);
final Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
textView.setText("更新主线程UI");
break;
}
}
};
new Thread(){
@Override
public void run() {
//在此执行耗时操作,如网络请求,文件读写等
//...
//耗时操作执行完毕后,更新UI
Message message = new Message();
message.obj = 1;
message.arg1 = 100;
handler.sendMessage(message);
}
}.start();
}
ThreadLocal的工作原理
ThreadLocal是一个线程内部的数据存储类,通过它可以在指定线程中存储数据,数据存储后,只能在指定线程中获取到存储的数据。一般来说,当数据是以线程为作用域并且不同线程具有不同的数据副本的时候,可以考虑采用ThreadLocal。
对于Handler来说,他需要获取当前线程的Looper,而Looper的作用域就是线程并且不同线程有不同的Looper,这个时候通过ThreadLocal就可以轻松实现Looper在线程中的存取。
注意:ThreadLocal的另一个使用场景是负责逻辑下的对象传递。
MessageQueue的工作原理
MessageQueue主要包含两个操作:插入(enqueMessage)和读取(next)
其中next()方法是一个无限循环的方法,如果队列中没有消息,next方法就一直阻塞,当有新的消息来时,next()方法会返回这条消息并将其从队列中删除。
注意:MessageQueue内部有一个boolean类型的mQuitting变量,在每次查询是否有消息存在之后,都会判断下mQuitting变量是否为true,如果为true表示消息队列要终止,则next()方法会返回null。
Looper的工作原理
Looper在Android的消息机制中扮演着消息循环的角色,它会不停地从MessageQueue中查看是否有新消息,如果有就会立刻处理,否则就一直阻塞在那里。
在Android的UI线程中,已经为我们创建好了Looper实例,如果想在非UI线程中Handler与Looper,可用如下代码实现:
new Thread(){
@Override
public void run() {
//1.创建Looper实例
Looper.prepare();
//2.创建Handler实例
Handler handler = new Handler();
//3.开启消息循环
Looper.loop();
}
}.start();
在Looper.prepare()方法中会先调用ThreadLocal类查询该线程是否已有其他Looper实例,如果有则抛出异常,因为一个线程只能有一个Looper实例。如果没有,就调用Looper的构造方法,生成一个Looper实例。Looper的构造方法如下:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
在Looper的构造方法中会生成一个MessageQueue的消息实例,用于存储handler生成的消息。注意:只有在创建了Looper实例之后,才能创建Handler实例,因为Handler在创建的时候,会判断Looper是否存在,不存在的话会抛出异常。
Looper.prepare()方法只是产生了消息队列的实例,并没有真正进行消息循环,只有调用了Looper.loop()之后,消息循环才正式开启。
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}
在Looper的loop()方法中,会开启一个for (;;) 的死循环,利用这个循环不断调用MessageQueue.next()方法查询消息队列中的消息,next()方法是一个阻塞方法,如果没有消息就会一直阻塞,有消息就会返回消息msg,这也导致looper()会一直阻塞在那里。获得消息msg后,就调用 msg.target.dispatchMessage(msg)处理这条消息,这里的msg.target是发送这条消息的Handler,这样Handler发送的消息最终又交给它的dispatchMessage方法来处理了。
所以说 Handler与MessageQueue、Looper的关系是N:1:1。
对于loop()方法内的死循环,唯一跳出循环的方法是消息队列返回null,为此需要调用Looper的quit()方法或者quitSafety()方法,二者的区别是quit()方法是直接退出Looper,而quitSafety()会等消息队列中的消息处理完后再退出。
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
主线程的消息循环
Android的主线程是ActivityThread,在ActivityThread的入口方法main中,系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MwssageQueue,并通过Looper.loop()来开启主线程的消息循环。
并且在主线程也定义了和消息队列进行交互的Handler,它内部定义了一组消息类型,主要包括四大组件的启动和停止等过程。
所以我们在主线程创建Handler的时候,其实主线程就存在多个Handler了,不同Handler发出的消息最终还是发给另外特定的Handler,主要是通过handler.target进行识别。