天天看點

Handler基本原理先來說一下流程Handler的簡單使用

在Android開發中,Handler機制是一個很重要的知識點,主要作用是消息通信。

Handler基本原理

Handler主要用于異步消息的處理:當發出一個消息之後,首先進入一個消息隊列,發送消息的函數即刻傳回,而另外一個部分在消息隊列中逐一将消息取出,然後對消息進行處理,也就是發送消息和接收消息不是同步的處理。 這種機制通常用來處理相對耗時比較長的操作。

先來說一下流程

在ActivityThread中通過prepareMainLooper()建立looper,并通過threadLocal将目前線程與Looper綁定【一個線程綁定一個Looper】,同時也建立了MessageQueue【一個Looper維護一個MessageQueue隊列】。

然後Looper.loop()開啟死循環,當沒有message時,循環是阻塞的

handler在子線程中發送了一條消息,将handler執行個體指派給target,并将消息按時間的順序插入到MessageQueue隊列中,循環會被喚醒,looper取出消息,通過target開始分發到對應的handler回調方法中【一個線程可以對應多個Handler】

各種類的含義

Message:消息,其中包含了消息ID,消息處理對象以及處理的資料等,由MessageQueue統一列隊,終由Handler處理。

Handler:處理者,負責Message的發送及處理。使用Handler時,需要實作handleMessage(Message msg)方法來對特定的Message進行處理,例如更新UI等。

MessageQueue:消息隊列,用來存放Handler發送過來的消息,并按照FIFO規則執行。當然,存放Message并非實際意義的儲存,而是将Message以連結清單的方式串聯起來的,等待Looper的抽取。

Looper:消息泵,不斷地從MessageQueue中抽取Message執行。是以,一個MessageQueue需要一個Looper。

Thread:線程,負責排程整個消息循環,即消息循環的執行場所

可以把handler基本工作原理看作一個傳送履帶,HandlerMessage發送消息,消息内容會往MessageQueue消息隊列中添加,同時開啟線程,一個線程對應一個Looper,Looper.loop()方法會無限循環調用MessageQueue的next()方法來擷取新消息,而next是是一個阻塞操作,但沒有資訊時,next方法會一直阻塞在那裡,這也導緻loop方法一直阻塞在那裡。如果MessageQueue的next方法傳回了新消息,Looper就會處理這條消息。最後handler接收處理消息。

Handler的簡單使用

我們一般使用handler發送消息,隻需要兩步,首先是建立一個Handler對象,并重寫handleMessage方法,就是上圖中的3(Message.target.handleMeesage),然後需要消息通信的地方,通過Handler的sendMessage方法發送消息(這裡我們建立了一個子線程,模拟子線程向主線程發送消息)。

public class MainActivity extends Activity {
    private static final String TAG = "MainActivity";

    private Handler mHandler;
    private Button btnSendeToMainThread;
    private static final int MSG_SUB_TO_MAIN= 100;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 1.建立Handler,并重寫handleMessage方法
        mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                // 處理消息
                switch (msg.what) {
                    case MSG_SUB_TO_MAIN:
                        // 列印出處理消息的線程名和Message.obj
                        Log.e(TAG, "接收到消息: " +    Thread.currentThread().getName() + ","+ msg.obj);
                        break;
                    default:
                        break;
                }
            }
        };

        btnSendeToMainThread = (Button) findViewById(R.id.btn_sendto_mainthread);
        btnSendeToMainThread .setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 建立一個子線程,在子線程中發送消息
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message msg = Message.obtain();
                        msg.what = MSG_SUB_TO_MAIN;
                        msg.obj = "這是一個來自子線程的消息";
                        // 2.發送消息
                        mHandler.sendMessage(msg);
                    }
                }).start();
            }
        });
    }
}
           

代碼如下:點選了發送按鈕,看log:

Handler基本原理先來說一下流程Handler的簡單使用

Looper.prepare()

public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        // 規定了一個線程隻有一個Looper,也就是一個線程隻能調用一次Looper.prepare()
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 如果目前線程沒有Looper,那麼就建立一個,存到sThreadLocal中
        sThreadLocal.set(new Looper(quitAllowed));
    }
           

從上面的代碼可以看出,一個線程最多隻有一個Looper對象。當沒有Looper對象時,去建立一個Looper,并存放到sThreadLocal中,sThreadLocal是一個static的ThreadLocal對象,關于它的詳細使用,以後有機會再介紹,這裡隻要知道,它存儲了Looper對象的副本,并且可以通過它取得目前線程在之前存儲的Looper的副本。如下圖:

Handler基本原理先來說一下流程Handler的簡單使用

接下來看Looper的構造方法:

private Looper(boolean quitAllowed) {
        // 建立了MessageQueue,并供Looper持有
        mQueue = new MessageQueue(quitAllowed);
        // 讓Looper持有目前線程對象
        mThread = Thread.currentThread();
    }
           

這裡主要就是建立了消息隊列MessageQueue,并讓它供Looper持有,因為一個線程最大隻有一個Looper對象,是以一個線程最多也隻有一個消息隊列。然後再把目前線程指派給mThread。

MessageQueue的構造方法沒有什麼可講的,它就是一個消息隊列,用于存放Message。

是以Looper.prepare()的作用主要有以下三點

  1. 建立Looper對象
  2. 建立MessageQueue對象,并讓Looper對象持有
  3. 讓Looper對象持有目前線程

new Handler()

Handler有很多構造方法,主要是提供自定義Callback、Looper等,我們先從最簡單的無參構造方法看起:

public Handler() {
        this(null, false);
    }

    public Handler(Callback callback, boolean async) {
      // 不相關代碼
       ......
        //得到目前線程的Looper,其實就是調用的sThreadLocal.get
        mLooper = Looper.myLooper();
        // 如果目前線程沒有Looper就報運作時異常
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        // 把得到的Looper的MessagQueue讓Handler持有
        mQueue = mLooper.mQueue;
        // 初始化Handler的Callback,其實就是最開始圖中的回調方法的2
        mCallback = callback;
        mAsynchronous = async;
    }
           

首先,調用了Looper.myLooper,其實就是調用sThreadLocal.get方法,會得到目前線程調用sThreadLocal.set儲存的Looper對象,讓Handler持有它。接下來就會判斷得到的Looper對象是否為空,如果為空,就會報

"Can't create handler inside thread that has not called Looper.prepare(),這不就是我們之前在沒有調用Looper.prepare就在子線程中建立Handler時報的錯誤嘛。的确,當我們沒有調用Looper.prepare(),則目前線程中是沒有Looper對象的。

然後,讓Handler持有得到的Looper對象的MessageQueue和設定處理回調的Callback對象(最開始圖中的回調方法2)。

到這裡,預設的Handler的建立過程就結束了,主要有以下幾點

  1. 建立Handler對象
  2. 得到目前線程的Looper對象,并判斷是否為空
  3. 讓建立的Handler對象持有Looper、MessageQueu、Callback的引用

(檢視更多(出處))