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,三者关系如下图所示
![](https://img.haomeiwen.com/i2837580/a316fd87e6831c10.png)
(图片来源: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方法在主线程调用)
总结
虽然现在做异步时可能更多使用RxJava了,但是Handler还是要掌握的。
Handler其实不只可以做异步通信,它更多的是作为一种消息机制存在,甚至可以利用其设计一套事件总线方案,Android源码中有很多地方都使用到了Handler,所以了解它也有益于我们对于Android相关源码的阅读。
![](https://img.haomeiwen.com/i2837580/5b56e44d4e87cf41.jpg)
网友评论