第三章--Handler的二三事

本來寫了開場白,算了,直接進入主題。

1、什麼是Handler,作用是啥,爲什麼人人用了都說好。

Handler,我去查了一下字典,有處理者管理者的意思,它主要是負責線程之間的通信,比如UI線程(主線程)和其他線程之間的。那爲什麼一定要它負責通信呢,直接操作ui不行嗎?當然不行,如果多個線程同時改變界面的屬性值,那會變的很混亂,也叫做會導致線程安全問題。爲此Android有一條必須遵守的規則:只允許UI線程修改Activity裏的UI組件,是不是很霸氣。但是這樣其他線程就無法動態改變UI屬性了,制定規則當然不是爲了堵住一條路,Handler的作用這時候就體現出來了。
1、在新啓動的線程中發送消息。
2、在主線程中處理消息。
比如你想改變界面上一張圖片,線程中發個消息,Handler收到後立馬乖乖的處理,然後給你修改。這麼聽話的東西怎麼會沒有人喜歡呢。

2、Handler簡單使用

先貼代碼

public class MainActivity extends Activity{
    //定義一個圖片id數組
    int[] imageIds = new int[]{...};
    定義當前的顯示的圖片id
    int currentId = 0;
    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        final ImageView show = (ImageView)findViewById(R.id.show);
        final Handler myHandler = new Handler(){
            @Override
            public void handleMessage(Message msg){
                if(msg.what == 0x1233){
                    //修改照片
                    show.setImageResource(imageIds[currentId++] % imageIds.length);
                }
            }
        }
        //計時器
        new Timer().schedule(new TimerTask(){
            @Override
            public void run(){
                myHandler.sendMessage(0x1233);
            }
        }, 0, 1200);
    }

}

這樣就完成了一個自動播放圖片的效果,定義了一個定時器,讓它定時的sendMessage,實質上就是開了一個新的線程,由於android不允許在新線程中訪問Activity裏的界面組件,所以只能發個消息咯,Handler也就定時的改變圖片,並且是循環的,在這裏不得不說算法好厲害。另外佈局文件就只有一個ImageView。

3、Looper,MessageQueue

其實Handler不是單打獨鬥,它還有幾個好朋友,Looper和MessageQueue。

先說Looper

  • 每個線程只能擁有一個Looper,不能有第二個,它的loop()方法就是讀取MessageQueue中的消息,讀到之後就會把消息交給發送消息的Handler處理。看到這有人可能會說那你上面的Looper呢?別急,這是因爲在UI線程也就是主線程中,系統已經初始了一個Looper,所以我們就不用了啊~~。但是如果是自己啓動的線程,我在Looper源碼中發現註釋裏有個例子。
class LooperThread extends Thread {
       public Handler mHandler;

        public void run() {
            Looper.prepare();

            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                    // process incoming messages here
                }
            };

            Looper.loop();
        }

先是Looper.prepare(),最後是Looper.loop()。我們來看看Looper.prepare()做了一些什麼。

     /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference 這能讓你在創建Handler的時候能引用Looper
      * this looper, before actually starting the loop. Be sure to call 在Looper使用前,必須保證調用了該方法
      * {@link #loop()} after calling this method, and end it by calling 調用之後,在loop()就停止了
      * {@link #quit()}.
      */
    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");
        }
        //這裏應該就是new了Looper對象
        sThreadLocal.set(new Looper(quitAllowed));
    }

我用我蹩腳的英語翻譯了一下,這個方法保證了線程中只有一個Looper對象。這似乎是使用了重載的方式調用,這樣設計難道保證了封裝的特性?
然後loop()的源碼


    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
        //沒有調用prepare().會拋出異常
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }

        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        //發消息的標識符,final
        final long ident = Binder.clearCallingIdentity();
        //死循環來了
        for (;;) {
            Message msg = queue.next(); // might block 獲取消息隊列的下一個
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger 
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(msg);

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted. 檢查標識符是否變了。
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }

就是從MessageQueue中不斷取出消息

然後MessageQueue

消息隊列,它有一點神祕,自始至終都沒有見過它,在loop()方法的源碼中有這樣一個語句final MessageQueue queue = me.mQueue;莫非Looper的構造器什麼的裏就有它?再次去瞧瞧:

   private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

原來啊系統在創建Looper對象的時候,就會在它的構造器中創建該對象。而且MessageQueue是採用先進先出的方式管理消息。

3、總結一下

使用線程中的Handler:
1、Loop.prepare(),它構造器也會創建配套的MessageQueue對象
2、創建Handler實例,重寫handleMessage()方法
3、Looper.loop()。啓動Looper.
4、沒有第四

4、Handler的內存泄漏問題

視頻中說到,如果Activity結束了,但是和其有關聯的Handler還沒有處理完它的消息,如果Handler不是靜態的,就不會被Activity或者Service回收,Handler使用的內存就不會被釋放,這就造成了內存泄漏問題。這裏的一個解決方法,就是弱引用。在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了具有弱引用的對象,都會回收它的內存。

public static class Testhandler extends Handler{

        public final WeakReference<HandlerButtonActivity> mHandlerButoonActivityWeakReference;

        public Testhandler(HandlerButtonActivity activity){
            mHandlerButoonActivityWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            HandlerButtonActivity activity = mHandlerButoonActivityWeakReference.get();
            ...}
   }

大概會寫點異步,老師提了一下,所以沒弄懂啊啊啊啊

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