Android HandlerThread 使用指南

在 Android 開發中,需要把耗時操作放到子線程中,避免阻塞主線程,從而導致程序 ANR。實現這類異步任務的方式有:

  • Thread + Handler
  • AsyncTask
  • HandlerThread
  • IntentService

本文來講解分析下 HandlerThread,在真正開始前,我們先了解下 Handler 的使用方式。

Handler 機制

子線程中創建 Handler
public class MainActivity extends AppCompatActivity {

    private Handler handler;

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

        new Thread(new Runnable() {
            @Override
            public void run() {
                handler = new Handler();
            }
        }).start();
    }

}

這裏寫圖片描述
程序崩潰了,意思是說:在線程中沒有調用 Looper.prepare() 方法,是不能創建 Handler 對象的。

原因分析:

我點進 Handler 構造函數源碼看下,會發現在創建 Handler 對象時,系統會檢驗當前線程中是否存在 Looper 對象,如果沒有,則會拋出異常。

Handler 源碼:

/**
Handler.java
*/
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();   // 獲取當前線程的 Looper 對象
    if (mLooper == null) {
        throw new RuntimeException(  // 沒有Looper 對象,則需要調用 Looper.prepare()創建 Looper
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

那麼爲什麼在主線程中創建 Handler 對象,沒有調用 Looper.prepare() 方法,程序沒有崩潰呢?這是因爲主線程在創建時,系統幫我們創建好了 Looper 對象。看下程序入口 ActivityThread.main() 源碼:

/**
* ActivityThread.java
*/
public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();  // 獲取主線程中的Looper對象,該方法最終會調用Looper構造函數

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

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
結論:在線程中創建 Handler 對象,需要存在該線程的 Looper 對象。子線程需要我們手動創建 Looper 對象,即在該線程中調用 Looper.prepare()創建,並用 Looper.loop() 方法啓動輪詢;主線程中系統幫我們創建好了 Looper 對象。

常規用法:

new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();   // 創建 Looper
                workerHandler = new Handler();
                Looper.loop();      // 開啓輪詢 
            }
        }).start();

Handler 不同線程之間通信

(1)子線程向子線程發送消息

子線程和子線程發消息,就是在一個子線程中創建 Handler ,這樣回調 handleMessage()就自然會在子線程中,然後,在另一個子線程中使用該 handler 進行發送消息。

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "xinxing.tao";

    private Handler workerHandler;

    private WorkerThread workerThread;

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

        workerThread = new WorkerThread("WorkerThread");
        workerThread.start();

        // 在另一個子線程中發送消息給 WorkerThread 子線程
        new Thread(new Runnable() {
            @Override
            public void run() {
                // sleep(1000) 確保 WorkerThread 已經初始化好了 workerHandler
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                for (int i = 0; i < 6; i++) {
                    Log.d(TAG, "sendMessage :name = " + Thread.currentThread().getName() + ", i = " + i);
                    Message message = new Message();
                    message.what = i;
                    message.obj = "WorkerThread Message";
                    workerHandler.sendMessage(message);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 停止 WorkThread,因爲 WorkThread run()通過 Looper.loop() 死循環輪詢,所以需要拿到他的 Looper 進行停止。
                workerHandler.getLooper().quit();
            }
        }).start();
    }

    class WorkerThread extends Thread {

        public WorkerThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            // 子線程創建時,沒有創建 Looper 對象,必須手動調用 prepare() 方法創建 Looper
            Looper.prepare();
            workerHandler = new Handler() {
                // 子線程中創建 Handler, 回調自然在子線程中
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    Log.d(TAG, "handleMessage: name = " + Thread.currentThread().getName() + ", msg.what= " + msg.what + " ,msg.obj = " + msg.obj);
                }
            };
            Log.d(TAG, "run: end ??????");
            Looper.loop();  // 開啓Looper輪詢,直到 Looper 停止退出輪詢,才能執行後面的代碼
            Log.d(TAG, "run: end");
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}

這裏寫圖片描述

(2)主線程向子線程發消息(Thread方式)

在子線程中創建 Handler(需要創建Looper對象),然後使用該 Handler 在主線程中發送消息。

public class MainThread2WorkerThreadActivity extends AppCompatActivity {

    private static final String TAG = "debug";

    private Handler workerHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_thread2_worker_thread);
        // 子線程
        WorkerThread workerThread = new WorkerThread("WorkerThread");
        workerThread.start();
    }

    public void click(View view) {
        // send message
        Message message = new Message();
        message.what = 100;
        message.obj = "message from main to worker thread";
        workerHandler.sendMessage(message);
        Log.d(TAG, "sendMessage : " + Thread.currentThread().getName());
    }


    class WorkerThread extends Thread {

        public WorkerThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            super.run();
            // 子線程中使用 Handler,需要手動創建 Looper 對象
            Looper.prepare();
            workerHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    Log.d(TAG, "handleMessage: name = " + Thread.currentThread().getName() + ", msg.what = " + msg.what + ",msg.obj = " + msg.obj);
                }
            };
            // 開啓輪詢
            Looper.loop();
        }
    }
}

輸出結果:

D/debug: sendMessage : main
D/debug: handleMessage: name = WorkerThread, msg.what = 100,msg.obj = message from main to worker thread

可以看到在子線程中使用 Handler 寫法還是挺麻煩的,好在 Android 已經爲我們提供更方便的使用方式,即 HandlerThread。

HandlerThread 解析

HandlerThread 介紹

Handy class for starting a new thread that has a looper. The looper can then be used to create handler classes. Note that start() must still be called.

用於方便開啓子線程的類,內部包含 Looper , 可用來創建 Handler 類,注意,必須調用 start() 方法。

HandlerThread 常規用法

  1. 創建 HandlerThread 對象

    HandlerThread handlerThread  = new HandlerThread("handler-thread"); // 名字隨意取
  2. 啓動 HandlerThread

    handlerThread.start();
  3. 創建子線程 Handler,用於處理異步任務,並與 handlerThread 進行關聯。

    Handler workerHandler = new Handler(handlerThread.getLooper()){
       public void handleMessage() {
           // 執行耗時操作,運行於子線程中
       }
    }

Demo1 : 以下代碼使用 HandlerThread 方式完成主線程向子線程發送消息:

public class MainThread2WorkerThreadActivity extends AppCompatActivity {

    private static final String TAG = "debug";

    private HandlerThread handlerThread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_worker_thread2_main_thread);

        // 創建,開啓子線程 handlerThread
        handlerThread = new HandlerThread("HandlerThread");
        handlerThread.start();

        // 創建 workerHandler對象,傳入的參數是 handlerThread 中的 Looper,即在 handlerThread這個子線程中創建 handler
        Handler workerHandler = new Handler(handlerThread.getLooper()) {

            // 這個方法運行在 handlerThread 子線程中,可以執行耗時操作
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);

                Log.d(TAG, "handleMessage: name = " + Thread.currentThread().getName() + ", msg.what = " + msg.what); // handleMessage: name = HandlerThread, msg.what = 11
            }
        };

        // 主線程中使用 workerHandler 發送消息
        Log.d(TAG, "sendMessage: name = " + Thread.currentThread().getName()); // sendMessage: name = main
        Message message = new Message();
        message.what = 11;
        message.obj = "message";
        workerHandler.sendMessage(message);


    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 退出 handlerThread looper 循環
        handlerThread.quit();
    }
}

輸出結果:

D/debug: sendMessage: name = main
D/debug: handleMessage: name = HandlerThread, msg.what = 11

Demo2 : 自定義 HandlerThread , 繼承自 HandlerThread 類,構造 workerHandler 傳入的 looper 是 HandlerThread 中的 looper 對象,所以 workerHandler 中的 handleMessage() 回調運行於子線程中。
這裏寫圖片描述

// DownloadHandlerThread.java
public class DownloadHandlerThread extends HandlerThread implements Handler.Callback {

    public static final int WHAT_START_DOWNLOAD = 1;

    public static final int WHAT_FINISH_DOWNLOAD = 2;

    private List<String> urlList = new ArrayList<>();

    private Handler uiHandler;

    private Handler workerHandler;

    public DownloadHandlerThread() {
        super("DownloadHandlerThread");
    }


    /**
     * 構造 workerHandler 時,傳入的 Looper 是 HandlerThread 中的 looper,
     * 所以 workerHandler 中的 handleMessage() 是運行於子線程中。
     */
    @Override
    protected void onLooperPrepared() {
        super.onLooperPrepared();
        //
        workerHandler = new Handler(getLooper(), this);   // 使用HandlerThread子線程中的 looper
        // 將接收到的任務消息挨個添加到消息隊列中
        for (String url : urlList) {
            Message msg = workerHandler.obtainMessage();
            Bundle bundle = new Bundle();
            bundle.putString("url", url);
            msg.setData(bundle);
            workerHandler.sendMessage(msg);
        }
    }

    /**
     * 子線程中處理任務,完成後發送消息給主線程
     *
     * @param msg
     * @return
     */
    @Override
    public boolean handleMessage(Message msg) {
        if (msg == null || msg.getData() == null) {
            return false;
        }
        String url = msg.getData().getString("url");
        // 下載之前通知主線程顯示下載提示
        Message startMsg = uiHandler.obtainMessage();
        startMsg.what = WHAT_START_DOWNLOAD;
        startMsg.obj = url + " = start download ";
        // 發送消息給主線程
        uiHandler.sendMessage(startMsg);

        // 開始下載
        SystemClock.sleep(2000);   // 模擬下載過程

        // 每個 URL 下載完成通知主線程更新ui
        Message finishMsg = uiHandler.obtainMessage();
        finishMsg.what = WHAT_FINISH_DOWNLOAD;
        finishMsg.obj = url + " = finish download";
        uiHandler.sendMessage(finishMsg);

        return true;
    }


    public void setUrlList(List<String> list) {
        this.urlList = list;
    }


    /**
     * 注入主線程 handler
     *
     * @param handler
     */
    public void setUiHandler(Handler handler) {
        this.uiHandler = handler;
    }

    @Override
    public boolean quitSafely() {
        uiHandler = null;
        return super.quitSafely();
    }
}
// MainActivity.java
public class MainActivity extends AppCompatActivity implements Handler.Callback {

    private String[] urls = {"http://www.baidu.com", "http://www.sina.com", "http://google.com"};

    private DownloadHandlerThread handlerThread;

    private TextView startTextView;

    private TextView finishTextView;

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

        startTextView = findViewById(R.id.tv_tip);
        finishTextView = findViewById(R.id.tv_finish);


        Handler uiHandler = new Handler(this);
        handlerThread = new DownloadHandlerThread();
        handlerThread.setUrlList(Arrays.asList(urls));
        handlerThread.setUiHandler(uiHandler);

    }

    public void click(View view) {
        handlerThread.start();
    }

    /**
     * 主線程回調
     *
     * @param msg
     * @return
     */
    @Override
    public boolean handleMessage(Message msg) {
        switch (msg.what) {
            case DownloadHandlerThread.WHAT_START_DOWNLOAD:
                startTextView.setText(startTextView.getText().toString() + "\n" + msg.obj);
                break;
            case DownloadHandlerThread.WHAT_FINISH_DOWNLOAD:
                finishTextView.setText(finishTextView.getText().toString() + "\n" + msg.obj);
                break;
        }

        return true;
    }
}

HandlerThread 源碼分析

  /**
    * HandlerThread.java
    */
    public class HandlerThread extends Thread {
        int mPriority;
        int mTid = -1;
        Looper mLooper;
        private @Nullable Handler mHandler;

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

        /**
         * Constructs a HandlerThread.
         * @param name
         * @param priority The priority to run the thread at. The value supplied must be from 
         * {@link android.os.Process} and not from java.lang.Thread.
         */
        public HandlerThread(String name, int priority) {
            super(name);
            mPriority = priority;
        }

        /**
         * Call back method that can be explicitly overridden if needed to execute some
         * setup before Looper loops.
         */
        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;
        }

        /**
         * This method returns the Looper associated with this thread. If this thread not been started
         * or for any reason isAlive() returns false, this method will return null. If this thread
         * has been started, this method will block until the looper has been initialized.  
         * @return The looper.
         */
        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;
        }

        /**
         * @return a shared {@link Handler} associated with this thread
         * @hide
         */
        @NonNull
        public Handler getThreadHandler() {
            if (mHandler == null) {
                mHandler = new Handler(getLooper());
            }
            return mHandler;
        }

        /**
         * Quits the handler thread's looper.
         * <p>
         * Causes the handler thread's looper to terminate without processing any
         * more messages in the message queue.
         * </p><p>
         * Any attempt to post messages to the queue after the looper is asked to quit will fail.
         * For example, the {@link Handler#sendMessage(Message)} method will return false.
         * </p><p class="note">
         * Using this method may be unsafe because some messages may not be delivered
         * before the looper terminates.  Consider using {@link #quitSafely} instead to ensure
         * that all pending work is completed in an orderly manner.
         * </p>
         *
         * @return True if the looper looper has been asked to quit or false if the
         * thread had not yet started running.
         *
         * @see #quitSafely
         */
        public boolean quit() {
            Looper looper = getLooper();
            if (looper != null) {
                looper.quit();
                return true;
            }
            return false;
        }

        /**
         * Quits the handler thread's looper safely.
         * <p>
         * Causes the handler thread's looper to terminate as soon as all remaining messages
         * in the message queue that are already due to be delivered have been handled.
         * Pending delayed messages with due times in the future will not be delivered.
         * </p><p>
         * Any attempt to post messages to the queue after the looper is asked to quit will fail.
         * For example, the {@link Handler#sendMessage(Message)} method will return false.
         * </p><p>
         * If the thread has not been started or has finished (that is if
         * {@link #getLooper} returns null), then false is returned.
         * Otherwise the looper is asked to quit and true is returned.
         * </p>
         *
         * @return True if the looper looper has been asked to quit or false if the
         * thread had not yet started running.
         */
        public boolean quitSafely() {
            Looper looper = getLooper();
            if (looper != null) {
                looper.quitSafely();
                return true;
            }
            return false;
        }

        /**
         * Returns the identifier of this thread. See Process.myTid().
         */
        public int getThreadId() {
            return mTid;
        }
    }

可以看到,HandlerThread 繼承自 Thread ,所以本質上 HandlerThread 還是 Thread,其 run() 方法執行於子線程中。當我們使用 HandlerThread 時,通過調用它的 start() 開啓異步任務時,即會調用 HandlerThread 的 run() 方法,在 run() 方法,調用 Looper.prepare() 創建該線程的 Looper 對象,然後調用了 Looper.loop() 開啓消息的輪詢,loop() 方法是一個無限循環,不斷的從消息隊列 MessageQueue 中拿到消息進行處理。由於消息隊列是由鏈表結構構成的,所以異步任務時串行執行的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章