1. 程式人生 > >Android 併發二三事之 Handler 機制的妙用 HandlerThread

Android 併發二三事之 Handler 機制的妙用 HandlerThread

Android 併發第五篇

本篇會講解如何利用 HandlerThread 實現非同步操作。

HandlerThread 本身其實就是一個 Thread ,但是其內部還利用 Handler 機制。
對於提交的任務(或者說是資訊 Message)依次處理。
所以在介紹 HandlerThread 原理以及如果使用之前,會首先說一個 Handler 非同步機制。

當然 Handler, Looper, Message 之間的關係相信很多人都已經很熟悉了,這裡會只著重介紹和本節相關的內容。

一 、Handler 機制:

1、 我們都知道,在子執行緒中通知主執行緒更新UI介面,需要使用Handler。


一般我們就直接在 Activity 中直接 初始化一個Handler 物件,像這樣:

        Handler uiHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };

重寫 handlerMessage() 方法,然後利用 uiHandler 物件在子執行緒中傳送訊息。

2、 或者我們也可以直接在主執行緒直接 new 一個 Handler 物件:

Handler handler = new Handler();

但在子執行緒中 new Handler()需要這樣:

Handler handler = new Handler(Context.getMainLooper());

然後在子執行緒中:

        handler.post(new Runnable() {
            @Override
            public void run() {
        //執行在主執行緒中
                Log.d(TAG, "run on UI  Thread Id : "+Thread.currentThread().getId());
            }
        });

Handler 原始碼:

    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

在handler 中會將Runnable 物件賦值給 message.callback 屬性,封裝成Message,呼叫 sendMessageDelaye() 將訊息傳送出去。
sendMessageDelaye() 方法最後在輾轉幾次後最終會呼叫sendMessageAtTime() 將訊息放到訊息佇列中。

    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);
    }

而 Handler.sendMessage()的原始碼為:

    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

所以本質上,無論是利用Handler.sendMessage(),還是 Handler.post() 都是將訊息新增到訊息佇列之中。

那麼為什麼在子執行緒中需要傳入 MainLooper , 而主執行緒卻不需要呢?

首先我們是要在子執行緒中通知主執行緒,那麼我們便需要程式碼執行在UI 執行緒中。
如果在子執行緒中直接:

Handler handler = new Handler();

會丟擲異常:
Can’t create handler inside thread that has not called Looper.prepare()
我們可以看一下原始碼:

Handler 原始碼:

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

    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());
            }
        }

        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;
    }

我們能夠看到,無參的構造方法,會呼叫public Handler(Callback callback, boolean async) 。
在這個方法中,呼叫 Looper.myLooper(); 獲取 Looper 物件,之所以丟擲異常,一定是其為null了。
那麼為什麼沒有獲取到Looper物件呢?
接下來看Looper原始碼:

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

從 ThreadLocal 變數中獲取當前執行緒的值,那麼這個值是在哪裡設定的呢?

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

    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));
    }

在Looper.prepare() 中設定的。
也就是說在子執行緒中直接 new Handler() 物件,需要先呼叫Looper.prepare() 方法。
而在主執行緒中是不需要的,因為在應用初始化時,已經呼叫 Looper.prepare() 了。
而Looper 中還有一個方法:Looper.loop() 方法

Looper.loop() 內包含一個死迴圈,不斷的從佇列中獲取訊息,如果沒有訊息時,會阻塞。
Looper.loop() 呼叫了 Handler.dispatchMessage() 方法:

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

    private static void handleCallback(Message message) {
        message.callback.run();
    }

在dispatchMessage() 方法中會呼叫 handleMessage() 方法,或者呼叫 handleCallback() 方法處理我們利用Handler post Runnable
所封裝的訊息。

3 、所以通過以上總結
我們知道如果Looper.loop()是在主執行緒中呼叫的,那麼我們重寫的 handlerMessage() 方法
和封裝在訊息中的 Runnable 都會在主執行緒中執行。
反過來說,如果Looper.prepare() 以及 Looper.loop() 是在子執行緒中呼叫的,那麼基於子執行緒的Looper,所建立的Handler
所傳送的訊息都將會執行在子執行緒中,HandlerThread 便是利用了這個原理。

二 、HandlerThread

1 、我們首先看一下 HandlerThread 如何使用:

    private void requestWithHandlerThread() {
    //初始化一個 HandlerThread 物件
        HandlerThread handlerThread = new HandlerThread("HandlerThread");
    //呼叫start() 方法
        handlerThread.start();
    Log.d(TAG, "Main : "+Thread.currentThread().getId());
        Log.d(TAG, "HandlerThread : "+handlerThread.getId());
    //初始化一個Handler 物件,利用 HandlerThread 中的 Looper 物件
        Handler handler = new Handler(handlerThread.getLooper()){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                //執行在子執行緒中
                Log.d(TAG,"Thread : "+Thread.currentThread().getId() +"   "+msg.obj);


            }
        };
        Message message = Message.obtain();
        message.obj = "From Message";
        handler.sendMessage(message);
        handler.post(new Runnable() {
            @Override
            public void run() {
        //執行在子執行緒中
                Log.d(TAG, "post : "+Thread.currentThread().getId());
            }
        });
    }

2 、結果:

11-15 17:20:15.634 12297-12297/com.loader.demo D/Demo: Main : 1
11-15 17:20:15.634 12297-12297/com.loader.demo D/Demo: HandlerThread : 26599
11-15 17:20:15.640 12297-12416/com.loader.demo D/Demo: Thread : 26599   From Message
11-15 17:20:15.640 12297-12416/com.loader.demo D/Demo: post : 26599

在這裡 HandlerThread 需要和 Handler 一起配合使用,HandlerThread 提供一個在子執行緒中建立的 Looper 。
按照之前的推論,Looper.prepare(), 以及 Looper.loop() 都是執行在子執行緒中,那麼在處理訊息時也必然執行在子執行緒中。
所以其實現了非同步的效果。

3 、接下來看一下 HandlerThread 的原始碼:

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }

    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }


    protected void onLooperPrepared() {
    }

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }

        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

    public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
    }

    public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }

    public int getThreadId() {
        return mTid;
    }
}

4 、總結

可以看到 HandlerThread 本身是一個 Thread 所以其 run() 方法會執行在子執行緒中。在 run() 方法中首先呼叫了Looper.prepare(),
用於初始化訊息佇列以及Looper物件,緊接著呼叫 Looper.loop() 開始從訊息佇列中輪詢,一旦有訊息便將訊息取出處理。
因為整個過程都執行在子執行緒中,所以當我們用在子執行緒中建立的Looper作為引數傳給Handler時,其處理訊息的程式碼就會執行在子執行緒中了。

以上便是 HandlerThread 的原理,主要還是利用 Handler,Message, Looper 之間的關係。

三 、自定義 HandlerThread:

當我們瞭解了其原理之後,其實我們也可以自定義自己 HandlerThread , 線上程之中處理訊息。
現在我們自定義一個MyHandlerThread 同樣繼承 Thread。

1 、程式碼如下:

public class MyHandlerThread extends Thread {

    private Handler asyncHandler;
    private Looper mLooper;
    public MyHandlerThread() {

    }

    @Override
    public void run() {

        Looper.prepare();
        mLooper = Looper.myLooper();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        asyncHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                handlerThreadMessage(msg);
            }
        };
        Looper.loop();

    }

    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }

        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

    //退出
    public void quit() {
        Looper looper = getLooper();
        if(looper != null) {
            looper.quit();
        }
    }



    /**
     * 傳送訊息
     * @param message
     */
    public void sendMessage(Message message) {
        if(asyncHandler != null) {
            Log.d("test","sendMessage");
            asyncHandler.sendMessage(message);
        }
    }

    /**
     * 處理訊息
     * @param message
     */
    private void handlerThreadMessage(Message message) {
        Log.d("test","Message : "+message.obj+" Thread " +Thread.currentThread().getId());
    }
}

2、用法也很簡單:

定義變數:

private MyHandlerThread handlerThread;

在onCreate() 中初始化:

handlerThread = new MyHandlerThread();
handlerThread.start();

在需要非同步時呼叫:

    private void updateData() {
        Message message = Message.obtain();
        message.obj = "更新資料";
        handlerThread.sendMessage(message);
    }

這樣我們便實現自定義 HandlerThread ,其中我們還可以根據需求封裝不同傳送訊息的方法。
並且我們還將提交任務的程式碼和在子執行緒中處理任務的程式碼分開了,兩塊程式碼利用 MessageQueue 相連線,
那麼這是不是也算是一種生產者消費者模式呢? 因為Handler 機制本身也算是一種生產者消費者模式啊。

四、

下一篇會講解 Android 中另外一個可以實現非同步的類: IntentService 。
IntentService 本身當然是一個 Service , 但是它可以做到完成任務後自動退出,下一篇一起看看其是怎麼做到的。