Android之HandlerThread源碼分析和簡單使用(主線程和子線程通信、子線程和子線程通信)

1、先熟悉handler方式實現(xiàn)主線程和子線程互相通信方式,子線程和子線程的通信方式

      如果不熟悉或者忘記了,請參考我的這篇博客     Android之用Handler實現(xiàn)主線程和子線程互相通信以及子線程和子線程之間的通信     http://blog.csdn.net/u011068702/article/details/75577005

 
2、貼上簡單HandlerThread簡單使用(主線程和子線程通信、子線程和子線程通信)的例子
1、activity_main.xml文件

 

    <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:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context="com.example.handler.MainActivity1" >
     
        <Button
            android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ParentToChile" />
        <Button
            android:layout_below="@+id/button1"
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ChileToParent" />
        <Button
            android:layout_below="@+id/button2"
            android:id="@+id/button3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ChileToChile" />
     
    </RelativeLayout>

 
2、MainActivity.java文件

 

    package com.example.handler;
     
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.HandlerThread;
    import android.os.Looper;
    import android.os.Message;
    import android.support.v7.app.ActionBarActivity;
    import android.util.Log;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Button;
     
    public class MainActivity extends ActionBarActivity {
        
        public static final String TAG = "HandlerTest";
        public HandlerThread mHandlerThread;
        public Handler mChileHandler;
        public Handler mHandlerCToP = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                int id =  (int) Thread.currentThread().getId();
                Log.d(TAG, "mHandlerCToP currentThread id is:" + id);
            }
        };
        public Button mButtonPtoC;
        public Button mButtonCtoP;
        public Button mButtonCtoC;
        
        public Handler mHandler = new Handler();
        public Handler mHandlerCtoC = null;
        
        
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            
            int id =  (int) Thread.currentThread().getId();
            Log.d(TAG, "onCreate currentThread id is:" + id);
            
            initUIAndThread();
        }
        
        public void initUIAndThread() {
            mHandlerThread = new HandlerThread("chenyu");
            mHandlerThread.start();
            mHandler = new Handler(mHandlerThread.getLooper()) {
                @Override
                public void handleMessage(Message msg) {
                    int id =  (int) Thread.currentThread().getId();
                    Log.d(TAG, "initThread() mHandler handleMessage currentThread id is:" + id);
                    switch(msg.what) {
                        case 0:
                            mHandlerCToP.post(new Runnable(){
                                @Override
                                public void run() {
                                    int id =  (int) Thread.currentThread().getId();
                                    Log.d(TAG, "initThread() mHandlerCToP post currentThread id is:" + id);
                                    mButtonPtoC.setText("chenyu");
                                }
                            });
                            break;
                        case 1:
                            mHandlerCToP.post(new Runnable(){
                                @Override
                                public void run() {
                                    int id =  (int) Thread.currentThread().getId();
                                    Log.d(TAG, "initThread() mHandlerCToP post currentThread id is:" + id);
                                    mButtonCtoC.setText("chenyu");
                                }
                            });
                        default:
                            break;
                    }
                }
            };
            
            mButtonPtoC = (Button)findViewById(R.id.button1);
            mButtonCtoP = (Button)findViewById(R.id.button2);
            mButtonCtoC = (Button)findViewById(R.id.button3);
            
            mButtonPtoC.setOnClickListener(new OnClickListener(){
                @Override
                public void onClick(View v) {
                    int id =  (int) Thread.currentThread().getId();
                    Log.d(TAG, "mButtonPtoC currentThread id is:" + id);
                    Log.d(TAG, "mHandlerPToc msg.what is 0");
                    mHandler.sendEmptyMessage(0);
                }
            });
            
            mButtonCtoP.setOnClickListener(new OnClickListener(){
                @Override
                public void onClick(View v) {
                    int id =  (int) Thread.currentThread().getId();
                    Log.d(TAG, "mButtonCtoP currentThread id is:" + id);
                    Log.d(TAG, "mHandlerPToc msg.what is 0");
                    mHandlerCToP.sendEmptyMessage(0);
                }
            });
            
            mButtonCtoC.setOnClickListener(new OnClickListener(){
                @Override
                public void onClick(View v) {
                    new Thread(new Runnable(){
                        @Override
                        public void run() {
                            int id =  (int) Thread.currentThread().getId();
                            Log.d(TAG, "mButtonCtoC currentThread id is:" + id);
                            Log.d(TAG, "mHandlerCToc msg.what is 1");
                            mHandler.sendEmptyMessage(1);
                        }
                    }).start();
                }
            });
        }
        
         @Override
         protected void onDestroy() {
             super.onDestroy();
             mHandlerThread.quit();
         }    
    }

 

 
3、簡單分析例子
   1)、原始頁面效果

      

 
    2)、控制臺初始化打印的線程ID

       

       分析:在onCreate方法里面打印的主線程的Id為1,有3個按鈕,分別是主線程向子線程發(fā)消息,子線程向主線程發(fā)消息,子線程和子線程發(fā)送消息,我們一開始就是對HandlerThread進(jìn)程初始化,其實它就是一個線程,后面會分析,然后執(zhí)行了start方法,我們把HandlerThread的looper對象傳遞給了Handler,之前的文章已分析,一個線程只能有一個looper對象,Handler擁有了HandlerThread的looper對象,就相當(dāng)于這個Handler在HandlerThread線程同樣的線程Id,可以理解為Handler 在子線程里面構(gòu)建,為什么我這里還有其它的Handler構(gòu)建,因為想搞清楚在哪里構(gòu)建屬于哪個線程以及子線程的handler是否可以更新UI,主線程構(gòu)建的handler是否可以更新UI,
    3)、依次點(diǎn)擊3個按鈕后控制臺打印的日志

   

    

     我們可以看到在onCreate方法里面始化handler的時候傳遞了一個HandlerThread的looper對象,點(diǎn)擊第一個按鈕后后沒有開啟線程,當(dāng)前線程依然是主線程,handler發(fā)送了一個消息,然后初始化的handlMessage收到消息了,也就完成了主線程到子線程的通信,當(dāng)收到消息的時候,我們發(fā)現(xiàn)線程的id是6314,所以這個時候雖然是在onCreate里面構(gòu)建的handler里面的handlerMessage方法,但是線程Id是和HandlerThread線程Id是一樣的,然后初始化handler去更新界面,我們代碼是用mHandlerCtopP去更新的,因為它的初始化是在主線程構(gòu)建的,所以可以post可以更新UI,但是這個時候用擁有HandlerThread的looper對象的handler更新界面就會出問題,和子線程里面的handler去更新界面異常一樣,如下圖

然后點(diǎn)擊第二個按鈕,是現(xiàn)實子線程向主線程通信,我們發(fā)現(xiàn)點(diǎn)擊時間里面的線程Id,和handler收到消息的 handleMessage方法里面的線程id, 都是一樣,和主線程Id一樣,所以我們可以用這個handler直接post來更新UI,

點(diǎn)擊第三個按鈕,是實現(xiàn)子線程和子線程的通信,執(zhí)行點(diǎn)擊方法,我們開啟了一個線程,自然線程Id會和主線程的不一樣,為6318, handler收到消息的 handleMessage方法里面的線程id為6314,所以這里可以理解為為子線程線程里面構(gòu)建了handler,然后用主線程構(gòu)建的handler更新ui

 
   4)、依次點(diǎn)擊3個按鈕后手機(jī)效果

   

 
4、HandlerThread.java源碼分析
    1、上源代碼

     

    package android.os;
     
    /**
     * 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.
     */
    public class HandlerThread extends Thread {
        int mPriority;
        int mTid = -1;
        Looper mLooper;
     
        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 is 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;
        }
     
     
        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;
        }
     
        /**
         * Returns the identifier of this thread. See Process.myTid().
         */
        public int getThreadId() {
            return mTid;
        }
    }


1、我們知道這個類在android.os目錄下,然后繼承了Thread,也就是一個線程

 

2、構(gòu)造方法會傳一個字符串,這里可以隨便寫,只作為一個標(biāo)識而已,初始化的時候會調(diào)用start方法,然后會執(zhí)行run()方法

 

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


先執(zhí)行了Looper,prepare(),然后給當(dāng)前mLooper賦值,然后進(jìn)行Looper.loop(),進(jìn)行輪尋,這個代碼和標(biāo)準(zhǔn)的子線程用handler的方式差不多,也可以理解為這種方式的封裝,所以才能顯示子線程和子線程和主線程之間的通信,原理都是一樣。

 

3、getLooper()中有個wait(),這有什么用呢?因為的mLooper在一個線程中執(zhí)行創(chuàng)建,而我們的handler是在UI線程中調(diào)用getLooper()初始化的,必須等到mLooper創(chuàng)建完成,才能正確的返回。getLooper();wait(),notify()就是為了解決這兩個線程的同步問題。

 

4、這里有個退出的方法是

 

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


我之前寫代碼的時候,以為停止線程就調(diào)用了,mHandlerThread.stop()方法,后面沒發(fā)現(xiàn)什么問題,直到我們有個功能需要,從控制臺下發(fā)消息讓手機(jī)恢復(fù)默認(rèn)出場,函數(shù)執(zhí)行到了這里,導(dǎo)致進(jìn)程崩潰了,然后就發(fā)現(xiàn)這里有問題,需要調(diào)用mHandlerThread.quit()方法正常退出。

 


 
5、總結(jié)

      有時還需要頻繁更新UI,或則主線程向子線程通信,以及子線程和子線程經(jīng)常通信的時候,我們可以使用HandlerThread,如果哪里沒說清楚,或則講得有問題,歡迎點(diǎn)評

 



 


 



  作者:chen.yu
深信服三年半工作經(jīng)驗,目前就職游戲廠商,希望能和大家交流和學(xué)習(xí),
微信公眾號:編程入門到禿頭 或掃描下面二維碼
零基礎(chǔ)入門進(jìn)階人工智能(鏈接)