Handler消息傳遞機制分析

Handler的用途和用法

寫過Android程序的人大概都會遇到ANR(Application Not Responding)。若是程序在一段時間內沒有響應,系統就會彈出一個對話框,讓用戶選擇繼續等待仍是強制關閉應用。爲了不ANR,咱們須要把耗時的邏輯放到後臺線程裏執行。可是後臺線程沒法更新界面。那麼當任務完成後,如何根據結果更新界面呢?Handler就能夠承擔這個職責。下面的例子展現了Handler的用法:java

package com.tq.handlerdemo;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private static final int UPDATE_TEXT = 1;
    private TextView textView;

    Thread backgroundThread = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(5000);
                Message message = handler.obtainMessage(UPDATE_TEXT);
                message.obj = "Hello";
                handler.sendMessage(message);
            } catch (InterruptedException e) {
                // ignore
            }
        }
    });

    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message message) {
            if (message.what == UPDATE_TEXT) {
                String text = (String) message.obj;
                textView.setText(text);
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        backgroundThread.start();
    }
}

爲何handleMessage()能夠更新界面呢?由於handlerMessage()是在主線程中調用的。主線程中存在一個無限循環,不斷的將消息分派給Handler處理。執行這個無限循環的對象就是Looper。android

Looper和MessageQueue

Looper是處理消息的主循環,Activity的消息所有由Looper進行分派。下面的代碼是從ActivityThread.main()截取的,從這裏能夠看到,Android應用的主循環就是Looper.loop()。app

public static void main(String[] args) {

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
}

下面的代碼是從Looper.java中截取的,部分函數有刪減。能夠看到每一個線程有一個Looper對象,它的方法looper()就是線程中處理消息的主循環。looper()不斷從MessageQueue中獲取消息,交給Message.target.dispatchMessage()。Message.target是一個Handler,dispatchMessage()方法裏會調用handleMessage()。ide

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

private static void prepare(boolean quitAllowed) {
        sThreadLocal.set(new Looper(quitAllowed));
}

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
}

public static void loop() {
        final Looper me = myLooper();
        final MessageQueue queue = me.mQueue;

        for (;;) {
                Message msg = queue.next(); // might block
                if (msg == null) {
                        return;
                }

                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
                msg.recycleUnchecked();
        }
}

Looper經過MessageQueue.next()方法獲得消息,這是一個阻塞方法,大體的流程以下:函數

Message next() {
        int nextPollTimeoutMillis = 0;
        for (;;) {
                nativePollOnce(ptr, nextPollTimeoutMillis);

                long now = SystemClock.uptimeMillis();
                Message msg = get_next_message();

                if (now < msg.when) {
                        nextPollTimeoutMillis = msg.when - now;
                } else {
                        return msg;
                }

                nextPollTimeoutMillis = 0;
        }
}

nativePollOnce()是一個本地方法,實際做用大體至關於sleep()。和sleep()不一樣的是,若是在nextPollTimeMills以前收到了新消息,nativePollOnce()會當即返回。這是經過內部調用的epoll系統調用實現的。oop

有的讀者可能會奇怪,消息是經過Handler.sendMessage()發送的,Message對象如何傳遞到Looper.mQueue中的呢?在Handler的構造函數中,若是沒有傳入Looper對象,Handler會將當前線程的Looper對象和Looper的mQueue成員保存起來。在Handler.sendMessage()方法中,消息會傳遞給MessageQueue。ui

void sendMessage(Message message) {
        enqueueMessage(queue, message);
}

void enqueueMessage(MessageQueue queue, Message message) {
        queue.enqueueMessage(message);
}

Handler內存泄漏

前面提到Message.target是一個Handler對象。若是這個Handler定義爲Activity的內部類(本文的第一個例子就是這樣),當Activity退出時,若是Looper的消息隊列中還有Message對象,那麼Message.target會持有Activity的引用(經過內部類),致使Activity沒法回收,這就是所謂的Handler內存泄漏問題。要解決這個問題須要作到兩點,一是將Handler定義爲靜態內部類或非內部類。二是在退出Activity時清空消息隊列。下面的例子展現了這兩點。this

package com.tq.handlerdemo;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;

import java.lang.ref.WeakReference;

public class MainActivity extends AppCompatActivity {

    private static final int UPDATE_TEXT = 1;
    private TextView textView;

    Thread backgroundThread = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(5000);
                Message message = handler.obtainMessage(UPDATE_TEXT);
                message.obj = "Hello";
            } catch (InterruptedException e) {
                // ignore
            }
        }
    });

    private static class MyHandler extends Handler {
        private WeakReference<MainActivity> activity;

        public MyHandler(MainActivity aActivity) {
            activity = new WeakReference<>(aActivity);
        }

        @Override
        public void handleMessage(Message message) {
            if (message.what == UPDATE_TEXT) {
                String text = (String) message.obj;
                MainActivity mainActivity = activity.get();
                if (mainActivity != null) {
                    mainActivity.textView.setText(text);
                }
            }
        }
    };

    private MyHandler handler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        handler = new MyHandler(this);
        backgroundThread.start();
    }

    @Override
    protected void onDestroy() {
        handler.removeCallbacksAndMessages(null);
    }
}
相關文章
相關標籤/搜索