Android线程间通信-Handler消息机制
需要handler消息机制的原因
- 在android中由于UI线程并不是线程安全的,如果有子线程更新UI容易导致数据错乱,如果UI线程设置为线程安全的话导致效率低下;
- 而UI线程做耗时操作容易导致ANR发生。所以需要由子线程做耗时操作当子线程需要更新UI时通知主线程更新UI,而线程间的通信就由Handler消息机制完成。
Handler消息机制原理
在主线程创建一个handler的同时创建了looper和MessageQueue,子线程将需要更新UI的信息构建为message对象,调用入队方法添加到消息队列里,由looper调用loop方法无限循环取出消息并分发给对应的target(handler),由handler调用handlemessage方法处理消息。由此完成了子线程和主线程的通信。
流程图如下:
文字解释:
- 创建一个线程(一般是主线程),在线程内创建一个handler,创建handler的时候初始化了looper和MQ。
- 工作线程产生消息后,调用handler的sendMessageAtTime()方法,发送消息。
- MQ调用enqueueMessage方法将消息入队到MQ中
- Looper调用loop方法不断循环MQ的下一条消息
- 获取下一条消息后Handler调用dispatchMessage方法将消息分发给对应的tartget(Handler)
- 由Handler调用handlerMessage方法处理消息。
由于Handler和线程是绑定的,同一进程中,不同线程是可以公用资源的,所以在线程A中创建了handler,线程B可以调用来发送消息,经过上面步骤将线程B的消息分发到线程A处理,线程间的通信完成。
源码分析
基于Androidsdk28版本。
1、handler的创建(构造函数)
1.1、空构造函数
//空构造,最终调用的是有参构造函数 public Handler() { this(null, false); }
1.2、有参构造函数
空构造函数默认采用当前线程的looper,回调方法callback为null,消息为同步处理方式。
//可以指定传入的looper,可以在子线程传入mainlooper public Handler(Looper looper) { this(looper, null, false); } public Handler(Callback callback, boolean async) { //匿名内部类如果不声明为static,会警告内存泄漏 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()); } }//初始化获取looper,具体获取方法见1.2.1 mLooper = Looper.myLooper(); //必须有一个looper,否则抛异常,这也就是为什么子线程直接使用handler会抛异常的原因,解决办法初始化一个looper if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()"); } //初始化MQ,详细分析见1.2.2 mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }
1.2.1、Looper与MQ的初始化
looper通过Looper.myLooper()方法获取。是通过当前线程的threadlocal获取。
**ThreadLocal:**线程本地存储区(TLS),每一个线程都有自己的本地存储区,不同线程间彼此不能访问彼此的TLS。
public static @Nullable Looper myLooper() {//通过线程本地存储区获取 return sThreadLocal.get();} public T get() { //获取当前线程 Thread t = Thread.currentThread(); //获取当前线程为key值的ThreadLocalMap:以threadlocal为key,Entry为value 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();}
一般线程的looper的初始化是在调用Looper.prepare()里初始化looper并初始化MQ。**注:**主线程的looper及MQ的初始化是在创建主线程的时候由ActivityThread的prepareMainLooper()
方法自动初始化的。
//调用的方法public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { //一个线程只允许有一个looper if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } //创建looper并将looper存储到当前线程为key的线程本地存储区里 Threadlocal中 sThreadLocal.set(new Looper(quitAllowed)); //new looper的同时初始化了MQ }
主线程的looper的初始化,主线程不允许退出looper。
public static void prepareMainLooper() { prepare(false); //设置不允许退出的Looper synchronized (Looper.class) { //将当前的Looper保存为主Looper,每个线程只允许执行一次。 if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); }}
Threadlocal的存储方法:
public void set(T value) { //获取当前线程 Thread t = Thread.currentThread(); //获取当前线程为key值的ThreadlocalMap中 ThreadLocalMap map = getMap(t); //如果已经存在,替换value if (map != null) map.set(this, value); else //不存在,创建map createMap(t, value); }
MessageQueue的初始化:
private Looper(boolean quitAllowed) { //初始化MQ mQueue = new MessageQueue(quitAllowed); //将looper绑定为当前线程 mThread = Thread.currentThread(); }
2、Handler的发送消息
2.1、handler发送消息
在工作线程中构建一个Message对象,调用handler的sentXXX进行发送消息,几个sentXXX方法最终都是调用sendMessageAtTime(Message msg, long uptimeMillis);将消息放入一个消息队列。
//获取到消息队列,并将发送的消息按时间入列,消息队列在loop的构造方法中创建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; } //指定msg的target为handler并将消息入列 return enqueueMessage(queue, msg, uptimeMillis); } //sendMessageAtFrontOfQueue 设置消息触发时间为0达到将消息放在队列头的目的
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { //指定了msg的target是handler msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } //调用MQ的入列操作 return queue.enqueueMessage(msg, uptimeMillis); }
MQ的消息入列操作:注:MQ的存储结构不是队列,而是单链表。
boolean enqueueMessage(Message msg, long when) { //msg必须有一个分发的目标 if (msg.target == null) { 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; Message p = mMessages; //当前待处理的消息 boolean needWake; if (p == null || when == 0 || when < p.when) { //MQ中没有消息,或者当前待处理消息的时间是最早的 // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; //阻塞时需要唤醒 } else { // Inserted within the middle of the queue. Usually we don't have to wake // up the event queue unless there is a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; //将消息按时间顺序插入到MQ中 for (;;) { prev = p; p = p.next; //无当前处理消息,或者传进来的消息时间比当前处理消息时间早跳出循环 if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } //将当前消息放在P(待处理消息)前 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; }
3、轮询取出消息
3.1、loop方法
- 获取looper
- 不断的读取MQ里的下一条消息(没有消息时跳出死循环)
- 将消息分发给相应的target
- 把分发后的消息回收到消息池。
/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */ public static void loop() { //获取looper 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 (;;) { //读取MQ里的下一条消息 Message msg = queue.next(); // might block //没有消息的时候跳出死循环 if (msg == null) { // No message indicates that the message queue is quitting. return; } // ....省略.... final long traceTag = me.mTraceTag; if (traceTag != 0 && Trace.isTagEnabled(traceTag)) { Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); } final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); final long end; try { //msg.target 即Handler进行分发事件 msg.target.dispatchMessage(msg); end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); } //...省略... //确保事件分发中identity不会被损坏 final long newIdent = Binder.clearCallingIdentity(); //...省略... msg.recycleUnchecked(); //将消息放入消息池以便重复利用。 }
3.2、读取下一条消息
Message next() { //当looper已经退出时,直接返回,这种情况出现在App试图在退出后重启looper,这是不允许的 final long ptr = mPtr; if (ptr == 0) { return null; }//循环迭代间隔的标记位 int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; //循环取消息 for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); }//阻塞操作,在native层完成 nativePollOnce(ptr, nextPollTimeoutMillis); 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) { // 当target为null 时,查找下一条消息 do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { // 当前时间比消息触发时间短,重新设置下一次轮询的超时时长 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } // Process the quit message now that all pending messages have been handled. if (mQuitting) { dispose(); return null; } // If first time idle, then get the number of idlers to run. // Idle handles only run if the queue is empty or if the first message // in the queue (possibly a barrier) is due to be handled in the future. //当消息是消息队列的第一个消息或者MQ为null时执行Idle handle if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true; continue; } if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // Run the idle handlers.只有第一次循环时 // We only ever reach this code block during the first iteration. for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; try { keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0; // While calling an idle handler, a new message could have been delivered // so go back and look again for a pending message without waiting. nextPollTimeoutMillis = 0; } }
3.3、循环获取到消息后分发给target----dispatchMessage
public void dispatchMessage(Message msg) {//msg回调方法不为null ,调用 message.callback.run(); if (msg.callback != null) { handleCallback(msg); } else { //当Handler成员mCallback 不为null时,调用成员变量的callback handleMessage if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } //否则调用Handler的自身的handleMessage方法。 handleMessage(msg); } }
消息分发的优先级:
- Message的回调方法:
message.callback.run()
,优先级最高。 - Handler的回调方法:
Handler.mCallback.handleMessage(msg)
- Handler的默认方法:
Handler.handleMessage(msg);
问题:
1、 Handler
,Looper
,Message
,MessageQueue
,Thread
的对应关系?
- Handler:主要发送消息(Handler.sentMessage())和处理消息(Handler.handleMessage())。Handler中有Looper和MessageQueue。
- Looper:循环执行(Looper.loop()),按分发机制将消息分发给目标处理者。Looper中有一个MessageQueue.
- Message:消息分为硬件(按钮、触摸)或软件生成的消息。Message中有一个Handler。
- MessageQueue:消息队列主要功能是入队(MessageQueue.enqueueMessage())消息和取出(MessageQueue.next())消息。消息队列中有一组Message(待处理消息)。
- Thread:线程,主要 是处理事务,一个Thread绑定一个Looper
2、主线程向子线程发消息如何发?
在子线程创建Handler,同时需要创建Looper,发送消息,在子线程中获取消息并处理消息。
3、在子线程 new 一个 Handler
需要注意什么?
在子线程中直接创建Handler会导致程序崩溃,报错:Can’t create handler inside thread that has not called Looper.prepare()。 需要手动创建一个looper。
4、Looper 死循环为什么不会导致应用卡死,会消耗大量资源吗?**
- 当子线程运行结束时,线程退出,线程生命周期结束,(通过查看next方法,子线程开的looper对象的是否允许退出是true,所以在子线程执行next时循环到没有消息时,会执行dispose进行清理工作。)
- 而对于主线程,我们不希望可运行期间退出,所以死循环保证了线程不被退出,当没有消息时,会通过nativePollOnce进行阻塞。
- 主线程死循环不会消耗大量的资源,因为在主线程的MessageQueue没有消息时,便阻塞在loop()的next()里的nativePollOnce()方法里。此时主线程会释放CPU资源进入休眠状态,直到下个消息入队到消息队列,通过往管道写入字符唤醒loop线程(主线程)。
5、主线程的消息循环机制是什么(死循环如何处理其它事务)?
-
主线程死循环 通过创建新线程处理其他事务。
-
主线程的消息循环模型:AT(ActivityThread)通过ApplicationThread和AMS(ActivityManagerService)进行进程间通信。AMS完成AT的请求后会回调ApplicationThread中的Binder方法,ApplicationThread会向H发送消息,H接收到消息后会将ApplicationThread中的逻辑切换到AT中执行。
6、ActivityThread 的动力是什么?(ATLooper中绑定的线程是什么?)
AT没有集成Thread,不是一个线程,那么在AT中Looper绑定的线程是zygote fork出来的进程,进程与线程的区别可能只是是否可以资源共享。
7、Handler如何能够切换线程?
同一进程间线程资源是共享的,Handler绑定的是在它关联的Looper绑定的线程处理消息的。
8、子线程有哪些更新UI的方法?
- 主线程定义Handler,子线程发送消息,主线程更新UI。
- runOnMainThread
- 创建Handler,传入getMainLooper
- View.Post(Runable r);
Handler绑定的是在它关联的Looper绑定的线程处理消息的,几种方法的源码归根结低都是使用Handler消息机制。
9、如何避免Handler造成的内存泄漏?
在子线程中如果创建Looper,那么在所有的事情完成后如果不将looper调用quit方法退出,子线程会一直等待,如果Looper退出,线程也就退出了。
另外如果在主线程Handler处理消息是有一个延时消息,会一直保存在 主线程的消息队列里,会影响系统对Activity的回收。
所以避免内存泄漏:
- 在确定子线程不需要looper时将其退出。
- 有延时消息时在Activity销毁时将Message移除。
- 非静态内部类和匿名内部类会隐式持有外部类的引用,handler不被释放,持有的外部类也不能被释放,匿名内部类改成匿名静态内部类(一开始创建内存),对Activity的引用使用弱引用。
解决Handler内存泄漏例子如下:
1、内存泄漏的例子:
public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity";//创建handler----非静态内部类 private Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //模拟异步操作 handler.postDelayed(new Runnable() { @Override public void run() { Log.d(TAG, "run: 模拟异步操作"); } },1000*60*5); }}
2、检测内存泄漏-Android profiler
- 运行项目,点击Android profiler,选择设备及报名,选择MEMORY项查看内存。
- 选择app package(Arrange by package) ,点击旁边 Jump Java heap按钮查看堆栈信息,在左边看到引用树。
- 反复关闭页面操作,观察引用树,MainActivity一直未被回收,此时已经发生内存泄漏。
- 点击左上角的垃圾桶(GC)内存也没有明显变化。
两个实例的depth都是3,不可以被GC,引用树里Reference有massage相关的,大概就是Handler发生了内存泄漏。
2.1、检测内存泄漏-LeakCanary
-
添加依赖
//内存泄漏检测 debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1' releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1' // Optional, if you use support library fragments: debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.1'
-
在Application中安装LeakCanary
if (LeakCanary.isInAnalyzerProcess(this)){ return;}LeakCanary.install(this);
如果可能发生内存泄漏时会通知引用树。查看最后的引用情况就是MessageQueue.mMessages.
3、修复内存泄漏
**分析:**在Java中非静态内部类或匿名内部类会隐式持有外部类实例。修改为静态内部类和弱引用持有外部类。
//修改为静态内部类 private static class MyHandler extends Handler{ private final WeakReference mActivity; public MyHandler(MainActivity activity) { mActivity = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { MainActivity mainActivity = mActivity.get(); super.handleMessage(msg); if (mainActivity!=null){ Log.d(TAG, "handleMessage: 处理逻辑"); } } } private static final Runnable mRunable = new Runnable() { @Override public void run() { Log.d(TAG, "run: 模拟耗时操作"); } }; private final MyHandler handler = new MyHandler(MainActivity.this); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //模拟异步操作// handler.postDelayed(new Runnable() {// @Override// public void run() {// Log.d(TAG, "run: 模拟异步操作");// }// },1000*60*5); handler.postDelayed(mRunable,1000*60*5); } @Override protected void onDestroy() { super.onDestroy(); handler.removeCallbacks(mRunable); handler.removeCallbacksAndMessages(null);// handler.removeMessages(); }
再次使用Android profiler 查看内存,每次页面关闭时都会触发GC,内存有明显变化。
LeakCanary也没有内存泄漏的通知。
感谢前辈们的分享链接:
http://gityuan.com/2015/12/26/handler-message-framework/
http://www.10tiao.com/html/227/201711/2650241824/1.html
更多相关文章
- Android(安卓)线程模型和 AsyncTask
- Android(安卓)消息机制(Handler Looper Message )理解
- Android信息推送—AndroidPN的学习(上)
- android线程、UI、AsyncTask
- Android中MotionEvent的来源和ViewRootImpl
- Android的线程详解(几种实现方法及区别)
- android log 分析(一)
- Android学习笔记——从源码看Handler的处理机制
- Android(安卓)远程图片获取和本地缓存策略