Android版本:8.1
- 我們知道MainActivity裏面是不能做耗時操作的,幾秒鐘就會導致ANR,Application Not Responding應用無響應報錯。
- 於是我們會把耗時操作代碼放在異步任務裏AsyncTask,比如用異步任務從網上下載一個圖片。
- 下載完了我們就需要顯示圖片到Imageview上,而如果我們直接用imageview去更新圖片,就會報錯,提示你非主線程無法直接操作UI
CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
- 這時我們通常就會調用Handler對象,去發送一個Message,在Handler的handleMessage方法裏,就能直接去更新Imageview了。
當初我在學習的時候就一下子就冒出了一些問題,
- 爲什麼UI線程會ANR?
- 爲什麼子線程不能訪問主線程的UI?
- AsyncTask是怎麼使用的?
- Handler是怎麼使用的?
。。。
到了現在,當初的答案也都知道了,這裏就寫寫關於Handler我知道的這一部分。
AsyncTask異步任務在使用的時候創建了一個新的Thread子線程,而主線程的view在初始化的時候都含有一個主線程對象,當然是ViewrootImpl那裏,直接操作view的時候,都會先去檢查是否是創建的時候的同一個線程,很明顯,子線程不是那個創建的時候的線程,於是就會拋異常。在使用Handler的時候,發送Message,實現了跨線程通信,Message傳到了主線程,再操作view更新,就不會異常了。
因爲耗時,選擇異步任務,因爲子線程不能操作主線程UI,選擇了Handler。
Hander在使用的時候
直接Handler handler = new Handler()就可以了,或者通常還會寫一個匿名內部類方式。
private final Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}
然後看看Handler源碼,才知道handleMessage是handler的內部接口的方法
public interface Callback {
public boolean handleMessage(Message msg);
}
在Handler的裏面有幾個成員變量
final Looper mLooper;
final MessageQueue mQueue;
final Callback mCallback;
IMessenger mMessenger;
Handler把利用Messenger把Message發送到MessageQueue,然後Looper不斷從隊列裏取出Message,這就是Handler處理信息的流程。
Handler的構造
public Handler() {
this(null, false);
}
public Handler(Callback callback) {
this(callback, false);
}
public Handler(Looper looper) {
this(looper, null, false);
}
public Handler(Looper looper, Callback callback) {
this(looper, callback, false);
}
public Handler(boolean async) {
this(null, async);
}
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
public Handler(Callback callback, boolean async) {
...
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
在構造的時候如果傳Looper對象,需要在Handler初始化之前調用Looper.prepare(),不然會異常。
其餘的一般會使用無參或者帶回調這兩種方法。
於是可以理解前面的的匿名內部類寫法了。
private final Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}
---
private final Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
return false;
}
});
Handler的Message構造方式
public final Message obtainMessage()
{
return Message.obtain(this);
}
public final Message obtainMessage(int what)
{
return Message.obtain(this, what);
}
public final Message obtainMessage(int what, Object obj)
{
return Message.obtain(this, what, obj);
}
public final Message obtainMessage(int what, int arg1, int arg2)
{
return Message.obtain(this, what, arg1, arg2);
}
public final Message obtainMessage(int what, int arg1, int arg2, Object obj)
{
return Message.obtain(this, what, arg1, arg2, obj);
}
看看Message.obtain
public static Message obtain(Handler h, int what,
int arg1, int arg2, Object obj) {
Message m = obtain();
m.target = h;
m.what = what;
m.arg1 = arg1;
m.arg2 = arg2;
m.obj = obj;
return m;
}
---
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
這就是別人常說的消息池,先從消息池裏獲取已經存在的對象,如果沒有才創建,降低內存泄漏,重複利用。
所以我們平時在sendmessage的時候,應該不要直接new Message, 而是用Message.obtain 或者Handler.obtainMessage
Handler的sendMessage
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendEmptyMessage(int what)
{
return sendEmptyMessageDelayed(what, 0);
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
發送message可以用的方法還有
sendEmptyMessageAtTime(int what, long uptimeMillis)
sendMessageDelayed(Message msg, long delayMillis)
sendMessageAtTime(Message msg, long uptimeMillis)
sendMessageAtFrontOfQueue(Message msg)
最後我們看看它到底怎麼發送Message的
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
這裏的mQueue就是在構造Handler的時候的Looper的MessageQueue,
boolean enqueueMessage(Message msg, long when) {
//代碼省略
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
//代碼省略
}
當message被髮送給MessageQueue的時候,會有個無限循環查找當前隊列的message,如果有就把msg傳給prev,然後Looper裏面,還在循環cha
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
try {
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
msg.recycleUnchecked();
}
}
Looper無限從MessageQueue 裏面next()獲取下一個msg.
然後調用msg.target.dispatchMessage(msg);
msg的target就是Handler,
public static Message obtain(Handler h, int what,
int arg1, int arg2, Object obj) {
Message m = obtain();
m.target = h; <---
m.what = what;
m.arg1 = arg1;
m.arg2 = arg2;
m.obj = obj;
return m;
}
於是回去看Handler的dispatchMessage
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
先檢查msg是否含有回調,如果有就處理回調內容
如果沒有則會檢測當前Handler是否有回調對象,如果有就處理回調內容
如果沒有回調,則直接處理msg
至此msg從handler發送出去,又回到handler來處理,好像沒什麼特別的。
其實這裏忽略了一個點,msg是從子線程發送的,發出去之後它走到了主線程裏,因爲Looper是主線程裏的Looper
在framework裏,ActivityThread的Main方法裏,Looper.prepareMainLooper();就是在Activity裏構造了Looper對象,所以這裏Looper是主線程的,那msg是什麼時候從子線程傳到主線程裏的呢?
答案在MessageQueue的enqueueMessage裏,做了一次傳遞
boolean enqueueMessage(Message msg, long when) {
//代碼省略
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages; //就是這裏,從子線程達到主線程
boolean needWake;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
//代碼省略
}
所以,Message對象是跨過了線程,達到了主線程,所以提示信息可以更新,只需要根據msg的信息來更新不同的view就可以了。
然後關於Message對象,可以看到
public final class Message implements Parcelable {
Message實現了序列化接口Parcelable,這個標誌表示這個對象可以實現跨進程傳輸,在不同進程傳輸的時候其實不是直接把對象給賦值過去,而是copy過去,把對象的所以屬性都複製給一個新的另一個進程的對象,這就是實現序列化的意義。既符合不同進程對象不可直接傳輸,又實現了賦值對象間接的實現傳輸。
如此一來,發現其實Handler也很簡單。