一、引言:
Android中的消息机制主要是用于线程间通信,常见的应用场景有apk中,UI只能在主线程中更新,在子线程中是不能的,这个时候,就需要使用消息机制,让子线程通知主线程更新UI。Android中的消息机制不仅在java层大量使用,native更是频繁,比如媒体的stagefright框架,Android中消息机制的运转由四个部分联合实现,分别是Message、Handler、Looper和MessageQueue。
二、写个demo感受一下:
理清4者关系之前,我们先写一个简单的demo来看看,消息机制是怎么玩的:我们在Main Activity中创建两个控件,一个button,一个TextView,点击button,实现TextView中内容的改变。
- xml配置:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/change_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Change Text" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Hello world"
android:textSize="20sp" />
</RelativeLayout>
- Main Activity代码:
package com.example.androidthreadtest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
public static final int UPDATE_TEXT = 1;
private TextView text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView) findViewById(R.id.text);
Button changeText = (Button) findViewById(R.id.change_text);
changeText.setOnClickListener(this);
}
/* 使用匿名内部类简化代码 */
private Handler handler = new Handler() {
/* 覆写handleMessage方法 */
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_TEXT:
// 在这里可以进行UI操作
text.setText("Nice to meet you");
break;
default:
break;
}
}
};
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.change_text:
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.what = UPDATE_TEXT;
handler.sendMessage(message); // 将Message对象发送出去
}
}).start();
break;
default:
break;
}
}
}
在主活动中,我们监听按键事件,如果按键响应,我们创建一个子线程,在子线程中发送一个message,然后,主线程中收到消息之后,更新TextView中的内容;
3. 测试结果:
未按键前,TextView显示hello world:
按键改变显示内容:
三、代码分析:
- 主线程:
/* 使用匿名内部类简化代码 */
private Handler handler = new Handler() {
/* 覆写handleMessage方法 */
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_TEXT:
// 在这里可以进行UI操作
text.setText("Nice to meet you");
break;
default:
break;
}
}
};
这里,我们直接使用匿名内部类来创建消息处理者handler,并且覆写了其中的handleMessage来处理具体的消息,如果接收到的消息内容为UPDATE_TEXT的话,那么我们就改变TextView中的内容。那么什么是handler?handler即消息处理者,其作用是发送消息和处理消息,通常使用sendMessage()方法,也可以使用延迟发送消息的方法,发送出去的消息经过looper之后,会分发到handle的handleMessage()方法进行处理;其实,也就是自己处理自己发送的消息,只不过,在子线程中发送消息,主线程中处理消息;
原生代码中的sendMessage()方法:
frameworks\base\core\java\android\os\Handler.java
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
什么也没做,也就是要我们覆写实现自己的处理逻辑。
- 子线程:
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.change_text:
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.what = UPDATE_TEXT;
handler.sendMessage(message); // 将Message对象发送出去
}
}).start();
break;
default:
break;
}
}
按键响应中,创建一个子线程,子线程干的事是首先实例化一个message,然后将要发送的消息内容填充,最后通过调用主线程中handler的sendMessage方法将消息发送出去。
这里简单说下message:Message是在线程之间传递的消息, 它可以在内部携带少量的信息, 用于在不同线程之间交换数据。 demo中我们使用到了Message的what 字段, 除此之外还可以使用arg1 和arg2 字段来携带一些整型数据, 使用obj 字段携带一个Object 对象。
- looper该出场了:
目前我们简单说了message和handle,接下来我们需要引出messagequeue的概念了,这是消息队列,用于存放所有使用handler发送的消息,相当于在代码中扮演了“邮箱”的角色,既然存放消息的“邮箱”有了,那么自然还需要一个角色来扮演“邮局”,没错,那就是looper,looper用于分发信息;但是,为什么我们的代码中全程都没有看到looper的存在呢?那是因为我们的主活动是运行在apk的主线程中,主线程已经帮我们创建好了looper,并开启了loop循环,所以,在代码中,looper是蒙着面纱的,简单看下“蒙面者”的部分代码:
frameworks\base\core\java\android\os\Looper.java
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
...
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
这是初始化looper,从构造函数可以看出来,这是一个单例设计模式,所以,每个线程只能有一个looper和MessageQueue。
再来看下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;
...
for (;;) {
/* 如果没有获取消息,则退出loop方法 */
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
/* 调用message类中的dispatchMessage方法去处理消息 */
msg.target.dispatchMessage(msg);
...
}
}
首先是获取当前线程的looper对象,然后进入for循环开始不停地处理消息队列中的消息,如果没有获取到消息,那么就将退出消息队列,如果获取到了消息,那么交由message中的dispatchMessage方法处理,看一下dispatchMessage方法:
public void dispatchMessage(Message msg) {
/* 1.如果message中有callback 函数,则使用回调 */
if (msg.callback != null) {
handleCallback(msg);
} else {
/* 2.如果当前handler有回调,那么使用回调去处理消息 */
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
/* 由handler来处理 */
handleMessage(msg);
}
}
这个方法可以看到,消息处理优先看是否有回调,没有的话才会调用handler的handleMessage来处理。
四、贴图总结:
demo参考:
郭霖老师:《第一行代码:Android(第2版)》