网上关于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如何线程切换。
另外再补充一点,我们平时在经常在主线程使用 Handler,所以导致了很少用到 Looper.prepare() 方法,是因为ActivityThread主线程中默认调用Looper.prepareMainLooper()
如下代码:
frameworks/base/core/java/android/app/ActivityThread.java
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
...
Looper.prepareMainLooper();
// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
// It will be in the format "seq=114"
long startSeq = 0;
if (args != null) {
for (int i = args.length - 1; i >= 0; --i) {
if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
startSeq = Long.parseLong(
args[i].substring(PROC_START_SEQ_IDENT.length()));
}
}
}
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
...
Looper.loop();
...
}
竟然主线程可以创建Handler,反之亦然,但是在子线程不能直接创建Handler,会空指针异常,因为Looper没有实例化),正常这样写:
new Thread(new Runnable() {
@Override
public void run() {
// 在当前线程创建Looper对象
Looper.prepare();
// 创建Handler对象传入当前线程的Looper
Handler handler = new Handler(Looper.myLooper()){
@Override
public void handleMessage(Message msg) {//运行在子线程
super.handleMessage(msg);
}
};
// 开始循环消息队列
Looper.loop();
}
}).start();
如果我们要在子线程创建Handler,推荐使用封装好的HandlerThread创建,如下:
HandlerThread handlerThread = new HandlerThread("hello");
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper()){
@Override
public void handleMessage(Message msg) {//运行在子线程
super.handleMessage(msg);
}
};
下面说下应用场景:
主线程创建Handler,可以在子线程或主线程使用Handler 发送消息并回调handleMessage(运行在主线程,可更新UI),比如:网络请求,文件处理等;
子线程创建Handler,可以在子线程或主线程使用Handler 发送消息并回调handleMessage(运行在子线程,不可更新UI),比如:处理相机预览帧的数据,子线程间数据通讯
小结
上面通过主要讲解了Handler如何进行主子线程切换,如有写得不对,请指出不足。