Android中Java層消息機制的簡單分析

一、引言:
Android中的消息機制主要是用於線程間通信,常見的應用場景有apk中,UI只能在主線程中更新,在子線程中是不能的,這個時候,就需要使用消息機制,讓子線程通知主線程更新UI。Android中的消息機制不僅在java層大量使用,native更是頻繁,比如媒體的stagefright框架,Android中消息機制的運轉由四個部分聯合實現,分別是Message、Handler、Looper和MessageQueue。

二、寫個demo感受一下:
理清4者關係之前,我們先寫一個簡單的demo來看看,消息機制是怎麼玩的:我們在Main Activity中創建兩個控件,一個button,一個TextView,點擊button,實現TextView中內容的改變。

  1. 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>
  1. 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:
在這裏插入圖片描述
按鍵改變顯示內容:
在這裏插入圖片描述
三、代碼分析:

  1. 主線程:
    /* 使用匿名內部類簡化代碼 */
    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) {
    }

什麼也沒做,也就是要我們覆寫實現自己的處理邏輯。

  1. 子線程:
    @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 對象。

  1. 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版)》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章