網上關於Handler
的使用及原理文章很多,都講得不錯,但關於主線程和子線程切換方面都是一筆帶過,不夠清晰易懂。回顧一下Handler消息機制,其組成元素:Handler
、Looper
、MessageQueue
、Message
,主要作用:發送和處理消息,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線程切換,如有寫得不對,請指出不足。