Android HandlerThread 完全解析

jopen 8年前發布 | 11K 次閱讀 Android開發 移動開發

轉載請標明出處:
http://blog.csdn.net/lmj623565791/article/details/47079737
本文出自:【張鴻洋的博客】

1、概述

話說最近股市變動不變,也成了熱火朝天的話題。不知道大家有沒有考慮做個實時更新股市數據的app呢?假設我們要做一個股市數據實時更新的app,我們可以在網上找個第三方的股市數據接口,然后在我們的app中每隔1分鐘(合適的時間)去更新數據,然后更新我們的UI即可。

當然了,本文不是要教大家做這樣一個app,只是舉個場景。言歸正傳,回到我們的HandlerThread,大家一定聽說過Looper、Handler、Message三者的關系(如果不了解,可以查看Android 異步消息處理機制 讓你深入理解 Looper、Handler、Message三者關系),在我們的UI線程默默的為我們服務。其實我們可以借鑒UI線程Looper的思想,搞個子線程,也通過Handler、Message通信,可以適用于很多場景。

對了,我之前寫過一篇博文Android Handler 異步消息處理機制的妙用 創建強大的圖片加載類,這篇博文中就在子線程中創建了Looper,Handler,原理和HandlerThread一模一樣,可惜當時我并不知道這個類,不過大家也可以當做HandlerThread應用場景進行學習。

2、HandlerThread實例

下面看我們模擬大盤走勢的代碼,其實非常簡單,就一個Activity

package com.zhy.blogcodes.intentservice;

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

import com.zhy.blogcodes.R;


public class HandlerThreadActivity extends AppCompatActivity {

    private TextView mTvServiceInfo;

    private HandlerThread mCheckMsgThread;
    private Handler mCheckMsgHandler;
    private boolean isUpdateInfo;

    private static final int MSG_UPDATE_INFO = 0x110;

    //與UI線程管理的handler
    private Handler mHandler = new Handler();


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

        //創建后臺線程
        initBackThread();

        mTvServiceInfo = (TextView) findViewById(R.id.id_textview);

    }

    @Override
    protected void onResume()
    {
        super.onResume();
        //開始查詢
        isUpdateInfo = true;
        mCheckMsgHandler.sendEmptyMessage(MSG_UPDATE_INFO);
    }

    @Override
    protected void onPause()
    {
        super.onPause();
        //停止查詢
        isUpdateInfo = false;
        mCheckMsgHandler.removeMessages(MSG_UPDATE_INFO);

    }

    private void initBackThread()
    {
        mCheckMsgThread = new HandlerThread("check-message-coming");
        mCheckMsgThread.start();
        mCheckMsgHandler = new Handler(mCheckMsgThread.getLooper())
        {
            @Override
            public void handleMessage(Message msg)
            {
                checkForUpdate();
                if (isUpdateInfo)
                {
                    mCheckMsgHandler.sendEmptyMessageDelayed(MSG_UPDATE_INFO, 1000);
                }
            }
        };


    }

    /** * 模擬從服務器解析數據 */
    private void checkForUpdate()
    {
        try
        {
            //模擬耗時
            Thread.sleep(1000);
            mHandler.post(new Runnable()
            {
                @Override
                public void run()
                {
                    String result = "實時更新中,當前大盤指數:<font color='red'>%d</font>";
                    result = String.format(result, (int) (Math.random() * 3000 + 1000));
                    mTvServiceInfo.setText(Html.fromHtml(result));
                }
            });

        } catch (InterruptedException e)
        {
            e.printStackTrace();
        }

    }

    @Override
    protected void onDestroy()
    {
        super.onDestroy();
        //釋放資源
        mCheckMsgThread.quit();
    }


}

可以看到我們在onCreate中,去創建和啟動了HandlerThread,并且關聯了一個mCheckMsgHandler。然后我們分別在onResume和onPause中去開啟和暫停我們的查詢,最后在onDestory中去釋放資源。

這樣就實現了我們每隔5s去服務端查詢最新的數據,然后更新我們的UI,當然我們這里通過Thread.sleep()模擬耗時,返回了一個隨機數,大家可以很輕易的換成真正的數據接口。

布局文庫

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin">

    <TextView  android:id="@+id/id_textview" android:text="正在加載大盤指數..." android:layout_width="wrap_content" android:layout_height="wrap_content"/>

</RelativeLayout>

運行效果圖

別問我為什么要用紅色!!!

ok,當然了,我們的效果很單調,但是你完全可以去擴展,比如ListView顯示用戶關注的股票數據。或者是其他的需要一直檢測更新的數據。

看到這,你是否好奇呢,HandlerThread的內部是如何做的呢?其實非常的簡單,如果你看過Android 異步消息處理機制 讓你深入理解 Looper、Handler、Message三者關系估計掃幾眼就會了。

HandlerThread 源碼分析

對于所有Looper,Handler相關細節統一參考上面提到的文章。

我們輕輕的掀開HandlerThread的源碼,還記得我們是通過

 mCheckMsgThread = new HandlerThread("check-message-coming");
 mCheckMsgThread.start(); 

創建和啟動的對象,那么隨便掃一眼:

package android.os;


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

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

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

看到了什么,其實我們就是初始化和啟動了一個線程;然后我們看run()方法,可以看到該方法中調用了Looper.prepare(),Loop.loop();

prepare()呢,中創建了一個Looper對象,并且把該對象放到了該線程范圍內的變量中(sThreadLocal),在Looper對象的構造過程中,初始化了一個MessageQueue,作為該Looper對象成員變量。

loop()就開啟了,不斷的循環從MessageQueue中取消息處理了,當沒有消息的時候會阻塞,有消息的到來的時候會喚醒。如果你不明白我說的,參考上面推薦的文章。


接下來,我們創建了一個mCheckMsgHandler,是這么創建的:

mCheckMsgHandler = new Handler(mCheckMsgThread.getLooper())

對應源碼

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

mCheckMsgThread.getLooper()返回的就是我們在run方法中創建的mLooper。

那么Handler的構造呢,其實就是在Handler中持有一個指向該Looper.mQueue對象,當handler調用sendMessage方法時,其實就是往該mQueue中去插入一個message,然后Looper.loop()就會取出執行。

好了,到這我們就分析完了,其實就幾行代碼;不過有一點我想提一下:

如果你夠細心你會發現,run方法里面當mLooper創建完成后有個notifyAll(),getLooper()中有個wait(),這是為什么呢?因為的mLooper在一個線程中執行,而我們的handler是在UI線程初始化的,也就是說,我們必須等到mLooper創建完成,才能正確的返回getLooper();wait(),notify()就是為了解決這兩個線程的同步問題。

不過對于這樣的線程間的同步問題,我非常喜歡使用Semaphore。

還記得在Android Handler 異步消息處理機制的妙用 創建強大的圖片加載類一文中有類似的說明:

如果你比較細心,可能會發現里面還有一些信號量的操作的代碼,如果你不了解什么是信號量,可以參考:Java 并發專題 : Semaphore 實現 互斥 與 連接池 。 簡單說一下mSemaphore(信號數為1)的作用,由于mPoolThreadHander實在子線程初始化的,所以我在初始化前調用了mSemaphore.acquire去請求一個信號量,然后在初始化完成后釋放了此信號量,我為什么這么做呢?因為在主線程可能會立即使用到mPoolThreadHander,但是mPoolThreadHander是在子線程初始化的,雖然速度很快,但是我也不能百分百的保證,主線程使用時已經初始化結束。

哈,當時也有很多人問,為什么使用這個Semaphore,到這里我想大家應該清楚了。話說假設我當時真的HanderThread這個類,可能之前的代碼能簡化不少呢~

對了,你可能會問與Timer相比有什么優勢呢?

直接參考這篇文章吧:Handler vs Timer

ok~~

歡迎關注我的微博http://weibo.com/u/3165018720

群號:463081660,歡迎入群

微信公眾號:hongyangAndroid
(歡迎關注,第一時間推送博文信息)

來自: http://blog.csdn.net//lmj623565791/article/details/47079737

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!