Android|Android Handler详解(附面试题)

Android|Android Handler详解(附面试题)
文章图片
Handler模型.png 可以将Handler模型理解为:生产者—消费者 模型。
该模型中,生产者在子线程中生产Message,调用Handler对象的sendMessage()等方法,将Message加入到MessageQueue中;Looper.loop()死循环从MessageQueue中取出Message,然后调用Handler对象的handleMessage()方法在主线程中消耗掉Message。
1.源码分析 想要弄清楚Handler,得先理解Thread和ThreadLocal
1.1 Thread和ThreadLocal

public class Thread implements Runnable { ... ThreadLocalMap threadLocals = null; ... }

从Thread源码中得知:
  1. 每一个Thread都有一个ThreadLocalMap类型的成员变量:threadLocals
  2. ThreadLocalMap定义在ThreadLocal中
public class ThreadLocal {public ThreadLocal() { } 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(); } public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } static class ThreadLocalMap { ... } }

从ThreadLocal源码中得知:
  1. ThreadLocal并不是一个Thread,只不过Thread使用了ThreadLocal中定义的ThreadLocalMap。
  2. 由createMap方法可知,ThreadLocal负责创建当前线程对应Thread对象的ThreadLocalMap
  3. getMap方法获取的是当前线程对应Thread对象的threadLocals属性
  4. set方法是将某一个对象添加到当前线程对应Thread对象的threadLocals中,get方法同理。
测试ThreadLocal:
public class MyTest { /** * 多个线程可以共用一个ThreadLocal */ private static ThreadLocal mThreadLocal = new ThreadLocal<>(); public static void main(String[] args) { new MyThread("张三").start(); new MyThread("李四").start(); }static class MyThread extends Thread { private Person mPerson; public MyThread(String name) { mPerson = new Person(name); } @Override public void run() { super.run(); // ThreadLocal的set、get方法操作的是当前线程对应Thread对象的threadLocals属性 mThreadLocal.set(mPerson); Person person = mThreadLocal.get(); System.out.println(Thread.currentThread() + "----" + person.getName()); } }static class Person { private String mName; public Person(String name) { mName = name; } public String getName() { return mName; } public void setName(String name) { this.mName = name; } } }

测试结果:
Thread[Thread-0,5,main]----张三 Thread[Thread-1,5,main]----李四

ThreadLocal总结:
ThreadLocal提供了线程局部变量,每个线程都可以通过set和get方法来对这个局部变量进行操作,且不会和其他线程的局部变量冲突,实现了线程的数据隔离。
1.2 Looper、MessageQueue、Message、Handler Looper和Handler都持有MessageQueue的引用,那么是谁创建的MessageQueue?
查看Looper的源码,从Looper的构造函数可知,MessageQueue是由Looper创建的。
public final class Lopper { @UnsupportedAppUsage static final ThreadLocal sThreadLocal = new ThreadLocal(); @UnsupportedAppUsage final MessageQueue mQueue; final Thread mThread; private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }public static void prepare() { prepare(true); }@Deprecated public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } } 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)); } public static @Nullable Looper myLooper() { return sThreadLocal.get(); } public static void loop() { final Looper me = myLooper(); ... me.mInLoop = true; final MessageQueue queue = me.mQueue; ... for (; ; ) { ... Message msg = queue.next(); // might block ... try { msg.target.dispatchMessage(msg); } } } public void quit() { mQueue.quit(false); } }

从Looper源码中得知:
  1. Looper的构造函数私有化,不能通过new创建Looper对象,需要使用Looper.prepare()来创建Looper对象
  2. sThreadLocal.set(new Looper(quitAllowed)); 得知可以在当前线程的其他地方使用sThreadLocal.get()获取Looper对象
  3. loop()方法里面写了一个死循环,不断的调用MessageQueue的next()方法获取Message对象,并调用Handler的dispatchMessage(msg)方法(msg.target指的就是Handler对象)
public class Handler { // Handler的构造函数有很多个,这里只展示其中的两个public Handler(@Nullable Callback callback, boolean async) { ... mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }@UnsupportedAppUsage public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; }// Handler的子类必须实现handleMessage方法 public void handleMessage(@NonNull Message msg) { }public void dispatchMessage(@NonNull Message msg) { ... handleMessage(msg); ... }/** * sendMessage/sendEmptyMessage/sendEmptyMessageDelayed/sendMessageDelayed/... * 如上所有的发送消息方法最终都执行的是sendMessageAtTime */ public boolean sendMessageAtTime(@NonNull 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); }private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) { msg.target = this; msg.workSourceUid = ThreadLocalWorkSource.getUid(); if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }}

从Handler源码可知:
  1. 我们在使用Handler的时候,一般是new一个Handler,然后重写handleMessage方法,该方法是由dispatchMessage来调用,而dispatchMessage是在Looper对象的loop()方法中的死循环中执行。
  2. sendMessage、sendEmptyMessage、sendMessageDelayed等发送消息的方法,实际上调用的是MessageQueue对象的enqueueMessage(msg, uptimeMillis)方法,将Message对象放入MessageQueue中
public final class MessageQueue { { // True if the message queue can be quit. @UnsupportedAppUsage private final boolean mQuitAllowed; @UnsupportedAppUsage Message mMessages; MessageQueue(boolean quitAllowed) { mQuitAllowed = quitAllowed; mPtr = nativeInit(); }@UnsupportedAppUsage Message next() { // Message是一个单链表结构,有一个属性next指向了后一个Message }boolean enqueueMessage(Message msg, long when) { synchronized (this) { // 时间参数when表示Message的优先级 // 1.根据时间参数when来判断当前Message(假设为curr_msg)要插入到哪一个Message(假设为pre_msg)的后面 // 2.如果pre_msg的next不为空,则用一个临时变量记录:temp_msg = pre_msg.next // 3.将pre_msg.next = curr_msg,然后将curr_msg.next = temp_msg } } void quit(boolean safe) { if (!mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit."); } ... } }

从MessageQueue的源码可知:
mQuitAllowed表示MessageQueue是否可以销毁。
我们在创建Looper时,一般调用的是Looper.prepare(),该方法最终调用的是Looper的构造方法,会将mQuitAllowed设置为true,表示可以销毁。
public final class ActivityThread extends ClientTransactionHandler { public static void main(String[] args) { ... Looper.prepareMainLooper(); ... Looper.loop(); ... } }

从ActivityThread源码可知:
启动一个app时,会在app的主线程创建一个Looper,而此时调用的是Looper.prepareMainLooper(),会将mQuitAllowed设置为false,表示不可以销毁。
public final class Message implements Parcelable { @UnsupportedAppUsage /*package*/ Handler target; @UnsupportedAppUsage /*package*/ Message next; /** @hide */ public static final Object sPoolSync = new Object(); private static Message sPool; private static int sPoolSize = 0; public void setTarget(Handler target) { this.target = target; }public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); } public static Message obtain(Message orig) { Message m = obtain(); m.what = orig.what; m.arg1 = orig.arg1; m.arg2 = orig.arg2; m.obj = orig.obj; m.replyTo = orig.replyTo; m.sendingUid = orig.sendingUid; m.workSourceUid = orig.workSourceUid; if (orig.data != null) { m.data = https://www.it610.com/article/new Bundle(orig.data); } m.target = orig.target; m.callback = orig.callback; return m; } }

从Message源码可知:
  1. 成员变量target是一个Handler对象
  2. 可以通过Message.obtain()来创建Message对象,这种方式可以复用已经创建过的Message,从而避免频繁的创建、销毁Message,达到优化内存和性能的目的。
1.3 Toast 不知道大家有没有在子线程中使用过Toast,如果有,那么你可能碰到过在子线程中直接调用Toast的makeText()方法会报错Can't toast on a thread that has not called Looper.prepare()
public class Toast {public Toast(Context context) { this(context, null); }public Toast(@NonNull Context context, @Nullable Looper looper) { ... looper = getLooper(looper); ... }private Looper getLooper(@Nullable Looper looper) { if (looper != null) { return looper; } return checkNotNull(Looper.myLooper(), "Can't toast on a thread that has not called Looper.prepare()"); } public static Toast makeText(Context context, CharSequence text, @Duration int duration) { return makeText(context, null, text, duration); }public static Toast makeText(@NonNull Context context, @Nullable Looper looper, @NonNull CharSequence text, @Duration int duration) { if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) { Toast result = new Toast(context, looper); result.mText = text; result.mDuration = duration; return result; } else { Toast result = new Toast(context, looper); View v = ToastPresenter.getTextToastView(context, text); result.mNextView = v; result.mDuration = duration; return result; } } }

从Toast源码可知:
  1. makeText()会调用Toast的构造函数,构造函数中调用getLooper(),此时looper为空,会执行checkNotNull(Looper.myLooper(), "...")方法,通过方法名可以直接得出该方法用于判空,为空则抛出异常。
  2. 从Looper的源码可知,Looper.myLooper()获取的是当前线程的Looper对象,而此时子线程的Looper对象为空,导致了异常。
子线程中使用Toast的方法:
第一步,调用Looper.prepare()给子线程创建一个Looper对象。
第二步,调用Toast.makeText(...).show()向子线程的MessageQueue插入Message
第三步,调用Looper.loop(),从MessageQueue中取出Message
1.4 ThreadHandler

1.5 同步屏障 同步Message和异步Message的区别:
  1. 同步(sync)就是一个一个的来,处理完一个再处理下一个;异步(async)就是可以同时处理多个,不需要等待。
  2. 同步Message,就是按照顺序执行的Message,默认情况下,我们通过Handler对象的sendMessage()方法发送的Message就是同步Message。
  3. 异步Message,类似于屏幕点击事件的Message,就是异步Message。
什么是同步屏障?
字面意思理解,就是设置了一道屏障,让同步Message不可以通过,从而优先处理异步Message
那么同步屏障如何设置?我们来看ViewRootImpl的源码:
public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks { ... @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); } }void unscheduleTraversals() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); mChoreographer.removeCallbacks( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); } } ... }

从ViewRootImpl的源码可知:
  1. scheduleTraversals()中调用了MessageQueue对象的postSyncBarrier()方法,设置了同步屏障,设置同步屏障的代码如下:
public final class MessageQueue { @UnsupportedAppUsage @TestApi public int postSyncBarrier() { return postSyncBarrier(SystemClock.uptimeMillis()); }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; } } }

【Android|Android Handler详解(附面试题)】如上代码可知,设置同步屏障就是在MessageQueue队列中新增了一个Message对象,并将该Message对象插入到队列的最前面。当我们调用Handler对象的sendMessage()方法时,会将Message对象的target属性设置为Handler对象,而此处postSyncBarrier()方法没有设置Message对象的target,说明target=null
  1. scheduleTraversals()中调用了mChoreographer.postCallback(),追踪源码可知调用的是Handler对象的sendMessageAtTime()发送Message,但是在发送Message之前执行了调用了Message对象的setAsynchronous(true),将Message设置为了异步消息
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime);

  1. unscheduleTraversals()中调用了MessageQueue对象的removeSyncBarrier()方法,移除了同步屏障
  2. unscheduleTraversals()中调用了mChoreographer.removeCallbacks(),追踪源码可知调用的是Handler对象的removeMessages(),就是将Message从MessageQueue中移除。
再来看MessageQueue的next()方法:
@UnsupportedAppUsage Message next() { ... synchronized (this) { // Try to retrieve the next message.Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; 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()); } } ... }

next()方法中的if语句中,判断了Message对象的target属性是否为空。如果为空,则说明MessageQueue队列中有需要优先处理的异步Message,此时执行do-while循环,去查找队列中的异步Message,找到异步Message之后返回给Looper。判断一个Message是否为异步Message:msg.isAsynchronous()
同步屏障总结:
  1. 要使用异步Message,需要发送两个Message。一个Message的target=null,用于告诉MessageQueue,当前队列中有异步消息;另一个Message才是真正的消息载体,并且要设置setAsynchronous(true),用于标记Message的异步属性。
  2. Looper的死循环中,会调用MessageQueue的next()方法从消息队列中获取消息,而 MessageQueue的next()方法会优先返回异步消息。
2.Handler常见面试题 2.1 一个线程有几个Handler? 答:一个线程可以有多个Handler,需要时,通过new Handler()的方式创建Handler
2.2 一个线程有几个Looper,如何保证? 答:一个线程只有一个Looper,从Looper的源码中可知,Looper的构造私有化了,需要通过Looper.prepare()创建Looper,创建的Looper会使用ThreadLocal存起来,再次调用Looper.prepare()会检查ThreadLocal中是否有Looper,如果有则抛出异常。
2.3 Handler内存泄漏的原因? 答:假设在Activity中使用匿名内部类的方式创建了一个Handler:
public class MainActivity extends Activity {private TextView mTextView; Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); // 等价于:MainActivity.this.mTextView.setText("hello") mTextView.setText("hello"); if (msg.what == 100) { // do something... } } }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); new Thread(){ @Override public void run() { super.run(); Message message = new Message(); message.what = 100; // 延迟一小时发送 mHandler.sendMessageDelayed(message, 1000 * 60 * 60); } }.start(); } }

结论:匿名内部类默认会持有外部类对象的引用,这也是为什么能直接调用mTextView.setTex()的原因
  1. 从Message的源码可知,Message有一个Handler类型的属性target,说明Message持有一个Handler
  2. 从上述结论可知Handler持有MainActivity对象
  3. 如果Message得不到释放,则Handler也得不到释放,那么MainActivity也得不到释放
上述代码中,将Message设置为延迟一小时后再发送,如果一小时内需要finish掉MainActivity跳转到其他Activity,则此时会发生内存泄漏,因为GC无法回收MainActivity。
可以使用静态内部类 + 弱引用的方式解决:
public class MainActivity extends Activity {private TextView mTextView; Handler mHandler; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mHandler = new MyHandler(this); new Thread(){ @Override public void run() { super.run(); Message message = new Message(); message.what = 100; // 延迟一小时发送 mHandler.sendMessageDelayed(message, 1000 * 60 * 60); } }.start(); }private static class MyHandler extends Handler {private WeakReference mMainReference; public MyHandler(MainActivity mainActivity) { mMainReference = new WeakReference(mainActivity); }@Override public void handleMessage(Message msg) { MainActivity mainActivity = mMainReference.get(); mainActivity.mTextView.setText("hello"); if (msg.what == 100) { // do something... } } } }

2.4 为何主线程中可以new Handler,在子线程中new Handler要做什么准备? 答:先调用Looper.prepare(),再调用Looper.loop()
2.5 子线程中维护的Looper,当MessageQueue中无消息时,该如何处理? 答:调用Looper对象的quit()方法,最终调用的是MessageQueue的quit()方法
2.6 不同线程的Handler往MessageQueue中添加Message,Handler内部如何保证线程安全? 答:
  1. 从Looper的源码可知,MessageQueue对象在Looper的构造函数中创建。即每一个线程对应一个MessageQueue对象
  2. 从MessageQueue的enqueueMessage(...)方法可知,该方法中的synchronized(this)持有的是this对象锁,当前MessageQueue对象其他使用synchronized(this)的地方都会等待
以上两点,保证了线程安全。
2.7 使用Message时应该如何创建它? 答:从Message源码可知,应该使用Message.obtain()来创建Message对象
2.8 为什么主线程不会因为 Looper.loop() 里的死循环卡死? 答:
  1. ActivityThread是应用程序的入口,该类中的main函数是整个Java程序的入口,main函数的主要作用就是做消息循环,一旦main函数执行完毕,循环结束,那么应用也就可以退出了。
  2. ActivityThread的main函数中调用了Looper.loop()开启了一个死循环,loop()方法中调用了MessageQueue的next()方法,该方法是一个阻塞方法(这里涉及到Linux中的pipe,不做赘述),如果MessageQueue中没有Message,则会等待。
  3. loop()的阻塞,是指MessageQueue中没有Message,此时释放CPU执行权,等待唤醒。
  4. ANR:Application Not Responding。导致ANR的情况比如主线程中访问网络、操作数据库等耗时操作,此时也会往MessageQueue中发送Message,然后loop()循环中获取到了Message,但是在处理Message时耗时过长,导致短时间内无法响应屏幕点击事件等操作(屏幕被点击也会发送Message到MessageQueue中),然后就出现了ANR。

    推荐阅读