Android Handler消息机制原理最全解读(持续补充中)

article/2025/11/6 2:25:36

 本文主要详细去解读Android开发中最常使用的Handler,以及使用过程中遇到的各种各样的疑问。

Handler


 在Android开发的过程中,我们常常会将耗时的一些操作放在子线程(work thread)中去执行,然后将执行的结果告诉UI线程(main thread),熟悉Android的朋友都知道,UI的更新只能通过Main thread来进行。那么这里就涉及到了如何将
子线程的数据传递给main thread呢?
 Android已经为我们提供了一个消息传递的机制——Handler,来帮助我们将子线程的数据传递给主线程,其实,当熟悉了Handler的原理之后我们知道,Handler不仅仅能将子线程的数据传递给主线程,它能实现任意两个线程的数据传递。
 接下来,我们便详细的了解下Handler的原理及其使用。
 首先看一下Handler最常规的使用方式:

private Handler mHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);switch (msg.what) {case MESSAGE_WHAT:Log.d(TAG, "main thread receiver message: " + ((String) msg.obj));break;}}};private void sendMessageToMainThreadByWorkThread() {new Thread(){@Overridepublic void run() {Message message = mHandler.obtainMessage(MESSAGE_WHAT);message.obj = "I am message from work thread";mHandler.sendMessage(message);}}.start();}/** 通常我们在主线程中创建一个Handler,* 然后重写该Handler的handlerMessage方法,可以看到该方法传入了一个参数Message,* 该参数就是我们从其他线程传递过来的信息。** 我们在来看下子线程中如何传递的信息,子线程通过Handler的obtainMessage()方法获取到一个Message实例,* 我们来看看Message的几个属性:* Message.what------------------>用来标识信息的int值,通过该值主线程能判断出来自不同地方的信息来源* Message.arg1/Message.arg2----->Message初始定义的用来传递int类型值的两个变量* Message.obj------------------->用来传递任何实例化对象* 最后通过sendMessage将Message发送出去。** Handler所在的线程通过handlerMessage方法就能收到具体的信息了,如何判断信息的来源呢?当然是通过what值啦。* 怎么样很简单吧*/

 文章的开头说过,Handler不仅仅是能过将子线程的数据发送给主线程,它适用于任意两个线程之间的通信。
 下面我们来看下两个子线程之间如何进行通信的。
 很简单啊,在一个线程创建Handler,另外一个线程通过持有该Handler的引用调用sendMessage发送消息啊!
 写程序可不能关说不练啊,我们把代码敲出来看一下!

private Handler handler;private void handlerDemoByTwoWorkThread() {Thread hanMeiMeiThread = new Thread() {@Overridepublic void run() {
//                Looper.prepare();handler = new Handler() {@Overridepublic void handleMessage(Message msg) {Log.d(TAG, "hanMeiMei receiver message: " + ((String) msg.obj));Toast.makeText(MainActivity.this, ((String) msg.obj), Toast.LENGTH_SHORT).show();}};
//                Looper.loop();}};Thread liLeiThread = new Thread() {@Overridepublic void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}Message message = handler.obtainMessage();message.obj = "Hi MeiMei";handler.sendMessage(message);}};hanMeiMeiThread.setName("韩梅梅 Thread");hanMeiMeiThread.start();liLeiThread.setName("李雷 Thread");liLeiThread.start();/** 搞定,我们创建了两个Thread,liLeiThread和hanMeiMeiThread两个线程,很熟悉的名字啊!* 跟之前的代码没太大区别hanMeiMeiThread创建了Handler,liLeiThread通过Handler发送了消息。* 只不过此处我们只发送一个消息,所以没有使用what来进行标记* 运行看看,我们的李雷能拨通梅梅吗?* 啊哦,出错了* 05-13 17:08:17.709 20673-20739/? E/AndroidRuntime: FATAL EXCEPTION: 韩梅梅 ThreadProcess: design.wang.com.designpatterns, PID: 20673java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()at android.os.Handler.<init>(Handler.java:200)at android.os.Handler.<init>(Handler.java:114)*Can't create handler inside thread that has not called Looper.prepare()* -----------》它说我们创建的handler没有调用Looper.prepare();* 好的,我们在实例化Handler之前调用下该方法,看一下。加上是不是没有报错了呢。* 等等,虽然没有报错,但是hanMeiMeiThread也没有接到消息啊,消息呢?别急。* 我们在Handler实例化之后加上Looper.loop();看一看,运行一下,是不是收到消息了呢。* 这是为什么呢?* 接下来我们就去看看Handler是怎么实现的发消息呢,弄清楚了原理,这里的原因也就明白了。*/}

 好了,卖了半天的关子,终于要开始真正的主题了。
 首先我们来看下,为什么在子线程里实例化的时候不调用Looper.prepare()就会报错呢?

//我们先来看看new Handler();时出错的原因。后续讲解源码分析只贴出关键部分。
//如下是Handler构造函数里抛出上文异常的地方,可以看到,由于mLooper对象为空才抛出的该异常。
mLooper = Looper.myLooper();if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");}
/*异常的原因看到了,接下来我们看看Looper.prepare()方法都干了些什么?
*/
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));
}
/*可以看到,该方法在当前thread创建了一个Looper(), ThreadLocal主要用于维护线程的本地变量,  
*/private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();
}
//而Looper的构造函数里面又为我们创建了一个MessageQueue()对象。

 了解到此,我们已经成功引出了Handler机制几个关键的对象了,Looper、MessageQueue、Message。
 那么,肯定也有人又产生新的疑问了——为什么在主线程中创建Handler不需要要用Looper.prepare()和Looper.loop()方法呢?
 其实不是这样的,App初始化的时候都会执行ActivityThread的main方法,我们可以看看ActivityThread的main()方法都做了什么?

        Looper.prepareMainLooper();ActivityThread thread = new ActivityThread();thread.attach(false);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));}// End of event ActivityThreadMain.Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);Looper.loop();
/*真相只有一个,是的在创建主线程的时候Android已经帮我们调用了Looper.prepareMainLooper()和Looper.loop()方法,所以我们在主线程能直接创建Handler使用。
*/

 我们接着来看Handler发送消息的过程:

//调用Handler不同参数方法发送Message最终都会调用到该方法
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的关键在于enqueueMessage(),其内部调用了messageQueue的enqueueMessage方法

boolean enqueueMessage(Message msg, long when) {...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;Message p = mMessages;boolean needWake;if (p == null || when == 0 || when < p.when) {// New head, wake up the event queue if blocked.msg.next = p;mMessages = msg;needWake = mBlocked;} else {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.nextprev.next = msg;}// We can assume mPtr != 0 because mQuitting is false.if (needWake) {nativeWake(mPtr);}}return true;}/*从代码可以看出Message被存入MessageQueue时是将Message存到了上一个Message.next上, 形成了一个链式的列表,同时也保证了Message列表的时序性。*/

 Message的发送实际是放入到了Handler对应线程的MessageQueue中,那么,Message又是如何被取出来的呢?
 细心的朋友可能早早就发现了,之前抛出异常的地方讲解了半天的Loop.prepare()方法,一直没有说到Loop.loop()方法。同时,在之前的例子中也看到了,如果不调用Looper.loop()方法,Handler是接受不到消息的,所以我们可以大胆的猜测,消息的获取肯定和它脱不了关系!当然关怀疑还不行,我们还必须找出真相来证明我们的猜想?那还等什么,先看看loop()方法吧。

public static void loop() {
//可以看到,在调用Looper.prepare()之前是不能调用该方法的,不然又得抛出异常了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 (;;) {Message msg = queue.next(); // might blockif (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 loggerfinal Printer logging = me.mLogging;if (logging != null) {logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);}final long traceTag = me.mTraceTag;if (traceTag != 0) {Trace.traceBegin(traceTag, msg.target.getTraceName(msg));}try {msg.target.dispatchMessage(msg);} finally {if (traceTag != 0) {Trace.traceEnd(traceTag);}}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.recycleUnchecked();}}
/*
这里我们看到,mLooper()方法里我们取出了,当前线程的looper对象,然后从looper对象开启了一个死循环 
不断地从looper内的MessageQueue中取出Message,只要有Message对象,就会通过Message的target调用
dispatchMessage去分发消息,通过代码可以看出target就是我们创建的handler。我们在继续往下分析Message的分发
*/
public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}
}
/*好了,到这里已经能看清晰了
可以看到,如果我们设置了callback(Runnable对象)的话,则会直接调用handleCallback方法
*/
private static void handleCallback(Message message) {message.callback.run();}
//即,如果我们在初始化Handler的时候设置了callback(Runnable)对象,则直接调用run方法。比如我们经常写的runOnUiThread方法,由于Handler在主线程创建,所以最终得以在主线程执行:
runOnUiThread(new Runnable() {@Overridepublic void run() {}});
public final void runOnUiThread(Runnable action) {if (Thread.currentThread() != mUiThread) {mHandler.post(action);} else {action.run();}}/*
而如果msg.callback为空的话,
存在一种情况,当创建Handler使用了带Callback的构造方法的话,将会执行Callback的handleMessgae方法,并且会根据其方法的返回值判断是否会被callback拦截,  
是否需要继续执行Handle的handlMessgae方法,如果不存在Callback的拦截则Handler本身的handleMessage方法得意执行。(Handler(Callback)的构造方法使用场景暂时未遇到,欢迎补充)*/

 到这里,想必你应该清楚如何在不同的线程之间来使用Handler了吧。

最后总结一下:

  1. 在使用handler的时候,在handler所创建的线程需要维护一个唯一的Looper对象, 每个线程对应一个Looper,每个线程的Looper通过ThreadLocal来保证,如需了解ThreadLocal,点击查看详细讲解 ,
    Looper对象的内部又维护有唯一的一个MessageQueue,所以一个线程可以有多个handler,
    但是只能有一个Looper和一个MessageQueue。
  2. Message在MessageQueue不是通过一个列表来存储的,而是将传入的Message存入到了上一个
    Message的next中,在取出的时候通过顶部的Message就能按放入的顺序依次取出Message。
  3. Looper对象通过loop()方法开启了一个死循环,不断地从looper内的MessageQueue中取出Message,
    然后通过handler将消息分发传回handler所在的线程。

最后附上一张自己理解画出来的流程图:
这里写图片描述



Handler补充:

1. Handler在使用过程中,需要注意的问题之一便是内存泄漏问题。

为什么会出现内存泄漏问题呢?
首先Handler使用是用来进行线程间通信的,所以新开启的线程是会持有Handler引用的,
如果在Activity等中创建Handler,并且是非静态内部类的形式,就有可能造成内存泄漏。

  1. 首先,非静态内部类是会隐式持有外部类的引用,所以当其他线程持有了该Handler,线程没有被销毁,则意味着Activity会一直被Handler持有引用而无法导致回收。
  2. 同时,MessageQueue中如果存在未处理完的Message,Message的target也是对Activity等的持有引用,也会造成内存泄漏。
解决的办法:

 (1). 使用静态内部类+弱引用的方式:

  静态内部类不会持有外部类的的引用,当需要引用外部类相关操作时,可以通过弱引用还获取到外部类相关操作,弱引用是不会造成对象该回收回收不掉的问题,不清楚的可以查阅JAVA的几种引用方式的详细说明。

private Handler sHandler = new TestHandler(this);static class TestHandler extends Handler {private WeakReference<Activity> mActivity;TestHandler(Activity activity) {mActivity = new WeakReference<>(activity);}@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);Activity activity = mActivity.get();if (activity != null) {//TODO:}}
}

 (2). 在外部类对象被销毁时,将MessageQueue中的消息清空。例如,在Activity的onDestroy时将消息清空。

@Override
protected void onDestroy() {handler.removeCallbacksAndMessages(null);super.onDestroy();
}

2. 在使用Handler时,通常是通过Handler.obtainMessage()来获取Message对象的,而其内部调用的是Message.obtain()方法,那么问题来了,为什么不直接new一个Message,而是通过Message的静态方法obtain()来得到的呢?

下面就通过代码来一探究竟

public static Message obtain() {synchronized (sPoolSync) {if (sPool != null) {Message m = sPool;sPool = m.next;m.next = null;m.flags = 0; // clear in-use flagsPoolSize--;return m;}}return new Message();}

 其实在在Message中有一个static Message变量sPool,这个变量是用于缓存Message对象的,在obtain中可以看到当需要一个Message对象时,如果sPool不为空则会返回当前sPool(Message),而将sPool指向了之前sPool的next对象,(之前讲MessageQueue时讲过Message的存储是以链式的形式存储的,通过Message的next指向下一个Message,这里就是返回了sPool当前这个Message,然后sPool重新指向了其下一个Message),然后将返回的Message的next指向置为空(断开链表),sPoolSize记录了当前缓存的Message的数量,如果sPool为空,则没有缓存的Message,则需要创建一个新的Message(new Message)。
这里写图片描述
 接着看下sPool中缓存的Message是哪里来的?

public void recycle() {if (isInUse()) {if (gCheckRecycle) {throw new IllegalStateException("This message cannot be recycled because it "+ "is still in use.");}return;}recycleUnchecked();}void recycleUnchecked() {// Mark the message as in use while it remains in the recycled object pool.// Clear out all other details.flags = FLAG_IN_USE;what = 0;arg1 = 0;arg2 = 0;obj = null;replyTo = null;sendingUid = -1;when = 0;target = null;callback = null;data = null;synchronized (sPoolSync) {if (sPoolSize < MAX_POOL_SIZE) {next = sPool;sPool = this;sPoolSize++;}}}

 recycle()是回收Message的方法,在Message处理完或者清空Message等时会调用。
recycleUnchecked()方法中可以看到,将what、arg1、arg2、object等都重置了值,如果当前sPool(Message缓存池)的大小小于允许缓存的Message最大数量时,将要回收的Message的next指向sPool,将sPool指向了回收的Message对象(即将Message放到了sPool缓存池的头部)
这里写图片描述

总结:

由此可见,使用obtain获取Message对象是因为Message内部维护了一个数据缓存池,回收的Message不会被立马销毁,而是放入了缓存池,
在获取Message时会先从缓存池中去获取,缓存池为null才会去创建新的Message。


3. Handler sendMessage原理解读。

 引入问题!

  1. sendMessageDelayed是如何实现延时发送消息的?
  2. sendMessageDelayed是通过阻塞来达到了延时发送消息的结果,那么会不会阻塞新添加的Message?

详细分析请移步下篇文章:Handler进阶之sendMessage原理探索


欢迎提供其他有关Handler的问题分享讨论


http://chatgpt.dhexx.cn/article/CTZLwk0G.shtml

相关文章

Handler机制

1.为何引入Handler机制 Handler是线程间通讯的机制&#xff0c;Android中&#xff0c;网络访问、文件处理等耗时操作必须放到子线程中去执行&#xff0c;否则将会造成ANR异常。 ANR异常&#xff1a;Application Not Response 应用程序无响应 产生ANR异常的原因&#xff1a;在…

Handle消息机制解析

概述 Handler消息机制(由Handler/Looper/MessageQueue等构成)&#xff0c;Android有大量的消息驱动方法来进行交互&#xff0c;就像Android的四大组件(Activity、Service、Broadcast、ContentProvider)的启动过程交互&#xff0c;都离不开Handler的消息机制&#xff0c;所以An…

Handler机制(一)——Handler运行流程分析

1 概述 Handler机制是Android的异步消息处理机制&#xff0c;用于在线程间传递消息&#xff0c;主要涉及到四部分&#xff1a;Handler、Looper、Message和MessageQueue。其中Handler是消息的发送者和处理者&#xff1b;Message是消息主体&#xff1b;MessageQueue是消息队列&a…

reshape的作用

reshape就是矩阵的变换就是行和列相乘的数相等就可以相互变换

reshape函数

在opencv中&#xff0c;reshape函数比较有意思&#xff0c;它既可以改变矩阵的通道数&#xff0c;又可以对矩阵元素进行序列化&#xff0c;非常有用的一个函数。 函数原型&#xff1a; C: Mat Mat::reshape(int cn, int rows0) const 参数比较少&#xff0c;但设置的时候却要千…

Reshape的命令应用

import numpy as np tnp.arange(0,64).reshape(8,8) print(t) Reshape 的参考使用&#xff1a; (1条消息) Python的reshape的用法&#xff1a;reshape(1,-1)_冷月无声的博客-CSDN博客_reshape函数pythonhttps://blog.csdn.net/qq_29831163/article/details/90112000Reshape主…

matlab中reshape的用法,reshape2 函数 reshape 的用法

函数 reshape 的用法 请我在MATLAB编程中遇到了一个问题&#xff0c;函数reshape的用法我就是没有弄B reshape(A,m,n) 返回一个m*n的矩阵B&#xff0c; B中元素是按列从A中得到的。如果A中元素个数没有m*n个&#xff0c; 则会引发错误。 B reshape(A,m,n,p,...)和B reshape(…

Numpy之reshape()详解

Numpy中reshape的使用方法为:numpy.reshape(a, newshape, orderC) 参数详解&#xff1a;1.a: type:array_like(伪数组&#xff0c;可以看成是对数组的扩展&#xff0c;但是不影响原始数组。) 需要reshape的array2.newshape:新的数组 新形状应与原形状兼容。如果是整数&#xf…

利用Numpy库的方法reshape()对ndarray对象矩阵的形状进行调整

利用Numpy库的函数reshape()对ndarray对象矩阵的形状进行调整 调整矩阵或图像的形状是一个常用的操作。 在Numpy库中&#xff0c;可使用函数reshape()实现此操作。 其函数原型如下&#xff1a; dst numpy.reshape(a, newshape[, orderC])参数意义如下&#xff1a; a—需要调…

关于reshape

X.reshape(X.shape[0], -1).T和X.reshape(-1&#xff0c;X.shape[0]) 虽然矩阵形式仍然一致但矩阵元素排列完全不同 在降低测试集维度时注意&#xff0c;应使用X.reshape(X.shape[0], -1).T

matlab reshape 用法,函数 reshape 的用法

函数 reshape 的用法别问小编过得好不好不好你也帮助不了好也不是你的功劳。 请小编在MATLAB编程中遇到了一个问题,函数reshape的用法小编就是没有弄B = reshape(A,m,n) 返回一个m*n的矩阵B, B中元素是按列从A中得到的。如果A中元素个数没有m*n个, 则会引发错误。 你知道失望…

【晕头晕脑的Python】Python中Reshape函数解析

Reshape函数解析 Reshape()作用&#xff1a;Reshape()实例说明&#xff1a;一维reshape() 为 二维二维数组 reshape 切片&#xff0c;逆置三维Reshape情况 Reshape()作用&#xff1a; Reshape&#xff08;&#xff09;&#xff0c;函数的作用就是将数据的按照既定的维度进行整…

python中reshape的用法

python中reshape的用法 reshape函数的使用&#xff1a; #reshape&#xff08;&#xff09;是数组对象中的方法&#xff0c;用于改变数组的形状 arr [1,2,3,4,5,6,7,8,9] import numpy as np arrnp.array(arr) #一维 #变成一个3 * 3的二维矩阵&#xff1a; #方法一 arr.resha…

Python的reshape的用法

numpy中reshape函数的三种常见相关用法 reshape(1,-1)转化成1行&#xff1a; reshape(2,-1)转换成两行&#xff1a; reshape(-1,1)转换成1列&#xff1a; reshape(-1,2)转化成两列 numpy中reshape函数的三种常见相关用法 numpy.arange(n).reshape(a, b) 依次生成n个自然…

python中reshape函数用法详解

python中reshape函数用法详解 reshape函数 reshape函数是Numpy库中的一个函数&#xff0c;可以用于改变一个数组的形状&#xff0c;例如将一个二维数组转换成一个三维数组。 import numpy as np # 创建一个二维数组&#xff0c;形状为(4, 6) a np.array([[1, 2, 3, 4, 5, 6]…

ORA-12162 :TNS 指定的网络服务名不正确

原因&#xff1a;这台服务器有多个库 在环境变量文件/home/oracle/.bash_profile中也没有export ORACLE_SIDxxx 解决方法&#xff1a; 登录前先export ORACLE_SIDxxx 再确保查看一下echo $ORACLE_SID 再登录sqlplus / as sysdba

ORA-12162错误解决

新来的一个小伙儿&#xff0c;想学习Oracle我给了他文档&#xff0c;自己研究着&#xff0c;下午时段叫我过去&#xff0c;发现其在装oracle后&#xff0c;进行测试时报ORA-12162错误&#xff0c;正好本鸟之前也遇到过&#xff0c;这里面贴出了大家如有遇到不要惊慌。 报错图片…

ORA-12162: TNS:net service name is incorrectly specified

概述 因未设置系统环境变量ORACLE_SID导致ORA-12162错误 分析原因 首先登录数据库主机执行 oerr ora 12162 我们首先查看看下 tnsnames.ora文件 执行tnsping CC命令 检查下是否可以tnsping通&#xff0c;核对IP和端口以及实例名 数据库服务器端使用TNSNAMES.ORA中记录的…

Oracle“ ORA-12162:TNS:net服务名称指定不正确”错误和解决方案

During the connection to the Oracle Database server with the sqlplus we may get an error with the ORA-12162 specifier. This error will prevent to connect to the Oracle Database Server. 在使用sqlplus连接到Oracle数据库服务器的过程中,ORA-12162说明符可能会出错…

androidstudio自定义Dialog

新建一个XML文件&#xff0c;文件名为layout_custom_dialog <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"androi…