Android 學習 之 Looper Handler Thread Messahe 多線程

在Android下面也有多線程的概念,在C/C++中,子線程可以是一個函數,一般都是一個帶有循環的函數,來處理某些數據,優先線程只是一個複雜的運算過程,所以可能不需要while循環,運算完成,函數結束,線程就銷燬。對於那些需要控制的線程,一般我們都是和互斥鎖相互關聯,從而來控制線程的進度,一般我們創建子線程,一種線程是很常見的,那就是帶有消息循環的線程。

消息循環是一個很有用的線程方式,曾經自己用C在Linux下面實現一個消息循環的機制,往消息隊列裏添加數據,然後異步的等待消息的返回。當消息隊列爲空的時候就會掛起線程,等待新的消息的加入。這是一個很通用的機制。

在Android,這裏的線程分爲有消息循環的線程和沒有消息循環的線程,有消息循環的線程一般都會有一個Looper,這個事android的新概念。我們的主線程(UI線程)就是一個消息循環的線程。針對這種消息循環的機制,我們引入一個新的機制Handle,我們有消息循環,就要往消息循環裏面發送相應的消息,自定義消息一般都會有自己對應的處理,消息的發送和清除,消息的的處理,把這些都封裝在Handle裏面,注意Handle只是針對那些有Looper的線程,不管是UI線程還是子線程,只要你有Looper,我就可以往你的消息隊列裏面添加東西,並做相應的處理。

但是這裏還有一點,就是只要是關於UI相關的東西,就不能放在子線程中,因爲子線程是不能操作UI的,只能進行數據、系統等其他非UI的操作。

那麼什麼情況下面我們的子線程才能看做是一個有Looper的線程呢?我們如何得到它Looper的句柄呢?

Looper.myLooper();獲得當前的Looper

Looper.getMainLooper () 獲得UI線程的Lopper

我們看看Handle的初始化函數,如果沒有參數,那麼他就默認使用的是當前的Looper,如果有Looper參數,就是用對應的線程的Looper。

如果一個線程中調用Looper.prepare(),那麼系統就會自動的爲該線程建立一個消息隊列,然後調用 Looper.loop();之後就進入了消息循環,這個之後就可以發消息、取消息、和處理消息。這個如何發送消息和如何處理消息可以再其他的線程中通過Handle來做,但前提是我們的Hanle知道這個子線程的Looper,但是你如果不是在子線程運行 Looper.myLooper(),一般是得不到子線程的looper的。

public void run() {
            synchronized (mLock) {
                Looper.prepare();
               //do something
            }
            Looper.loop();
        }

所以很多人都是這樣做的:我直接在子線程中新建handle,然後在子線程中發送消息,這樣的話就失去了我們多線程的意義了。

class myThread extends Thread{

             private EHandler mHandler ;

             public void run() {

                 Looper myLooper, mainLooper;

                 myLooper = Looper.myLooper ();

                mainLooper = Looper.getMainLooper ();

                String obj;

                if (myLooper == null ){

                         mHandler = new EHandler(mainLooper);

                         obj = “current thread has no looper!” ;

                }

                else {

                     mHandler = new EHandler(myLooper);

                     obj = “This is from current thread.” ;

                }

                mHandler .removeMessages(0);

                Message m = mHandler .obtainMessage(1, 1, 1, obj);

                mHandler .sendMessage(m);

             }

  }

 

可以讓其他的線程來控制我們的handle,可以把 private EHandler mHandler ;放在外面,這樣我們的發消息和處理消息都可以在外面來定義,這樣增加程序代碼的美觀,結構更加清晰。

 

對如任何的Handle,裏面必須要重載一個函數

public void handleMessage(Message msg)

這個函數就是我們的消息處理,如何處理,這裏完全取決於你,然後通過 obtainMessage和 sendMessage等來生成和發送消息, removeMessages(0)來清除消息隊列。Google真是太智慧了,這種框架的產生,我們寫代碼更加輕鬆了。

 

有的時候,我們的子線程想去改變UI了,這個時候千萬不要再子線程中去修改,獲得UI線程的Looper,然後發送消息即可。

 

我們來看看高煥堂的代碼:

// class ac01 extends Activity {

          // ………

              public void onClick(View v) {

                     switch (v.getId()){

                     case 101:

                                  t = new myThread();

                            t .start();

                          break ;

                     case 102:

                  finish();

                                break ;

                     }

           }

//——————————————————             

class EHandler extends Handler {

                   public EHandler(Looper looper) {

                       super (looper);

                   }

                   @Override

                   public void handleMessage(Message msg) {

                      tv .setText((String)msg. obj );

               }

           }

//——————————————————             

class myThread extends Thread{

             private EHandler mHandler ;

             public void run() {

                Looper myLooper, mainLooper;

                myLooper = Looper.myLooper ();

                mainLooper = Looper.getMainLooper ();

                String obj;

                if (myLooper == null ){

                        mHandler = new EHandler(mainLooper);

                        obj = “current thread has no looper!” ;

                }

                else {

                     mHandler = new EHandler(myLooper);

                     obj = “This is from current thread.” ;

                }

                mHandler .removeMessages(0);

                Message m = mHandler .obtainMessage(1, 1, 1, obj);

                mHandler .sendMessage(m);

             }

  }

}

 

完全是不知所云,一坨狗屎。我們來看,在上面的run裏面

Looper myLooper, mainLooper;

myLooper = Looper.myLooper (); //很明顯這個會返回空,因爲你還沒有 prepare,不會返回Looper。

mainLooper = Looper.getMainLooper ();

 

建議大家在看Looper的時候不要看高煥堂的書,感覺他也不是很懂,倒還把我搞糊塗了。講了那麼多,完全是他自己的理解,他自己的理解很是複雜,關鍵的是把簡單的問題複雜化,並且複雜之後的東西還是錯的。我們看看Goole Music App的源代碼。

 

在MediaPlaybackActivity.java中,我們可以看一下再OnCreate中的有這樣的兩句:

        mAlbumArtWorker = new Worker(“album art worker”);
        mAlbumArtHandler = new AlbumArtHandler(mAlbumArtWorker.getLooper());

很明顯這兩句,是構建了一個子線程。並且這個子線程還是Looper的子線程,這裏很牛逼的使用了 mAlbumArtWorker.getLooper()這個函數,因爲我們知道,我們能夠得到子線程的Looper的途徑只有一個:就是在子線程中調用 Looper.myLooper (),並且這個函數還要在我們perpare之後調用才能得到正確的Looper,但是他這裏用了一個這樣的什麼東東 getLooper,不知道它是如何實現的?

 

這裏有一個大概的思路,我們在子線程的的prepare之後調用 myLooper ()這個方法,然後保存在一個成員變量中,這個getLooper就返回這個東西,但是這裏會碰到多線程的一個很突出的問題,同步。我們在父線程中調用 mAlbumArtWorker.getLooper(),但是想要這個返回正確的looper就必須要求我們的子線程運行了prepare,但是這個東西實在子線程運行的,我們如何保證呢?

 

我們看Google是如何實現的?

   private class Worker implements Runnable {
        private final Object mLock = new Object();
        private Looper mLooper;
       
        /**
         * Creates a worker thread with the given name. The thread
         * then runs a {@link android.os.Looper}.
         * @param name A name for the new thread

         */
        Worker(String name) {
            Thread t = new Thread(null, this, name);
            t.setPriority(Thread.MIN_PRIORITY);
            t.start();
            synchronized (mLock) {
                while (mLooper == null) {
                    try {
                        mLock.wait();
                    } catch (InterruptedException ex) {
                    }
                }
            }
        }
       
        public Looper getLooper() {
            return mLooper;
        }
       
        public void run() {
            synchronized (mLock) {
                Looper.prepare();
                mLooper = Looper.myLooper();
                mLock.notifyAll();
            }
            Looper.loop();
        }
       
        public void quit() {
            mLooper.quit();
        }
    }
 

我們知道,一個線程類的構造函數是在主線程中完成的,所以在我們的 Worker的構造函數中我們創佳一個線程,然後讓這個線程運行,這一這個線程的創建是指定一個 Runnabl,這裏就是我們的Worker本身,在主線程調用 t.start();,這後,我們子線程已經創建,並且開始執行work的run方法。然後下面的代碼很藝術:

synchronized (mLock) {
                while (mLooper == null) {
                    try {
                        mLock.wait();
                    } catch (InterruptedException ex) {
                    }
                }
            }

我們開始等待我們的子線程給mLooper賦值,如果不賦值我們就繼續等,然後我們的子線程在運行run方法之後,在給 mLooper賦值之後,通知worker夠着函數中的wait,然後我們的構造函數才能完成,所以我們說:

mAlbumArtWorker = new Worker(“album art worker”);

這句本身就是阻塞的,它創建了一個子線程,開啓了子線程,並且等待子線程給mLooper賦值,賦值完成之後,這個函數才返回,這樣才能保證我們的子線程的Looper的獲取絕對是正確的,這個構思很有創意。值得借鑑。

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