前言
1. 为什么要用Handler
我们的都知道,在Android中我们无法在子线程中去更新UI,为什么不能更新呢?我们可以在源码中可以看到
void checkThread() {
...
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
我们如果同时多个线程都需要去更新UI,那么绘制UI就是一个不可靠的非安全操作。如果选择通过加锁的方式,UI绘制的效率就会变低,影响到界面的显示。所以谷歌就限制了只能主线程才能更新UI,通过handler去更新UI。
概念
1.什么是Handler
handler是通过消息队列,发送传递和处理消息的工具。 主要用于异步消息的处理:当发出一个消息之后,首先进入一个消息队列,发送消息的函数即刻返回,而另外一个部分在消息队列中逐一将消息取出,然后对消息进行处理,也就是发送消息和接收消息不是同步的处理。 这种机制通常用来处理相对耗时比较长的操作。
说白了,handler就是个运输消息的工具,将消息发送到需要处理的线程中。
2.什么是MessageQueque
MessageQueue字面意思是消息队列。 队列是一种数据结构,有着先进先出的特点。其特性很适用于像消息生产消费依次顺序的场景。
3.Message是什么
Message就是handler发送与接收消息的实体,承载了数据。
4.Looper又是啥
Looper字面意思是循环的意思,在Android中它就是一个不断循环的线程,消息在这个线程中存储和发送。
原理
1. UI线程是如何创建Looper的
Looper的创建一般是Looper.prepare(),但是像Activity中我们一直没有看到创建的方法就可以直接使用Handler发送消息了,原因是系统已经在ActivityThread的main方法为我们创建Looper了。
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
...
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
2. Handler是如何发送消息的
我们直接看sendMessage源码,从sendMessage开始,依次调用sendMessageDelayed,sendMessageAtTime,再到队列的enqueueMessage方法。
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
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);
}
最后看队列Queue中的enqueue方法
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
...
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
...
}
}
return true;
}
这个过程就是入队,将消息根据时间顺序指定下一个消息。
出队的过程还是看源码,在Looper的loop循环中,最终找到dispatchMessage方法。
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
3. Looper没有消息死循环的时候,为什么不会卡死应用
当主线程的MessageQueue没有消息的时候,代码会阻塞在Looper的mQueue的next 方法那里,这个时候主线程会释放cpu资源,进入睡眠模式,直到下个消息到达会再次唤醒主线程。这套机制采用的是linux的pipe/epoll机制。通过往pipe管道中写入数据来唤醒主线程工作。原理类似于I/O,读写是阻塞的,不占用CPU资源。
4. 一个线程有几个handler,又有几个looper
一个线程可以有N个handler,因为可以不断的new出来新的。 一个线程中只有一个Looper。
Looper类创建时在源码中对内存中的Looper进行非空判断,仅限制一个Looper存在。
public static void prepare() {
prepare(true);
}
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的,在Looper这个类创建的时候就new出来了。ThreadLocal 提供了针对于单独的线程的局部变量,并能够使用 set()、get() 方法对这些变量进行设置和获取,并且能够保证这些变量与其他线程相隔离。通过使用 threadLocal 存储对象,线程和线程之间的彼此的数据就会隔离起来,从而保证了彼此线程的数据安全和独立性。
5. handler线程是如何切换的
线程之间的资源是共享的,也就是任何变量在任何线程都是可以修改的。切换线程的过程就是整个handler的运行过程。 总结来说就是三个步骤 a. 创建一个Looper对象保存在ThreadLocal中,同时持有一个MessageQueue对象 b. 创建Handler对象,获取到Looper和MessageQueue对象。在sendMessage的时候,在不同的线程(子线程)将消息发送到MessageQueue。 c. 主线程(UI线程)通过Looper.loop()无限循环获取MessageQueue中的消息,如果存在就将消息通过dispatchMessage方法最终调用到我们写的handleMessage方法中。这样一整个线程切换的过程就完成了。
6. 为什么handler会有内存泄漏的风险,如何规避
先看张截图,我们一般在Acitivty中new Handler的时候,会出现一个黄色的背景提示。 大致翻译过来就是handler是内部类,会引起内存泄漏。如果是handler的MessagQueue和Looper是子线程就没问题。主线程的话需要定义成静态内部类和使用弱引用。 那么为什么要这样使用呢,原因就是如果handler在主线程创建,如果发送的消息还在队列中,若此时Activity突然finish了,但是handler中的消息依然需要处理。最关键的就是非静态内部类天然持有外部类的引用,导致activity无法回收,引起内存泄漏。
定义成静态内部类,就不会持有外部类的引用,所以也不会有问题。 除此之外,由于是静态类,在handler中就无法操作activity对象,所以需要在handler中增加一个activty的弱引用。
static class MyHandler extends Handler {
WeakReference<Activity > mActivityReference;
MyHandler(Activity activity) {
mActivityReference= new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
final Activity activity = mActivityReference.get();
if (activity != null) {
text.setText(result);
}
}
}
7.如果队列中已存在消息,但需要一些消息紧急提前,该怎么做
这里需要引出一个概念叫同步屏障。线程的消息都是放到同一个MessageQueue中的,取消息是互斥取消息,而且只能从头部取消息,添加消息是按照消息的执行的先后顺序进行的排序,同一时间内的消息如果想要立马执行,就需要一个紧急通道,哪个通道就是同步屏障的概念。 同步屏障顾名思义就是阻碍同步消息,只让异步消息进入。 开启同步屏障的方法: MessageQueue#postSyncBarrier()
未完待续
|