一文搞懂Handler線程切換奧妙

網上關於Handler的使用及原理文章很多,都講得不錯,但關於主線程和子線程切換方面都是一筆帶過,不夠清晰易懂。回顧一下Handler消息機制,其組成元素:HandlerLooperMessageQueueMessage,主要作用:發送和處理消息,Looper會在一個無限循環中不斷從MessageQueue中獲取Message(它的target參數持有是發送它的Handler對象),交給對應的Handler去處理,最終回調Handler。

其實Handler主線程和子線程切換主要依靠ThreadLocal,Looper使用了ThreadLocal,代碼如下:

  private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));//重點
    }

關於ThreadLocal的原理這裏不多說,官方解釋如下:

Implements a thread-local storage, that is, a variable for which each thread
has its own value. All threads share the same {@code ThreadLocal} object,
but each sees a different value when accessing it, and changes made by one
thread do not affect the other threads. The implementation supports

這句話大概意思是:不同線程訪問時取得不同的值,任意線程對它的改變不影響其他線程的值。類實現是支持null值的。

ThreadLocal在Handler消息機制中可以這麼理解:一個線程內部的存儲類,可以在指定線程內存儲數據,數據存儲以後,只有指定線程可以得到存儲數據。簡單來說就是使用ThreadLocal,在主線程中創建對象,就與主線程有關,且實例不變,在不同線程創建對象屬於不同線程。 Looper在主線程創建那麼與主線程有關,處理消息的時候當然是回調在主線程,反之亦然。

舉個例子:

package com.sjl.test.designpatterns.singleton;

/**
 *使用ThreadLocal實現單例模式,不支持在不同線程使用
 * @author Kelly
 * @version 1.0.0
 * @filename Signleton9.java
 * @time 2020/5/13 9:47
 * @copyright(C) 2020 song
 */
public class Singleton9 {
    private static final ThreadLocal<Singleton9> signleton = new ThreadLocal(){
        @Override
        protected Object initialValue() {
            return new Singleton9();
        }
    };

    private Singleton9() {

    }

    public static Singleton9 getInstance(){
        return signleton.get();
    }
}

測試程序:

public class SingletonTest {
    public static void main(String[] args) {
        System.out.println("Singleton9:"+ Singleton9.getInstance());
        System.out.println("Singleton9:"+Singleton9.getInstance());
        System.out.println("=======");
        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                Singleton9 instance = Singleton9.getInstance();
                System.out.println("Singleton9:"+instance);

            }).start();
        }
    }
}

輸出結果:
在這裏插入圖片描述
從上圖可以看出第1-2行是在主線程,實例不變。第4-5行是在不同子線程創建,實例不同,只要子線程創建了,一直沒有終止,Singleton9.getInstance()的實例都是相同的。

下面在手寫個Handler消息機制例子,模擬Android中如何切換主線程。

package com.sjl.view.activity;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;

import com.sjl.view.R;
import com.sjl.view.widget.MyDialog;

import java.util.LinkedList;

/**
 * TODO
 *
 * @author Kelly
 * @version 1.0.0
 * @filename EqActivity.java
 * @time 2020/5/12 10:26
 * @copyright(C) 2020 song
 */
public class EqActivity extends AppCompatActivity {
    private static final String TAG = "EqActivity";
    final ThreadLocal<Looper> testThreadLocal = new ThreadLocal<Looper>() {
        @Override
        protected Looper initialValue() {
            return new Looper();
        }
    };

    private MyHandler myHandler;

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


        //Looper在主線程創建,故返回的消息在主線程
        final Looper looper = testThreadLocal.get();
        //創建Handler
        myHandler = new MyHandler(looper) {
            @Override
            public void handleMsg(String msg) {
                super.handleMsg(msg);
                Toast.makeText(EqActivity.this, msg, Toast.LENGTH_LONG).show();
            }
        };
        //子線程網絡請求,模擬業務數據
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                myHandler.sendMsg("hello");//子線程發送消息到主線程
            }
        }).start();
        //下面爲了方便演示,省掉了循環,假設是Looper循環取消息,模擬消息返回
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String msg = looper.getMsg();
        myHandler.handleMsg(msg);

       /* //Looper循環取消息,模擬消息返回,這裏在public static void main(String[] args) 測試
        looper.loop();*/
    }


    public class MessageQueue {
        /**
         * 消息隊列對應Android 中的類MessageQueue,android使用native方法,nativePollOnce(阻塞),nativeWake(喚醒)
         */
        private BlockingQueue<String> msgList = new ArrayBlockingQueue<>(100);

        /**
         * 取消息
         *
         * @return
         */
        public String next() {
            try {
                return msgList.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return null;
        }

        /**
         * 消息入隊
         *
         * @param msg
         */
        public void enqueueMessage(String msg) {
            try {
                msgList.put(msg);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    public class MyHandler {

        Looper looper;

        public MyHandler(Looper looper) {
            this.looper = looper;
        }

        /**
         * 發送消息
         *
         * @param msg
         */
        public void sendMsg(String msg) {
            this.looper.messageQueue.enqueueMessage(msg);
        }

        /**
         * 消息回調
         *
         * @param msg
         */
        public void handleMsg(String msg) {

        }

    }

    /**
     * 循環器
     */
    public class Looper {

        private MessageQueue messageQueue = new MessageQueue();

        public void loop() {
            for (; ; ) {
                // might block
                String msg = messageQueue.next();
                if (msg == null) {
                    return;
                }
                myHandler.handleMsg(msg);
            }
        }
        public String getMsg() {
            String msg = messageQueue.next();
            return msg;
        }
    }
}


上面只是個簡單例子,來說明Handler線程切換,如有寫得不對,請指出不足。

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