一、引言:
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版)》