android訊息機制—Handler

韓明澤發表於2019-02-28

在android日常開發中我們經常會有從網上獲取資料更新UI的需求,但是Goole出於安全考慮規定,只有android主執行緒才能更新UI,涉及到耗時操作的要放到子執行緒中處理。不過Google也為我們設計了Handler用於將子執行緒中的資料更新到UI執行緒。

專案原始碼

Handler的基本使用

在android中Handler主要用來接受和傳送訊息,他的基本使用如下:

//接受訊息
@SuppressLint("HandlerLeak")
private Handler mHanlder = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case 200:
               mTV_testHandler.setText((String) msg.obj);
            break;
        }
        super.handleMessage(msg);
    }
};

//線上程中傳送訊息
new Thread(new Runnable() {
    @Override
    public void run() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Message msg = new Message();
                msg.what = 200;
                msg.obj = "這是Send傳送的訊息";
                mHanlder.sendMessage(msg);
            }
        }).start();
    }
}).start();
複製程式碼

子執行緒中能建立Handler嗎

因為handler在建立的時候必須繫結Looper,否則回報not called Looper.prepare()異常,因此線上程中不能直接使用Handler空參構造方法;可以通過下面兩種方式解決

第一種方式

new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();
        Handler handler2 = new Handler();
        //一定要開啟迴圈
        Looper.loop();
    }
});
複製程式碼

第二種方式

//傳入一個Looper
Handler handler2=new Handler(looper);
複製程式碼

Handler如何與Looper關聯

Handler關聯Looper有兩種方式,一種是在構造中傳入Looper,另外是線上程中呼叫Looper.perpare()。

//構造1
public Handler() {
    this(null, false);
}
//構造2
public Handler(Callback callback) {
    this(callback, false);
}
//構造3
public Handler(Looper looper) {
    this(looper, null, false);
}
 //構造4
public Handler(Looper looper, Callback callback) {
    this(looper, callback, false);
}
//構造5
public Handler(boolean async) {
    this(null, async);
}
//構造6
public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }
    //關聯Looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can`t create handler inside thread that has not called Looper.prepare()");
    }
    //獲取Looper中建立的MessageQueue
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
 //構造7
public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    //獲取Looper中建立的MessageQueue
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
複製程式碼

Handler總共有六種構造,其中構造1、構造2、構造5他們的本質都是呼叫了構造6在該構造中呼叫了Looper.myLooper()來關聯Looper,另外從上面的程式碼中也可以知道構造3、構造4本質上呼叫了是構造7並且他們指定了關聯的Looper。

Handler如何傳送訊息

Handler傳送訊息本質上有兩種方式:

第一種handler.sendXXX()

通過mHandler.sendXXX()這種方式最後都會呼叫sendMessageDelayed(),下面我們來分析一下它的程式碼:

public final boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    //SystemClock.uptimeMillis()獲取的是從開機到現在的毫秒數
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
複製程式碼

sendMessageDelayed的程式碼也很簡單,就是將訊息和計算的從開機到現在的時間加上我們設定的延遲傳送時間(如果沒有設定延遲時間預設為0)作為引數呼叫
sendMessageAtTime()方法,最後這個方法會將從Looper中獲取的MessageQueue、訊息和延遲時間傳入enqueueMessage()方法中:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    //將Hanlder賦值給Message的target
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    //向MessageQueue插入訊息
    return queue.enqueueMessage(msg, uptimeMillis);
}
複製程式碼

第二種mHandler.postXXX()

通過mHandler.postXXX()這種方式其本質和sendXXX()一樣也是最後呼叫了sendMessageAtTime()方法傳送訊息,他們兩個的區別是postXXX()方式要傳入一個Runnable,這個Runnable會被包裝成Message的callback。使用這種方式發訊息比較方便一點。

//post傳送訊息
public final boolean post(Runnable r){
   return  sendMessageDelayed(getPostMessage(r), 0);
}
 //獲取Post傳送方式的資訊
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    //將post傳入的Runnable賦值給Message的callback變數
    m.callback = r;
    return m;
}
複製程式碼

handler如何接收訊息

handler的訊息是Looper的loop()方法中呼叫 msg.target.dispatchMessage(msg);方法傳過來的,下面我們看看handler的dispatchMessage()方法。

public void dispatchMessage(Message msg) {
    //如果使用了postXXX方式傳送訊息,那麼msg.callback就不會為空
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
複製程式碼

當程式執行dispatchMessage()方法時會做如下操作,首先判斷Message的callback是否為空,如果不為空就會進入該方法呼叫handleCallback(msg)如果為空就會做第二個判斷,即判斷Handler的mCallback是否為空,如果不為空就會執行Callback的handleMessage()方法,如果這個函式返回true則跳出dispatchMessage(),否則就會執行Handler的handleMessage(msg)方法。如果上面的判斷都不成立,那麼系統將會呼叫Handler的handleMessage(msg)方法,該方法為一個空的函式,需要開發者根據需求重寫該函式,。看到這裡可能會感覺有些亂,沒關係,下面我用三個例子來說明。

1. msg.callback != null

我們在上面postXXX()傳送訊息時候就提到過,postXXX()方法中的Runnable會被包裝成Message的callback,因此該訊息在Handler的dispatchMessage()方法中msg.callback != null判斷成立,並且會呼叫handleCallback(msg)方法,該方法裡面就一行程式碼message.callback.run();即執行postXXX()的Runnable物件的run()方法。

mHanlder.post(new Runnable() {
    @Override
    public void run() {
        mTV_testHandler.setText("這是Post傳送的訊息");
    }
});
複製程式碼
2. mCallback != null

該方法是在建立Hanlder的時候在建構函式中傳入Callback物件,Handler構造中會將這個Callback物件賦值給Handler的mCallback,因此mCallback != null這個判斷成立。

Handler callbackHandler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        switch (msg.what) {
            case 202:
                //顯示訊息
                mTV_testHandler.setText((String) msg.obj);
                break;
        }
        return false;
    }
});
//傳送訊息
new Thread(new Runnable() {
    @Override
    public void run() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Message msg = new Message();
                msg.what = 202;
                msg.obj = "這是Callback傳送的訊息";
                callbackHandler.sendMessage(msg);
            }
        }).start();
    }
}).start();
複製程式碼
3. 執行Hanlder的handleMessage(msg)

我們在建立Hanlder物件的時候,沒有傳入Callback物件,在傳送訊息的時候也沒有使用postXXX()的方式傳送,因此上面兩中情況的判斷都不會成立,dispatchMessage()最後回到用Hanlder的空函式handleMessage()執行裡面的程式碼。

@SuppressLint("HandlerLeak")
private Handler mHanlder = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case 200:
                //顯示訊息
                mTV_testHandler.setText((String) msg.obj);
                break;
        }
        super.handleMessage(msg);
    }
};

//傳送訊息
new Thread(new Runnable() {
    @Override
    public void run() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Message msg = new Message();
                msg.what = 200;
                msg.obj = "這是Send傳送的訊息";
                mHanlder.sendMessage(msg);
            }
        }).start();
    }
}).start();
複製程式碼

參考:

你真的懂Handler嗎?Handler問答

相關文章