why should you know
1.面试的经典问题
2.知其然知其所以然
相关名词解释
主线程即UI线程
Handler的简单使用
情境一:主线程使用handler
public class MainActivity extends AppCompatActivity {
private MyHandler myHandler = new MyHandler(this);
public TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView)findViewById(R.id.text);
doSth();
}
private void doSth(){
new Thread(new Runnable() {
@Override
public void run() {
try {
//模拟耗时操作
Thread.sleep(1000);
//将耗时操作的结果包装成message
Message message = new Message();
message.obj = "do something";
//发送message
myHandler.sendMessage(message);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
private static class MyHandler extends Handler{
//弱引用,防止内存泄漏
private final WeakReference<MainActivity> mainActivityWeakReference;
public MyHandler(MainActivity activity){
mainActivityWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity mainActivity = mainActivityWeakReference.get();
mainActivity.textView.setText((String)msg.obj);
Log.d("handler_message",(String) msg.obj);
Log.d("thread name",Thread.currentThread().getName());
}
}
}
输出结果
D/handler_message: do something
D/thread name: main 所以handleMessage里可以直接调用 mainActivity.textView.setText((String)msg.obj);
这段代码模拟了一个简单的异步业务流程
大致的调用流程就是在新线程中执行了一个耗时操作,然后把该结果塞给message,handler将发送这个message,最终通过handleMessage回调给主线程,然后在主线程中处理这个message,于是我们完成了一次的消息的收发(异步消息)。
情境二:异步线程中使用handler
public class MainActivity extends AppCompatActivity {
private Handler mThreadHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
doSthInOtherThread();
}
/**
* 子线程使用handler
*/
private void doSthInOtherThread(){
new Thread(new Runnable() {
@Override
public void run() {
Log.d("thread A","thread A run ");
Log.d("thread A name is",Thread.currentThread().getName());
Looper.prepare();
mThreadHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
String message = (String)msg.obj;
Log.d("thread handler",message);
Log.d("thread name",Thread.currentThread().getName());
}
};
new Thread(new Runnable() {
@Override
public void run() {
Log.d("thread B","thread B run");
Log.d("thread B name is",Thread.currentThread().getName());
try {
//模拟耗时操作
Thread.sleep(1000);
Message message = new Message();
message.obj ="message from thread B";
mThreadHandler.sendMessage(message);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
Looper.loop();
}
}).start();
}
}
输出结果
D/thread A: thread A run
D/thread A name is: Thread-2
D/thread B: thread B run
D/thread B name is: Thread-3
D/thread handler: message from thread B
D/thread name: Thread-2
这里的handleMessage不是在主线程中被回调的所以不能访问主线程中的控件
分析
虽然情境一的场景出现的更为频繁,但是情境二更具一般性,所以这里会具体分析情境二。
情境二场景分析:情镜二建立了两个线程A和B;A中处理消息,B中执行耗时操作并发送消息;
Handler消息机制所涉及的几个重要的类:Handler,MessageQueue,Looper,三者关系如下图所示
(图片来源:http://vjson.com/wordpress/handler-looper原理分析.html ,不愿画图网上找了一张图)
这张图较好的反映了上述三者的关系
Handler:负责将消息(已经在异步线程中处理好的消息 eg:http请求返回的结果)发送至looper中的messagequeue,然后在handlemessage中处理这个消息
MessageQueue:消息队列,负责存储message,这里MessageQueue是个单链表,先进入的消息将先被处理,然后被移除,下文会结合源码分析(上图没有反映出来)
Looper:消息循环传递者,负责维护消息队列并不断从MessageQueue中取出消息传递给Handler,通过Looper.loop取消息,这个方法内有个for循环,并且是无限循环的,只有你身体被掏空了才会停 (>__< 当消息队列里没有消息了就会return了)
整体的调用流程用文字来描述是这样的:
你在线程A中要执行一个业务,在A中开启了一个线程B具体去执行这个耗时业务,在线程B得到操作的结果后,将这个结果包装成一条message,通过Handler发送至MessageQueue中,Looper在线程A中调用了loop方法后则会不停的从MessageQueue中取出消息交给Handler处理,最终会回调Handler的handleMessage方法,这样线程就又切换回A中了,A则可以对这个message进行处理,如果这里A是主线程的话,也就是上述的情境一,你就可以将message通过主线程显示出来了。
Magic:
线程是怎么切换的?
答:这里先大致解释下,当Looper的loop方法被调用的时候,Looper会从MessageQueue中取出单链表中第一个msg(Message对象),之后会执行msg.target.dispatchMessage(msg),这个msg中有个target对象,然后target对象里有个dispatchMessage方法,dispatchMessage方法又会调用handleMessage,这时候机智的你肯定意识到了这个target其实就是Handler对象,其实这还不是重点,线程切换的重点在于loop方法是在线程A中执行的,所以即便消息是在线程B产生的,由于loop方法执行在线程A中,所以handleMessage方法也就执行在了线程A中,如果A是主线程,handleMessage方法也就回调在主线程了。
其实知道这些,一般的面试也就够了,但最好还是看下源码!So , let`s read the fucking source code.
一切从Handler创建开始说起
Handler的创建总会调用以下两个方法中的一个
方式1
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
方式2
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
两者的差异在于是否通过传递Looper对象创建Handler,但无论哪种方式Handler对象的创建都需要Looper对象,那么我们重点看一下方式1的创建过程。
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
这里通过 Looper.myLooper()创建了一个Looper实例,然后判断这个mLooper是否为null,如果为null则抛出我们熟悉的异常
Can't create handler inside thread that has not called Looper.prepare()
所以我们在子线程中创建Handler之前一定要先调用Looper.prepare方法,然而我们在主线程中初始化Handler时并没有调用这个方法啊,其实在ActivityThread的main方法调用了prepareMainLooper方法,prepareMainLooper中也是调用prepare方法的,所以主线程中并不需要我们去调用Looper.prepare方法,要获取到主线程的Looper对象,你只需要调用Looper的getMainLooper方法即可。
接着我们再来看看Looper.prepare方法里干了什么吧
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对象中查看是否已经有Looper的实例,如果已经有则又会抛出我们熟悉的异常了。ThreadLocal可以创建线程局部变量,为每个线程提供该变量独立的副本而不相互影响,这里只需要知道其提供Looper对象,并保证一个单独线程只存在一个Looper对象,这里不对其原理做详细分析。
Only one Looper may be created per thread
这意味着一个线程只能有一个Looper 对象,Looper.prepare方法只能在同一个线程中调用一次,之后ThreadLocal对象会将new出来的Looper对象set进去。
我们再转回Handler构造函数中的Looper.myLooper方法中
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
其实就是调用ThreadLocal的get方法获取刚刚set进去的Looper对象
那么MessageQueue又是怎么和Looper对象关联的呢
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
可以看到调用Looper的构造函数时会创建MessageQueue对象,并且Looper持有这个MessageQueue,并在Handler构造函数将MessageQueue交由Handler持有。
好了,现在需要的对象都已经实例化了,再来看看Handler,MessageQueue,Looper是怎么处理消息的吧
首先我们需要Handler的sendMessage方法,最终会调用 sendMessageAtTime
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
然后调用了Handler的enqueueMessage方法
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
msg.target = this,可以看到Handler把自己传递进了Message中所以Message持有Handler对象,接着我们再看看MessageQueue的enqueueMessage方法
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
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;
//mark 1 如果链表中没有元素则将该消息置为表头
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 {
//mark 2 如果链表中有元素则将该消息添加至表尾
// 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;
}
这里的意思就是将新的message添加至链表的表尾,如果链表中没有元素则置为表头,这样消息就插入至MessageQueue中了。
现在MessageQueue已经有消息了,是时候取出消息了,前文说了Looper会调用loop方法不断从MessageQueue中取出消息,这里为了更好的理解loop方法简化了其代码
public static void loop() {
for (;;) {
//取出表头的message
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
try {
msg.target.dispatchMessage(msg);
} finally {
}
}
}
从代码中可以看到其不断从MessageQueue中获取位于表头的消息( queue.next()取的是表头元素),然后调用消息中的target的dispatchMessage方法,来看看dispatchMessage方法
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
这个其实是Handler中的方法,最后其实还是调用了handleMessage方法,但是之前有个判断,当msg.callback不为null才会调用handleMessage,其实这个callback就是Runnable接口,当你调用Handler的post方法发送消息而不是通过sendMessage发送消息时,它最后就会调用Runnable中的run方法,而不是handleMessage。这里稍微解释一下,post方式中的Runnable接口会被包装成一个Message,最终也会调用enqueueMessage方法,所以post也可以发送消息。
到这里整个过程也就分析完了,拿到消息的你可以爱干啥干啥了!
Tips:
1.当你在主线程使用Handler时要注意内存泄漏的情况,可以在Handler中用弱引用的Activity
2.主线程使用Handler时使用post方式和sendMessage方式时,post的run方法或者Handler的handleMessage方法都将在主线程回调
3.loop方法被调用的线程就是run方法,handleMessage方法被回调线程(2是特例,loop方法在主线程调用)
同步屏障
主要用于提高消息执行的优先级,常见于UI的刷新操作
//此处设置target为null,正常情况下消息的target为handler
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
//当targe为null时则认为是同步屏障,取出并执行完异步消息
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
nativePollOnce的阻塞原理
- 通过linux的epoll操作符实现
- epoll是一种I/O事件通知机制,是linux 内核实现IO多路复用的一个实现。
IO多路复用是指,在一个操作里同时监听多个输入输出源,在其中一个或多个输入输出源可用的时候返回,然后对其的进行读写操作
handler postdelay不准确的原因
postDelay的时候已经把预期执行的时间点计算好了,因为消息是串行执行的,假设之前一个message存在耗时操作,导致时间超过了延时执行消息的话就会导致消息执行的时间点不准确
主线程中调用looper.loop()为什么不会阻塞主线程
looper中loop方法维护了主线程的事件处理(包含ui事件),其本身是主线程重要的一环,保证了主线程的持续工作,线程在执行完毕后该线程会结束,所以没有死循环的loop方法,主线程反而会结束
执行死循环并不会过多的cpu,因为在没有消息时,主线程会处于休眠状态,不会占用cpu,具体涉及到Linux pipe/epoll机制不过多深入
IdleHandler 消息闲时机制,主要触发于消息执行完或者延迟消息还未触发前,返回true则认为需要保留该idlehandler,下次闲时仍旧会再次触发
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
*/
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
https://www.colabug.com/2020/0218/7011593/
使用场景:
- 触发GC时,利用闲时的特性不阻塞当前线程,leakcanary由于需要触发gc时
- 不适合一些初始化操作,因为没法保证稳定的调用时机,和postDelay原因类似
- GcIdler使用
总结
虽然现在做异步时可能更多使用RxJava了,但是Handler还是要掌握的。
Handler其实不只可以做异步通信,它更多的是作为一种消息机制存在,甚至可以利用其设计一套事件总线方案,Android源码中有很多地方都使用到了Handler,所以了解它也有益于我们对于Android相关源码的阅读。