Android中Handler的消息处理机制以及源码分析
在实际项目当中,一个很常见的需求场景就是在根据子线程当中的数据去更新ui。我们知道,android中ui是单线程模型的,就是只能在UI线程(也称为主线程)中更新ui。而一些耗时操作,比如数据库,网络请求,I/O等都是在其他线程中进行的,那么此时就需要在不同线程中进行通信了,而我们最常用的方式就是Handler。
常见的使用方式如下:
1 public class HandlerTestActivity extends AppCompatActivity { 2 public static final String TAG = "HandlerTestActivity"; 3 private TextView displayTv; 4 5 private Handler mHandler = new Handler(){ 6 @Override 7 public void handleMessage(Message msg) { 8 if (msg.what == 42){ 9 displayTv.setText("" + msg.arg1); 10 } 11 } 12 }; 13 14 @Override 15 protected void onCreate(Bundle savedInstanceState) { 16 super.onCreate(savedInstanceState); 17 setContentView(R.layout.activity_handler_test); 18 displayTv = (TextView)findViewById(R.id.handlerDisplayTv); 19 20 new Thread(new Runnable() { 21 @Override 22 public void run() { 23 int count = 0; 24 25 while (true) { 26 27 try { 28 //模拟耗时操作 29 TimeUnit.MILLISECONDS.sleep(1000); 30 }catch (InterruptedException e){ 31 } 32 33 Message msg = new Message(); 34 msg.what = 42; 35 msg.arg1 = count++; 36 mHandler.sendMessage(msg); 37 38 } 39 } 40 }).start(); 41 } 42 }
实际的效果图:
其实也很简单,就是 每间隔1秒钟,屏幕上的textview显示的数字增加1。
总结一下,Handler,Looper,MessageQueue,Message这些组成了android当中的异步消息处理机制。主要用于不同的线程之间通讯。(虽然我们最常用到的场景就是 发送消息来更新ui)。
而在android的官方sdk文档中,给出的官方demo其实是这样的。
1 class LooperThread extends Thread { 2 public Handler mHandler; 3 4 public void run() { 5 Looper.prepare(); 6 7 mHandler = new Handler() { 8 public void handleMessage(Message msg) { 9 // process incoming messages here 10 } 11 }; 12 13 Looper.loop(); 14 } 15 }
它的基本原理如下。
1,在某个线程中。首先调用Looper.prepare()方法,来准备一个Looper实例,绑定到了该线程,并维护了一个消息队列(MessageQueue),MessageQueue它里面装的内容就是Message,然后调用 Looper.loop(),这是一个无限循环,它来取出消息队列当中的Message送到Handler进行处理。
2,我们new Handler()过程中,来获得到该线程的Looper。然后在其他线程当中,可以调用Handler实例来发送消息(sendMessage方法等),发送的message被放到了MessageQueue中,然后在Handler所在的线程中,Handler实例再通过handleMessage()等方法来处理 这个message,这样就实现了不同线程当中的通信。
所以问题的关键是 一个Handler实例所在的线程只有唯一的一个Looper来维持无限循环,并且它也维护了唯一的一个消息队列(MessageQueue)来传送Message(导入和取出)。
其实这个原理还是不复杂的,随便一本android入门书上也都会讲到这个问题,(并且肯定比我总结的要通俗易懂和严谨)。不过作为一个程序猿,不仅要知其然,更要知其所以然。深层次的理解了它的原理,不仅便于我们的记忆,也可以让我们使用起来更加得心应手。
所以下面,我就从源码的角度来分析这一过程,从子线程当中的sendMessage()开始,然后又怎么在主线程当中handleMessage()进行接收处理的。
首先来看 Handler.java
1 /** 2 * Default constructor associates this handler with the {@link Looper} for the 3 * current thread. 4 * 5 * If this thread does not have a looper, this handler won't be able to receive messages 6 * so an exception is thrown. 7 */ 8 public Handler() { 9 this(null, false); 10 } 11 12 public Handler(Callback callback, boolean async) { 13 if (FIND_POTENTIAL_LEAKS) { 14 final Class<? extends Handler> klass = getClass(); 15 if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && 16 (klass.getModifiers() & Modifier.STATIC) == 0) { 17 Log.w(TAG, "The following Handler class should be static or leaks might occur: " + 18 klass.getCanonicalName()); 19 } 20 } 21 22 mLooper = Looper.myLooper(); 23 if (mLooper == null) { 24 throw new RuntimeException( 25 "Can't create handler inside thread that has not called Looper.prepare()"); 26 } 27 mQueue = mLooper.mQueue; 28 mCallback = callback; 29 mAsynchronous = async; 30 }
当然,Handler的构造方法有好几个。不过我们先沿着主线去分析。
在这个构造方法中,最重要的是这两句。
1 mLooper = Looper.myLooper(); 2 mQueue = mLooper.mQueue;
这两句话,说明我们在new Handler时,该线程必须准备好了一个 Looper以及MessageQueue。
然后我们再来看一下Looper.java当中的代码。
1 // sThreadLocal.get() will return null unless you've called prepare(). 2 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); 3 4 final MessageQueue mQueue; 5 final Thread mThread; 6 7 private Looper(boolean quitAllowed) { 8 mQueue = new MessageQueue(quitAllowed); 9 mThread = Thread.currentThread(); 10 } 11 12 public static @Nullable Looper myLooper() { 13 return sThreadLocal.get(); 14 } 15 16 private static void prepare(boolean quitAllowed) { 17 if (sThreadLocal.get() != null) { 18 throw new RuntimeException("Only one Looper may be created per thread"); 19 } 20 sThreadLocal.set(new Looper(quitAllowed)); 21 }
首先代码中可以得知 Looper的唯一一个构造方法是private的,所以我们要想得到Looper,那就只能通过prepare()方法了,而在prepare()方法中,注意那句“throw new RuntimeException("Only one Looper may be created per thread")”,这就保证了我们的Looper是唯一的,而通过Handler的构造方法的 “throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()")”。我们也能保证在使用 Handler之前,必须已经调用了 Looper.prepare()来初始化这个唯一的Looper。
而关于这个存储Looper的ThreadLocal对象,其实是一个线程内部的数据存储类,它最大的特点是作用域是限定在该线程内的,也就是说,当它存储数据后(set方法)。只能在该线程内才能获取到数据。 其他线程调用get方法得到的其实会是null(也就是无法得到对应数据)。当然,它内部有一套相应的机制和算法来保证它的作用域是线程内的。
那么从这些代码当中,我们已经明白了,当我们在new Handler()时,必须已经 初始化了唯一的一个Looper和MessageQueue了。
这里插一个问题。我们一直在口口声声的说,在new Handler之前,我们要调用 Looper.prepare()来执行必要的初始化,可是在我们的第一个HandlerTestActivity demo中,根本没调用 这个方法啊。这个原因是因为在UI线程启动时,在ActivityThread中已经帮我们调用过 Looper.prepareMainLooper()方法了(ActivityThread也调用了Looper.loop())。
1 /** 2 * Initialize the current thread as a looper, marking it as an 3 * application's main looper. The main looper for your application 4 * is created by the Android environment, so you should never need 5 * to call this function yourself. See also: {@link #prepare()} 6 */ 7 public static void prepareMainLooper() { 8 prepare(false); 9 synchronized (Looper.class) { 10 if (sMainLooper != null) { 11 throw new IllegalStateException("The main Looper has already been prepared."); 12 } 13 sMainLooper = myLooper(); 14 } 15 }
所以我们在 HandlerTestActivity中,不需要调用Looper.prepare()方法了。而在官方的LooperThread demo中,因为不是在ui线程。那就只要你自己来调用 Looper.prepare()和Looper.loop()了.
既然Handler,Looper和MessageQueue我们已经都得到实例对象了,那么下面看看它的发送消息的过程。
1 /** 2 * Pushes a message onto the end of the message queue after all pending messages 3 * before the current time. It will be received in {@link #handleMessage}, 4 * in the thread attached to this handler. 5 * 6 */ 7 public final boolean sendMessage(Message msg) 8 { 9 return sendMessageDelayed(msg, 0); 10 } 11 12 /** 13 * Enqueue a message into the message queue after all pending messages 14 * before (current time + delayMillis). You will receive it in 15 * {@link #handleMessage}, in the thread attached to this handler. 16 */ 17 public final boolean sendMessageDelayed(Message msg, long delayMillis) 18 { 19 if (delayMillis < 0) { 20 delayMillis = 0; 21 } 22 return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); 23 } 24 25 /** 26 * Enqueue a message into the message queue after all pending messages 27 * before the absolute time (in milliseconds) <var>uptimeMillis</var>. 28 * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b> 29 * Time spent in deep sleep will add an additional delay to execution. 30 * You will receive it in {@link #handleMessage}, in the thread attached 31 * to this handler. 32 * 33 34 */ 35 public boolean sendMessageAtTime(Message msg, long uptimeMillis) { 36 MessageQueue queue = mQueue; 37 if (queue == null) { 38 RuntimeException e = new RuntimeException( 39 this + " sendMessageAtTime() called with no mQueue"); 40 Log.w("Looper", e.getMessage(), e); 41 return false; 42 } 43 return enqueueMessage(queue, msg, uptimeMillis); 44 }
通过代码可以看出,几经周折,其实调用的是 enqueueMessage(queue, msg, uptimeMillis)方法,这个方法就是重中之重了。(Handler的sendMessage的方法有几个,但是归根到底,最后还是 调用enqueueMessage方法来进行入队操作)。
1 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { 2 msg.target = this; 3 if (mAsynchronous) { 4 msg.setAsynchronous(true); 5 } 6 return queue.enqueueMessage(msg, uptimeMillis); 7 }
通过代码可以看出来,Looper的enqueueMessage方法最后调用的还是 queue.enqueueMessage方法,也就是MessageQueue本身来进行入队操作。不过在这个方法中,我们还有一句要注意的代码,那就是
msg.target = this;通过这句代码,来把message的 target对象来变成 Handler本身了。这句代码在后面的处理流程中,是有重要作用的。
那么现在,我们就看看 MessageQueue的 enqueueMessage()方法。
1 boolean enqueueMessage(Message msg, long when) { 2 if (msg.target == null) { 3 throw new IllegalArgumentException("Message must have a target."); 4 } 5 if (msg.isInUse()) { 6 throw new IllegalStateException(msg + " This message is already in use."); 7 } 8 9 synchronized (this) { 10 if (mQuitting) { 11 IllegalStateException e = new IllegalStateException( 12 msg.target + " sending message to a Handler on a dead thread"); 13 Log.w(TAG, e.getMessage(), e); 14 msg.recycle(); 15 return false; 16 } 17 18 msg.markInUse(); 19 msg.when = when; 20 Message p = mMessages; 21 boolean needWake; 22 if (p == null || when == 0 || when < p.when) { 23 // New head, wake up the event queue if blocked. 24 msg.next = p; 25 mMessages = msg; 26 needWake = mBlocked; 27 } else { 28 // Inserted within the middle of the queue. Usually we don't have to wake 29 // up the event queue unless there is a barrier at the head of the queue 30 // and the message is the earliest asynchronous message in the queue. 31 needWake = mBlocked && p.target == null && msg.isAsynchronous(); 32 Message prev; 33 for (;;) { 34 prev = p; 35 p = p.next; 36 if (p == null || when < p.when) { 37 break; 38 } 39 if (needWake && p.isAsynchronous()) { 40 needWake = false; 41 } 42 } 43 msg.next = p; // invariant: p == prev.next 44 prev.next = msg; 45 } 46 47 // We can assume mPtr != 0 because mQuitting is false. 48 if (needWake) { 49 nativeWake(mPtr); 50 } 51 } 52 return true; 53 }
这个方法内容比较多。但是主要作用就是一个,把message按照时间戳的顺序,插入到这个队列中,值得注意的是,MessageQueue队列的方式是通过链表来实现的。
至于为什么插入的顺序是按照时间来的,因为我们在使用Handler来发送message时,有几个方法本来就是有个延迟时间的。(比如sendMessageDelayed(Message, long)等,默认的发送时间就是当前时间),并且在Message对象中,也有when属性来保存这个时间的。
不过这里的这个时间戳取的是 手机自开机之后的时间,而不是我们经常说的linux那个源自1970年的那个时间。
我们的代码看到这里,其实就应该告一段落了,那就是 我们通过Handler来发送的Message,已经被送入到MessageQuequ了。消息已经在消息队列了。那么怎么取出来进行处理呢。
我们在最初分析原理的时候就说过,Looper.loop()中维持了一个无限循环,
看一下代码:
1 /** 2 * Run the message queue in this thread. Be sure to call 3 * {@link #quit()} to end the loop. 4 */ 5 public static void loop() { 6 final Looper me = myLooper(); 7 if (me == null) { 8 throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); 9 } 10 final MessageQueue queue = me.mQueue; 11 12 // Make sure the identity of this thread is that of the local process, 13 // and keep track of what that identity token actually is. 14 Binder.clearCallingIdentity(); 15 final long ident = Binder.clearCallingIdentity(); 16 17 for (;;) { 18 Message msg = queue.next(); // might block 19 if (msg == null) { 20 // No message indicates that the message queue is quitting. 21 return; 22 } 23 24 //省略一些其他代码 25 try { 26 msg.target.dispatchMessage(msg); 27 } finally { 28 if (traceTag != 0) { 29 Trace.traceEnd(traceTag); 30 } 31 } 32 33 if (logging != null) { 34 logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); 35 } 36 37 //省略一些其他代码 38 39 msg.recycleUnchecked(); 40 } 41 }
在这个代码当中,可以看到的确是维持了一个死循环,而这个死循环当中, 就是通过 queue.next() 从MessageQueue中取出message,并且如果没有数据的话,就会被阻塞。MessageQueue对象的next()方法这里就不贴出来了,就是一个出队操作(根据message的when属性,当时间到了,就返回对象,并从队列中删除),。
可是取出来的message数据,这里怎么做处理呢。关键是这一句 msg.target.dispatchMessage(msg),
别忘记了我们前面分析时,在message入队之前的一句代码 msg.target = this。所以 这里的 msg.target.dispatchMessage(msg)代码说白了还是在这个Handler的dispatchMessage()来分发处理的message。
1 /** 2 * Handle system messages here. 3 */ 4 public void dispatchMessage(Message msg) { 5 if (msg.callback != null) { 6 handleCallback(msg); 7 } else { 8 if (mCallback != null) { 9 if (mCallback.handleMessage(msg)) { 10 return; 11 } 12 } 13 handleMessage(msg); 14 } 15 } 16 17 /** 18 * Subclasses must implement this to receive messages. 19 */ 20 public void handleMessage(Message msg) { 21 }
在dispatchMessage方法中,经过两个if判断,最终message的处理,落到了 handleMessage()方法,至于handleMessage()方法,是个空方法,看看方法前面的注释,这个时候就应该恍然大悟了。
我们在 最初的 demo中的处理方式,不就是重写的 handleMessage方法嘛。。。
所以在这里,这个过程算是比较清晰了,Handler对象(在其他线程中)发送的message,被放入了 MessageQueue中,然后通过Looper的无限循环,最后又被取出到Handler所处的线程中进行了处理。
另外,除了我们传统Handler的sendMessage()方法外,还有一种方法来使用Handler。
public class HandlerTest_2_Activity extends AppCompatActivity { public static final String TAG = "HandlerTest_2_Activity"; private TextView displayTv; private Handler mHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler_test); displayTv = (TextView) findViewById(R.id.handlerDisplayTv); mHandler = new Handler(); new MyThread("子线程").start(); } class MyThread extends Thread{ public MyThread(String name) { super(name); } @Override public void run() { super.run(); mHandler.post(new Runnable() { @Override public void run() { //操作UI displayTv.setText("operation from " + Thread.currentThread().getName()); } }); } } }
我们在 MyThread子线程当中,可以直接使用 mHandler.post方法来传递任务到ui线程中进行相关操作。(Thread才是线程,Runnable只是任务,如果不明白这一点的,可以看一下我前面写的几篇多线程的文章)。
我们也分析一下相关的代码:
1 /** 2 * Causes the Runnable r to be added to the message queue. 3 * The runnable will be run on the thread to which this handler is 4 * attached. 5 */ 6 public final boolean post(Runnable r) 7 { 8 return sendMessageDelayed(getPostMessage(r), 0); 9 } 10 11 private static Message getPostMessage(Runnable r) { 12 Message m = Message.obtain(); 13 m.callback = r; 14 return m; 15 }
看到 sendMessageDelayed()方法,我们就很熟悉了,其实 还是老一套,将message发送到 队列中,不过 getPostMessage()方法是什么呢?
代码就几行,将Runnable包装成Message。并且m.callback = r; 而关于Message,我们可以new,也可以使用obtain方法。后者效率更好一些,因为系统会维护一个Message池进行复用。
不要忘记了我们的dispatchMessage()方法。
1 public void dispatchMessage(Message msg) { 2 if (msg.callback != null) { 3 handleCallback(msg); 4 } else { 5 if (mCallback != null) { 6 if (mCallback.handleMessage(msg)) { 7 return; 8 } 9 } 10 handleMessage(msg); 11 } 12 } 13 14 private static void handleCallback(Message message) { 15 message.callback.run(); 16 }
此时再读读if判断的第一句,msg.callback是什么,不就是post中传递进去的 Runnable嘛,而这里回调的 handleCallback()方法,转了一圈,最后还是回调到了 Runnable的run()方法了。
所以使用Handler.post(Runnable)算是一种简写了。
不过在dispatchMessage()方法中,还有这样一句判断,mCallback != null ,其实是这样的。
1 /** 2 * Callback interface you can use when instantiating a Handler to avoid 3 * having to implement your own subclass of Handler. 4 * 5 */ 6 public interface Callback { 7 public boolean handleMessage(Message msg); 8 } 9 public Handler(Callback callback) { 10 this(callback, false); 11 }
所以说,处理Message的方式不只一种。
写完这些,Handler这种消息处理机制已经从源码角度分析ok了。理解了原理,今后使用起来才更加的得心应手。原理其实不算复杂,但是真正的细节问题其实也有很多。这篇文章呢,也是我写的第一篇关于分析源码的文章,在这里呢,也给自己立个flag,希望今后能写更多的分析源码的文章。
---
github: https://github.com/yaowen369
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)