Handler消息机制详解
Handler机制是Android开发中最常见的机制,可以说贯穿整个Android,在探究Handler机制原理之前,我们先来捋一下用法
1.handler.post(Runnable)
2.handler.postdelayed(Runnable,int)
3.sendMessage(Message)
4.sendDelayMessage(Message,int)
从形式上就可以看出,第一种用法和第二种用法其实是一样的,只不过一个立即发送一个延迟发送而已,同理三四也是一样
我们先探究handler.post,post具体用法是
handler = new Handler();
hander.post(new runnable(){run(//具体逻辑){}});
首先我们找到Handler源码
public Handler() {this(null, false);}
public Handler(Callback callback, boolean async) { 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(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }
代码不多,一个一个解析,当我们handler = new Handler();时,实际上传入了两个参数进入Handler,一个是null,另一个是false
callback是一个接口,里面只有一个方法
public interface Callback {public boolean handleMessage(Message msg);}
这里传入的是null,暂且放下不谈。这个函数主要过程是初始化Handler里的变量mLooper,mQueue,mCallback以及mAsynchronous
首先mLooper = Looper.myLooper();从这里拿到一个looper,为什么这里会拿到一个looper对象?从这里就要引出ActivityThread.main方法
一个app启动开始,最先调用的方法是ActivityThread.main方法,如同java里的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);Environment.initForCurrentUser();// Set the reporter for event logging in libcoreEventLogger.setReporter(new EventLoggingReporter());Security.addProvider(new AndroidKeyStoreProvider());// Make sure TrustedCertificateStore looks in the right place for CA certificatesfinal File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());TrustedCertificateStore.setDefaultUserDirectory(configDir);Process.setArgV0("<pre-initialized>");Looper.prepareMainLooper();ActivityThread thread = new ActivityThread();thread.attach(false);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));}Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");}
多余的东西我们不看,我们只看Looper.prepareMainLooper();以及Looper.loop();
/*** Initialize the current thread as a looper, marking it as an* application's main looper. The main looper for your application* is created by the Android environment, so you should never need* to call this function yourself. See also: {@link #prepare()}*/public static void prepareMainLooper() {prepare(false);synchronized (Looper.class) {if (sMainLooper != null) {throw new IllegalStateException("The main Looper has already been prepared.");}sMainLooper = myLooper();}}
这里先调用了prepare(false)
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));}
这里先通过sThreadLocal.get()去查找looper,如果发现线程中以及有looper了就抛出异常,Only one Looper may be created per thread每一个线程中只能有一个loop
这里我们是程序第一次启动,当然没有looper,于是接下来调用new Looper创建一个looper
private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();}
looper里包含了一个mQueue和mThread,将当前线程传入,这里我们是主线程传入
然后将looper放入sThreadLocal中,ThreadLocal是一个数据内部储存类,它的神奇之处就在于,使用ThreadLocal储存的对象在不同线程访问会得出不同的对象
打个比方,我们用static进行变量储存的时候,就像我们玩游戏时的公会银行,谁都可以取,谁也可以存,不管谁取出来都是一样的东西,因此在我们不同线程取出来static变量其实是一样的。而使用ThreadLocal进行储存的话,就像我们现实中的银行,一人一个账户,我不可以取你存进去的东西。这里有什么好处呢,这里就可以保证了我(Thread)取出来的东西(looper)一定是我的。其实说了这么说,ThreadLocal里面的实现也很简单,只不过将当前的Thread作为key传进去,本质上还是键值对的方式进行存储。
好,说了这么多让我们回到主线,prepare方法我们已经执行完了,在里面我们新建了looper并存储在ThreadLocal中
程序继续走下去,走到sMainLooper = myLooper();
public static Looper myLooper() {return sThreadLocal.get();}
这里设置了主线程的looper。
综上分析,mLooper = Looper.myLooper();拿到的是主线程的looper。这里就要注意一点,如果我们是在子线程中调用Handler,我们必须给它传入一个looper,因为在子线程中是没有looper的,那我们在子线程中就应该通过Looper.prepare()拿到一个新的looper,将这个looper传入Handler的构造函数就好了
OK,我们回到handler的构造函数中,我们已经拿到了一个looper对象,接下来我们就可以从Looper中取出mQueue,然后的初始化就没有什么好分析了。
我们来看看handler.post函数为什么明明在子线程中,而它的run方法却是在主线程中
public final boolean post(Runnable r){return sendMessageDelayed(getPostMessage(r), 0);}
这里我们进入getPostMessage看看
private static Message getPostMessage(Runnable r) {Message m = Message.obtain();m.callback = r;return m;}
Message.obtain返回一个新的Message对象
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();}
然后将Runnable塞进m.callback里
然后我们接着调用sendMessageDelayed(getPostMessage(r), 0);
public final boolean sendMessageDelayed(Message msg, long delayMillis){if (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);}
最后将mQueue,msg,uptimeMillis一起传入
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this;if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);}
这里的target是一个Handler变量,这里它传入了主线程的Handler
接着走下去
boolean enqueueMessage(Message msg, long when) {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("MessageQueue", 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 {// 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;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;}
这里我们可以看到一个死循环,我们不必拘泥于代码本身,这段代码的意思将manager插入mQueue
代码走到这里仿佛走死了,说好的调用切换线程呢?
不急,还记得我们ActivityThread.main函数吗?里面我们还有一个方法没讲呢,那就是looper.loop();
这就是Handler之所以能切换线程的关键所在,让我们走进方法
/*** 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 (;;) {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 loggerPrinter 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.recycleUnchecked();}}
进入queue.next()方法
Message next() {// Return here if the message loop has already quit and been disposed.// This can happen if the application tries to restart a looper after quit// which is not supported.final long ptr = mPtr;if (ptr == 0) {return null;}int pendingIdleHandlerCount = -1; // -1 only during first iterationint nextPollTimeoutMillis = 0;for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();}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) {// Stalled by a barrier. Find the next asynchronous message in the queue.do {prevMsg = msg;msg = msg.next;} while (msg != null && !msg.isAsynchronous());}if (msg != null) {if (now < msg.when) {// Next message is not ready. Set a timeout to wake up when it is ready.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 (false) Log.v("MessageQueue", "Returning message: " + msg);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.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 handlerboolean keep = false;try {keep = idler.queueIdle();} catch (Throwable t) {Log.wtf("MessageQueue", "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;}}
这里的next方法是一个无线循环的方法,如果消息队形中没有消息,那么next方法会一直阻塞在这里,当有新消息时,next方法会返回这条消息并将消息删除
其实当初看书看到这里还是有疑问的,作者本身爱较真,奈何能力实在有限,等到很长时间才反应过来。作者是这么理解的,我的方法虽然在子线程调用,但是我将子线程中执行的方法打包成一个Message,然后发送到一个共同的Messagequeue,提醒主线程告诉它我已经发消息了,主线程收到我的提醒以后就去Messagequeue中拿出Message,把Message中的方法调用出来执行,这才是Handler消息机制的原理。很多Handler详解都没有告诉作者原来还有提醒这一步,导致作者原来对Handler机制十分模糊。
提醒主线程这一步是在native层实现的,翻了翻源码,发现只有在enqueueMessage方法中if (needWake) nativeWake(mPtr);比较可疑,只能靠一些边角东西推理了
private long mPtr; // used by native code
而在next方法中有这么一个方法
nativePollOnce(ptr, nextPollTimeoutMillis);
作者猜测这个方法应该是阻塞这个方法并接收nativeWake的了。
扯了这么多,还是回到Looper.loop方法中,我们已经取出Message了,接下来就调用msg.target.dispatchMessage(msg);
/*** Handle system messages here.*/public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}
这就是消息处理的方法,不同的消息发送处理方式是有先后之分的,一开始我们调用的方法中是将runnable放入Message中,所以msg.callback != null
这个方法实现就更简单了
private static void handleCallback(Message message) {message.callback.run();}
直接就调用runnable.run方法,简单粗暴我喜欢,到这里通过handler.post(Runnable)的流程已经梳理完了
接下来我们来看看sendMessage(Message),其实两种方式原理都是一样的,只不过一开始走的路不太一样
如果我们要用sendMessage方法,我们需要重写Handler.handleMessage方法,让我们进入源码
public final boolean sendMessage(Message msg){return sendMessageDelayed(msg, 0);}
public final boolean sendMessageDelayed(Message msg, long delayMillis){if (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);}
剩下的源码都和post是一模一样的,只有结尾是不一样的,那我再贴一遍好了
/*** Handle system messages here.*/public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}
因为我们没有传入Runnable,所以msg.callback == null,而mcallback是Handler初始化时传入的参数,我们调用的不是这个构造方法,所以mcallback也是空,最后调用我们重写的handleMessage方法。
其实Handler使用方法还不止四种,作者只是举出了几个常见的,例如还有view.post方法,或者new一个实现了callback接口的对象,将callback传入Handler一样能异步调用,这里就不再多说了。当然,最常见的还是直接在子线程中调用runOnUIThread方法,该方法里的逻辑代码全部运行在主线程中。
最后再说一点其他的,毕竟文章写了是详解
1.假如我们需要消息回传怎么办呢,我不满足子线程光发送消息给主线程,我还想主线程发消息给子线程?
我们回看当初子线程给主线程发送消息的机制,其实是子线程拿到了主线程的looper,从Looper中取出了mqueque,然后两个线程通过mqueue进行通信
所以如果主线程要想发消息给子线程,就一定要拿到子线程的looper。子线程的looper怎么创建在上文已经说了,最后还要调用Looper.loop方法,不然光主线程做好发消息的准备了,子线程还没做好接收消息的准备。
2.Handler中还有其他构造方法,比如说传入looper对象进行构造,当主线程给子线程Thread进行通信时,调用Thread.loop可能拿不到子线程的loop,因为这个时候子线程的loop可能还没创建,这时候又该怎么办?
换个构造方法!!!!好吧,你不想换怎么办,那就使用HandlerThread,HandlerThread是一个扩展了Thread的类,专门用于Handler通信,在HandlerThread的run方法里自带创建looper和loop.loop
public class HandlerThread extends Thread
HandlerThread中有一个getloop方法,我们来看一下
public Looper getLooper() {if (!isAlive()) {return null;}// If the thread has been started, wait until the looper has been created.synchronized (this) {while (isAlive() && mLooper == null) {try {wait();} catch (InterruptedException e) {}}}return mLooper;}
如果Thread还没有开始,那么就会调用wait方法,那么何时被唤醒呢
@Overridepublic void run() {mTid = Process.myTid();Looper.prepare();synchronized (this) {mLooper = Looper.myLooper();notifyAll();}Process.setThreadPriority(mPriority);onLooperPrepared();Looper.loop();mTid = -1;}
在HandlerThread的notifyAll中,调用这个方法后线程就不再处于wait状态,于是主线程的Handler就可以获取到子线程的loop
3.非UI线程真的不能更新ui吗,平常我们都是用Handler来更新UI的,可是真的如此吗?
并不是,我们之所以不让子线程更新UI,是因为有一个并发的问题,Android为了解决这个问题,就在View类里更新UI的方法进行了判断,判断该线程是不是主线程,可是View的实现类ViewRootImp是在OnResume中初始化的额,于是,当我们在oncrate中创建线程并更新UI的时候就有可能在OnResume方法之前完成,因此凡事都不是绝对的
Handler消息机制详解相关推荐
- Android 消息机制详解(Android P)
前言 Android 消息机制,一直都是 Android 应用框架层非常重要的一部分,想更加优雅的进行 Android 开发,我想了解消息机制是非常必要的一个过程,此前也分析过很多次 Handler ...
- Windows消息机制详解-5
一. 什么是消息 在解释什么是消息之前,我们先讨论一下程序的执行机制问题.大体上说,程序按照执行机制可以分为两类: 第一类是过程驱动.比如我们最早接触编程时写的C程序,又或者单片机程序.这类程序往往预 ...
- windows消息机制详解-3
1. 引言 Windows 在操作系统平台占有绝对统治地位,基于Windows 的编程和开发越来越广泛. Dos 是过程驱动的,而Windows 是事件驱动的[6],这种差别的存在使得很多Dos 程序 ...
- android异步工作,Android异步消息机制详解
Android中的异步消息机制分为四个部分:Message.Handler.MessageQueue和Looper. 其中,Message是线程之间传递的消息,其what.arg1.arg2字段可以携 ...
- android handler 传递对象,Android之Handler消息传递机制详解
前言 在Android开发中,多线程应用是非常频繁的,其中Handler机制随处可见. 下面就本人对Handle的一些理解与大家一起分享,共同回顾下Handle异步消息传递机制. 1.Handler是 ...
- Android消息机制详解
*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 Android消息机制 这一定是一个被写烂了的专题吧.那本媛也来凑一个热闹吧.哈哈 这篇博客将会涉及以下内容: 消息机制概述 UM ...
- Windows消息机制详解
消息是指什么? 消息系统对于一个win32程序来说十分重要,它是一个程序运行的动力源泉.一个消息,是系统定义的一个32位的值,他唯一的定义了一个事件,向 Windows发出一个通知,告诉应用 ...
- Windows消息机制详解-6
消息系统对于一个win32程序来说十分重要,它是一个程序运行的动力源泉.一个消息,是系统定义的一个32位的值,他唯一的定义了一个事件,向 Windows发出一个通知,告诉应用程序某个事情发生了.例如, ...
- Windows消息机制详解-2
消息是指什么? 消息系统对于一个win32程序来说十分重要,它是一个程序运行的动力源泉.一个消息,是系统定义的一个32位的值,他唯一的定义了一个事件,向 Windows发出一个通知,告诉应用 ...
最新文章
- 真相!30K拿到互联网大厂offer,网友:我服了!
- 3.4.3 嵌套查询
- 游戏程序员的学习资料
- [hdu5203]计数水题
- c语言基础傅里叶变换,急求:C语言如何实现快速傅里叶变换
- windows查看器无法打开图片_关于windows微软商城无法打开 错误代码0x80004003问题...
- 解决:Throwable:Stub index points to a file without PSI: com.intellij.openapi.fileTypes.UnknownFileType
- ECharts4简单入门
- ASP.NET中Http请求处理流程
- 解决Ubuntu安装tensorflow报错:tensorflow-0.5.0-cp27-none-linux_x86_64.whl is not a supported wheel on this
- hive 时间函数 总结
- View Agent Direct-Connection注册表
- 软考网络工程师真题讲解
- 车用总线技术 | J1939协议实用指南与J1939数据记录方案
- CAXA_三维电子图板v2
- c语言矩阵键盘控制四个led灯,51单片机矩阵键盘与左右流水灯控制C程序
- linux0.11 80386段
- 链家网东莞二手房数据分析
- jqgrid冻结前后列
- python 删除pdf页面_使用Python批量删除扫描PDF中的空白页
热门文章
- jxl.read.biff.BiffException: Unable to recognize OLE stream:
- 奔三的人长时间不睡觉 、第二天上班身体会如何?
- 使用FireBreath写浏览器插件(三)
- 使用MATLAB进行三维空间绘图
- iOS指定页面屏幕旋转,手动旋转
- iphone自动旋转
- No package identifier when getting value for resource number 0x0000001c
- 红鹰工作微信管理监控系统诞生!
- 观《达芬奇的人生秘密》浅谈艺工交叉
- css替换_CSS图像替换的历史