一般来说,提起跨线程通信,都能想起来使用Handler,但它到底是怎么实现跨线程的呢?
跨线程实现
handler的基本用法:
/**
* Handler用法
*/
@SuppressLint("HandlerLeak")
final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 0x11) {
//更新ui
Toast.makeText(getApplicationContext(),"更新UI操作",Toast.LENGTH_SHORT).show();
}
}
};
new Thread(new Runnable() {
@Override
public void run() {
//FIXME 这里直接更新ui是不行的
//还有其他更新ui方式,runOnUiThread()等
Message message = Message.obtain();
message.what = 0x11;
handler.sendMessage(message);
}
}).start();
在子线程中通过调用Handler.sendMessage(Message message)方法来发送消息,之后重写Handler的handlerMessag(Message message)方法拿到这个message对象,做UI更新操作。
那么思考一个问题:sendMessage之后的消息处理方法handleMessage()怎么就在主线程执行了呢?
首先,明确一个线程只能有一个Looper对象,这是个消息循环,内部维护着一个MessageQueue消息队列,主线程也有它对应的Looper,这个MessageQueue中的消息来源可以很多,它并不关心是哪个线程发送来的Message统统加入这个消息队列中,通过查看源码我们知道入队操作在该方法中进行:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
可以看到在这个方法中有一个synchronized修饰的同步代码块,同步锁针对的资源this指代的是当前的Message对象。
简化一下代码结构看看:
boolean enqueueMessage(Message msg, long when) {
// ... 处理异常
synchronized (this) {
// ...加入消息队列
}
return true;
}
Looper.loop()方法在线程中被调用之后,我们的MessageQueue中的消息就被轮询起来。
所以,当消息循环执行到我们发送的那个消息时,它自然就在调用Looper.loop()方法的线程内执行了,这就是跨线程的原因,就这么简单。
那到底怎么做线程间切换的呢?
首先,多线程中一个Thread对应一个Looper对象。
这是通ThreadLocal来实现的:Looper【类】
// sThreadLocal.get() will return null unless you've called prepare().
@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
Thread【类】
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class.
* 与此线程有关的 ThreadLocal 值。这张地图得到维护
* 通过 ThreadLocal 类。
*/
ThreadLocal.ThreadLocalMap threadLocals = null;
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));
}
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
* 设置这个线程局部变量的当前线程的副本
* 到指定值。大多数子类将不需要
* 重写此方法,仅依赖于 {@link #initialValue}
* 设置线程局部变量值的方法。
*
* @param value 要存储在当前线程的副本中的值
* 这个线程本地的。
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
* 返回当前线程的 this 副本中的值
* 线程局部变量。如果变量没有值
* 当前线程,首先初始化为返回值
* 通过调用 {@link #initialValue} 方法。
*
* @return 这个线程本地的当前线程的值
*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap是Thread类的属性。它里面保存着以ThreadLocal为key,Looper为value的Map。
也就是说通过ThreadLocal中根据key查找的Looper消息循环来切换线程。
验证一下:
/**
* Handler用法
*/
@SuppressLint("HandlerLeak")
final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 0x11) {
//更新ui
Toast.makeText(getApplicationContext(),"更新UI操作",Toast.LENGTH_SHORT).show();
}
}
};
new Thread(new Runnable() {
@Override
public void run() {
//FIXME 这里直接更新ui是不行的
//还有其他更新ui方式,runOnUiThread()等
Message message = Message.obtain();
message.what = 0x11;
// handler.sendMessage(message); //------方式(1)------
//从子线程切换到UI线程做更新UI操作
runOnUiThread(()->{ //------方式(2)-------
Toast.makeText(getApplicationContext(),"更新UI操作",Toast.LENGTH_SHORT).show();
});
//也就是说指定的主线程的Looper来进行消息循环,自然就跨线程了
Looper.prepare(); //------方式(3)-------
new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
if (msg.what == 0x11) {
//更新ui
// Toast.makeText(getApplicationContext(),"更新UI操作",Toast.LENGTH_SHORT).show();
}
}
}.sendMessage(message);
Looper.loop();
}
}).start();
好家伙,线程中也可以进行(方式<2、3>)更新UI操作哦~
网友评论