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源码中得知:
- 每一个Thread都有一个ThreadLocalMap类型的成员变量:threadLocals
- 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源码中得知:
- ThreadLocal并不是一个Thread,只不过Thread使用了ThreadLocal中定义的ThreadLocalMap。
- 由createMap方法可知,ThreadLocal负责创建当前线程对应Thread对象的ThreadLocalMap
- getMap方法获取的是当前线程对应Thread对象的threadLocals属性
- set方法是将某一个对象添加到当前线程对应Thread对象的threadLocals中,get方法同理。
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源码中得知:
- Looper的构造函数私有化,不能通过new创建Looper对象,需要使用
Looper.prepare()
来创建Looper对象 - 从
sThreadLocal.set(new Looper(quitAllowed));
得知可以在当前线程的其他地方使用sThreadLocal.get()
获取Looper对象 -
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源码可知:
- 我们在使用Handler的时候,一般是new一个Handler,然后重写
handleMessage
方法,该方法是由dispatchMessage
来调用,而dispatchMessage
是在Looper对象的loop()
方法中的死循环中执行。 -
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源码可知:
- 成员变量target是一个Handler对象
- 可以通过
Message.obtain()
来创建Message对象,这种方式可以复用已经创建过的Message,从而避免频繁的创建、销毁Message,达到优化内存和性能的目的。
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源码可知:
-
makeText()
会调用Toast的构造函数,构造函数中调用getLooper()
,此时looper为空,会执行checkNotNull(Looper.myLooper(), "...")
方法,通过方法名可以直接得出该方法用于判空,为空则抛出异常。 - 从Looper的源码可知,
Looper.myLooper()
获取的是当前线程的Looper对象,而此时子线程的Looper对象为空,导致了异常。
第一步,调用
Looper.prepare()
给子线程创建一个Looper对象。第二步,调用
Toast.makeText(...).show()
向子线程的MessageQueue插入Message第三步,调用
Looper.loop()
,从MessageQueue中取出Message1.4 ThreadHandler
1.5 同步屏障 同步Message和异步Message的区别:
- 同步(sync)就是一个一个的来,处理完一个再处理下一个;异步(async)就是可以同时处理多个,不需要等待。
- 同步Message,就是按照顺序执行的Message,默认情况下,我们通过Handler对象的sendMessage()方法发送的Message就是同步Message。
- 异步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的源码可知:
-
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
-
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);
-
unscheduleTraversals()
中调用了MessageQueue对象的removeSyncBarrier()
方法,移除了同步屏障 -
unscheduleTraversals()
中调用了mChoreographer.removeCallbacks()
,追踪源码可知调用的是Handler对象的removeMessages()
,就是将Message从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()
同步屏障总结:
- 要使用异步Message,需要发送两个Message。一个Message的
target=null
,用于告诉MessageQueue,当前队列中有异步消息;另一个Message才是真正的消息载体,并且要设置setAsynchronous(true)
,用于标记Message的异步属性。 - Looper的死循环中,会调用MessageQueue的
next()
方法从消息队列中获取消息,而 MessageQueue的next()
方法会优先返回异步消息。
new Handler()
的方式创建Handler2.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()
的原因- 从Message的源码可知,Message有一个Handler类型的属性target,说明Message持有一个Handler
- 从上述结论可知Handler持有MainActivity对象
- 如果Message得不到释放,则Handler也得不到释放,那么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内部如何保证线程安全? 答:
- 从Looper的源码可知,MessageQueue对象在Looper的构造函数中创建。即每一个线程对应一个MessageQueue对象
- 从MessageQueue的
enqueueMessage(...)
方法可知,该方法中的synchronized(this)
持有的是this对象锁,当前MessageQueue对象其他使用synchronized(this)
的地方都会等待
2.7 使用Message时应该如何创建它? 答:从Message源码可知,应该使用
Message.obtain()
来创建Message对象2.8 为什么主线程不会因为 Looper.loop() 里的死循环卡死? 答:
- ActivityThread是应用程序的入口,该类中的
main
函数是整个Java程序的入口,main
函数的主要作用就是做消息循环,一旦main
函数执行完毕,循环结束,那么应用也就可以退出了。 - ActivityThread的
main
函数中调用了Looper.loop()
开启了一个死循环,loop()
方法中调用了MessageQueue的next()
方法,该方法是一个阻塞方法(这里涉及到Linux中的pipe,不做赘述),如果MessageQueue中没有Message,则会等待。 -
loop()
的阻塞,是指MessageQueue中没有Message,此时释放CPU执行权,等待唤醒。 - ANR:Application Not Responding。导致ANR的情况比如主线程中访问网络、操作数据库等耗时操作,此时也会往MessageQueue中发送Message,然后
loop()
循环中获取到了Message,但是在处理Message时耗时过长,导致短时间内无法响应屏幕点击事件等操作(屏幕被点击也会发送Message到MessageQueue中),然后就出现了ANR。
推荐阅读
- android第三方框架(五)ButterKnife
- Android中的AES加密-下
- 带有Hilt的Android上的依赖注入
- Java|Java OpenCV图像处理之SIFT角点检测详解
- C语言浮点函数中的modf和fmod详解
- android|android studio中ndk的使用
- Android事件传递源码分析
- RxJava|RxJava 在Android项目中的使用(一)
- Android7.0|Android7.0 第三方应用无法访问私有库
- 深入理解|深入理解 Android 9.0 Crash 机制(二)