final Handler handler = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
/**
耗时操作
*/
handler.post(new Runnable() {
@Override
public void run() {
/**
更新UI
*/
}
});
}
}).start();
//1,创建Handler(主线程)
private Handler handler = new Handler() {
@Override
//3,处理消息
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
Toast.makeText(MainActivity.this, msg.obj.toString(), Toast.LENGTH_LONG).show();
break;
}
}
//2,发送消息(子线程)
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(2000);
Message msg = new Message();
msg.what = 1;
msg.obj = "我是子线程发来的消息";
handler.sendMessage(msg);
}
}).start();
【 Handler.java】
final MessageQueue mQueue;
final Looper mLooper;
final Callback mCallback;
/**
* Default constructor associates this handler with the queue for the
* current thread.
*
* If there isn't one, this handler won't be able to receive messages.
*/
public Handler() {
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();//核心代码。获取一个Looper
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;//核心代码。从Looper获取一个消息队列
mCallback = null;
}
在源码中,Handler定义了一个Looper对象mLooper和一个MessageQueue消息队列mQueue,并都进行了初始化,分别对mQueue和mLooper进行了赋值,其中mLooper是通过Looper.myLooper()赋值,mQueues是Looper中的mQueue。
让我们进入Looper类看看
【Looper.java】
public class Looper {
private static final String TAG = "Looper";
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class
final MessageQueue mQueue;
final Thread mThread;
volatile boolean mRun;
private Printer mLogging;
public static void prepare() {
prepare(true);
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mRun = true;
mThread = Thread.currentThread();
}
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));//设置myLooper
}
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
public static void loop() {
......
}
public static Looper myLooper() {
return sThreadLocal.get();
}
......
}
有两个方法prepare()和prepareMainLooper()对Looper进行了初始化。prepare()创建的Looper为允许退出循环的,而prepareMainLooper()方法创建的是不应许退出循环的,通过分析,很明显知道prepare()方法创建的是一般线程的Looper,而通过而prepareMainLooper()创建的,就是主线程消息循环的Looper。
【Handler.java】
......
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) { //delayMillis < 0判断是为了防止用户传入的延迟参数为负数。
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
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);
}
....
我们代码中调用sendMessage()方法,最终就是走的sendMessageAtTime()方法。而且其他发送消息的方法除了sendMessageAtFrontOfQueue(),例如sendMessageDelayed(),sendEmptyMessageDelayed()最终都会走sendMessageAtTime()方法,这个方法主要是赋值MessageQueue,调用enqueueMessage方法
Handler发送主体为Message,Message是啥呢?Message主要就是对一些数据做封装处理,其中有int变量what,arg1,arg2,Object变量obj等,具体可以查看Message源码,这里就不详细说了。
【Handler.java】
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; //将当前的Handler绑定给msg.target
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
enqueueMessage()方法中,首先将当前的Handler绑定给msg.target,接着调用MessageQueue的enqueueMessage()方法,MessageQueue的enqueueMessage()方法则是消息入队的方法。
【MessageQueue.java】
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {//msg.target就是发送此消息的Handler
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;//将延迟时间封装到msg内部
Message p = mMessages;//消息队列的第一个元素
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//如果此队列中头部元素是null(空的队列,一般是第一次),或者此消息不是延时的消息,则此消息需要被立即处理,此时会将这个消息作为新的头部元素,并将此消息的next指向旧的头部元素,然后判断如果Looper获取消息的线程如果是阻塞状态则唤醒它,让它立刻去拿消息处理
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//如果此消息是延时的消息,则将其添加到队列中,原理就是链表的添加新元素,按照when,也就是延迟的时间来插入的,延迟的时间越长,越靠后,这样就得到一条有序的延时消息链表,取出消息的时候,延迟时间越小的,就被先获取了。插入延时消息不需要唤醒Looper线程。
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;
}
MessageQueue中enqueueMessage方法的目的有两个:
【MessageQueue.java】
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
//从注释可以看出,只有looper被放弃的时候(调用了quit方法)才返回null,mPtr是MessageQueue的一个long型成员变量,关联的是一个在C++层的MessageQueue,阻塞操作就是通过底层的这个MessageQueue来操作的;当队列被放弃的时候其变为0。
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//阻塞方法,主要是通过native层的epoll监听文件描述符的写入事件来实现的。
//如果nextPollTimeoutMillis=-1,一直阻塞不会超时。
//如果nextPollTimeoutMillis=0,不会阻塞,立即返回。
//如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
//msg.target == null表示此消息为消息屏障(通过postSyncBarrier方法发送来的)
//如果发现了一个消息屏障,会循环找出第一个异步消息(如果有异步消息的话),所有同步消息都将忽略(平常发送的一般都是同步消息)
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 如果消息此刻还没有到时间,设置一下阻塞时间nextPollTimeoutMillis,进入下次循环的时候会调用nativePollOnce(ptr, nextPollTimeoutMillis)进行阻塞;
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
//正常取出消息
//设置mBlocked = false代表目前没有阻塞
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
//没有消息,会一直阻塞,直到被唤醒
nextPollTimeoutMillis = -1;
}
if (mQuitting) {
dispose();
return null;
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
由此可以看出:
通过enqueueMessage和next两个方法的分析我们不难得出:
上面说到,Looper的建立有两种方式prepare()和prepareMainLooper(),其中prepare建立的为一般子线程Looper,可以取消循环;而prepareMainLooper()建立的为主线程的Looper,不可以取消循环。到底而prepareMainLooper建立的是不是主线程循环呢?让我们继续分析
主线程即UI线程,说到UI线程,我们知道应用程序一启动,主(UI)线程就开始启动,而线程的建立必须要在进程的基础上。**通过对Android应用程序启动的分析,我们知道,应用程序启动,首先会通过Zygote复制自身fork出一个进程,然后再由进程创建一个主线程,主线程的建立和ActivityThread息息相关,通过分析,知ActivityThread的main方法就是应用程序启动的入口。
让我们来看一下ActivityThread类的main方法:
public static void main(String[] args) {
SamplingProfilerIntegration.start();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Process.setArgV0("" );
Looper.prepareMainLooper();//1.主线程Looper创建
if (sMainThreadHandler == null) {
sMainThreadHandler = new Handler();
}
ActivityThread thread = new ActivityThread();
thread.attach(false);
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();//2.主线程Looper循环
throw new RuntimeException("Main thread loop unexpectedly exited");
}
从源码知道,正如我们想的那样prepareMainLooper()建立的Looper就是主线程的Looper。
从上面ActivityThread的main方法中,我们发现Looper.loop()消息循环方法。Looper是怎么循环的,这里让我们来看一下Looper.loop()
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {//for循环
Message msg = queue.next(); //从消息队列中取值
if (msg == null) {//消息为空就返回
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);//分发消息
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycle();
}
}
代码第6行,从轮询器中获取消息队列。接着通过一个死循环来把消息队列中的消息逐个取出来。代码第18行,通过MessageQueue的next()方法取出消息,当queue.next返回null时会退出消息循环。有消息则调用msg.target.dispatchMessage(msg),target就是发送message时跟message关联的handler,Message被处理后会被调用recycleUnchecked()进行回收。
【Handler.java】
public void dispatchMessage(Message msg) {
//若msg.callback属性不为空,则代表使用了post(Runnable r)方式发送消息;则执行handleCallback(msg)()->> 分析2
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//若msg.callback属性为空,则代表使用了sendMessage(Message msg)发送消息,则执行handleMessage(msg),即回调复写的handleMessage(msg)
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
// Message对象的callback属性 = 传入的Runnable对象; 即回调post(Runnable r),Runnable对象里复写的run()
message.callback.run();
}
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。 一般来说,当某些数据是以线程为 作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。
对于Handler来说, 它需要获取当前线程的Looper,很显然Looper的作用域就是线程并且不同线程具有不同的Looper,这个时候通过ThreadLocal就可以轻松实现Looper在线程中的存取。如果不采用ThreadLocal,那么系统就必须提供一个全局的哈希表供Handler查找指定线程的Looper,这样一来就必须提供一个类似于LooperManager的类了,但是系统并没有这么做而是选择了ThreadLocal,这就是ThreadLocal的好处。
实际的例子来演示ThreadLocal的真正含义。首先定义一个ThreadLocal对象,这里选择Boolean类型的,如下所示。
private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<Boolean>();
然后分别在主线程、子线程1和子线程2中设置和访问它的值,代码如下所示。
mBooleanThreadLocal.set(true);
Log.d(TAG,"[Thread#main]mBooleanThreadLocal=" + mBooleanThreadLocal.get()); //输出 true
new Thread("Thread#1") {
@Override
public void run() {
mBooleanThreadLocal.set(false);
Log.d(TAG,"[Thread#1]mBooleanThreadLocal=" + mBooleanThreadLocal.get()); //输出 flase
};
}.start();
new Thread("Thread#2") {
@Override
public void run() {
Log.d(TAG,"[Thread#2]mBooleanThreadLocal=" + mBooleanThreadLocal.get()); // 由于没有设置值,所以应该是null
};
}.start();
ThreadLocal之所以有这么奇妙的效果,是因为不同线程访问同一个ThreadLocal的get方法,ThreadLocal内部会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找出对应的value值。很显然,不同线程中的数组是不同的,这就是为什么通过ThreadLocal可以在不同的线程中维护一套数
据的副本并且彼此互不干扰。
前面我们分析了如果拿到的消息还没有到时间,则会重新设置超时时间并赋值给nextPollTimeoutMillis,然后调用nativePollOnce(ptr, nextPollTimeoutMillis)进行阻塞,这是一个本地方法,会调用底层C++代码,C++代码最终会通过Linux的epoll监听文件描述符的写入事件来实现延迟的。
首先我们来看造成ANR的原因:
我们再来看一下APP的入口ActivityThread的main方法:
public static void main(String[] args) {
...
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
显而易见的,如果main方法中没有looper进行死循环,那么主线程一运行完毕就会退出,会导致直接崩溃,还玩什么!
现在我们知道了消息循环的必要性,那为什么这个死循环不会造成ANR异常呢?
我们知道Android 的是由事件驱动的,looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在 Looper的控制之下,如果它停止了,应用也就停止了。只能是某一个消息或者说对消息的处理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它,这也就是我们为什么不能在UI线程中处理耗时操作的原因。
主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,唤醒主线程,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。
android系统当中的控件都是线程不安全的,这将导致在多线程模式下,当多个线程共同访问更新操作同一个UI控件时容易发生不可控的错误,而这是致命的。所以Android中规定只能在UI线程中访问UI,这相当于从另一个角度给Android的UI访问加上锁,一个伪锁。
//直接new 不推荐
1.Message message = new Message();
//从全局消息池取出一个空闲Message供开发者使用,推荐
2.Message message = Message.obtain();
//从全局消息池获取,与handler关联,最终会调用2方法推荐使用
3.Message message = handler.obtainMessage();
简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
深入理解MessageQueue
关于Handler同步屏障你可能不知道的问题
Handler发送消息的方式
都 2021 年了,还有人在研究 Handler