从源码的角度分析Handler机
来源:互联网 发布:java 键值对会覆盖 编辑:程序博客网 时间:2024/05/20 09:47
众所周知Android UI是线程不安全的,如果在子线程中尝试进行UI操作,程序就有可能会崩溃。相信大家在日常的工作当中都会经常遇到这个问题,解决的方案应该也是早已烂熟于心,即创建一个Message对象,然后借助Handler发送出去,之后在Handler的handleMessage()方法中获得刚才发送的Message对象,然后在这里进行UI操作就不会再出现崩溃了。
如果现在运行一下程序,你会发现,在子线程中创建的Handler是会导致程序崩溃的,提示的错误信息为 Can't create handler inside thread that has not called Looper.prepare() 。说是不能在没有调用Looper.prepare() 的线程中创建Handler,报错的位置是在handler2的构造函数中,那么我们查看一下,handler中的无参构造函数
这种处理方式被称为异步消息处理线程,可作为一个程序员,我们不能仅仅满足于会使用,更要做到知其然知其所以然。今天我们就来一起深入探究一下Handler和Message背后的秘密。(参考了第二行代码的作者,郭神的文章。写出来纯粹是为了笔记)
首先来看一下如何创建Handler对象。你可能会觉得挺纳闷的,创建Handler有什么好看的呢,直接new一下不就行了?确实,不过即使只是简单new一下,还是有不少地方需要注意的,我们尝试在程序中创建两个Handler对象,一个在主线程中创建,一个在子线程中创建,代码如下所示:
public class MainActivity extends Activity { private Handler handler1; private Handler handler2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); handler1 = new Handler(); new Thread(new Runnable() { @Override public void run() { handler2 = new Handler(); } }).start(); } }
如果现在运行一下程序,你会发现,在子线程中创建的Handler是会导致程序崩溃的,提示的错误信息为 Can't create handler inside thread that has not called Looper.prepare() 。说是不能在没有调用Looper.prepare() 的线程中创建Handler,报错的位置是在handler2的构造函数中,那么我们查看一下,handler中的无参构造函数
public Handler() { this(null, false); }它通过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; }小伙伴们,我们来看看第十二行,他会对looper进行判断,若looper为空的话就会抛出异常,也就是说Looper.myLooper()方法返回的looper对象为空,那我们再来看看lopper的myLopper()方法:
/** * Return the Looper object associated with the current thread. Returns * null if the calling thread is not associated with a Looper. */ public static @Nullable Looper myLooper() { return sThreadLocal.get(); }官方给的注释已经很清了,也就是说返回和当前线程所关联的Looper对象,若没有的话会返回空。我们再来看看刚才给我们报的错误:Can't create handler inside thread that has not called Looper.prepare(),也就是说, Looper.prepare()此方法会帮我们返回一个looper对象:
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)); }可以看到,首先判断sThreadLocal中是否已经存在Looper了,如果还没有则创建一个新的Looper设置进去。这样也就完全解释了为什么我们要先调用Looper.prepare()方法,才能创建Handler对象。同时也可以看出每个线程中最多只会有一个Looper对象。
你可能会比较疑惑。主线程中的Handler也没有调用Looper.prepare()方法,为什么就可以正常使用呢?细心的朋友我相信都已经发现了这一点,这是由于在程序启动的时候,系统已经帮我们自动调用了Looper.prepare()方法。查看ActivityThread中的main()方法,代码如下所示:
public static void main(String[] args) { SamplingProfilerIntegration.start(); CloseGuard.setEnabled(false); Environment.initForCurrentUser(); EventLogger.setReporter(new EventLoggingReporter()); Process.setArgV0("<pre-initialized>"); Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } AsyncTask.init(); if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }在第7行调用了Looper.prepareMainLooper()方法,而这个方法又会再去调用Looper.prepare()方法,代码如下所示:
public static final void prepareMainLooper() { prepare(); setMainLooper(myLooper()); if (Process.supportsProcesses()) { myLooper().mQueue.mQuitAllowed = false; } }因此我们应用程序的主线程中会始终存在一个Looper对象,从而不需要再手动去调用Looper.prepare()方法了。
总结一下:new handler之前必须要有looper对象,在UI线程中系统帮我们调用的looper.prepare方法产生了一个looper对象,而我们自己的线程中需要new handler的话,就必须先调用looper对象prepare方法产生一个looper对象,那么可能有小伙伴会疑惑为什么不new 一个呢?相信大家都明白,必然是构造方法私有了。
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }只能通过prepare()方法来获取looper对象;
看完了如何创建Handler之后,可能有不少小伙伴比较疑惑,为什么要先有有looper呢?还是通过源码,来解决你的疑惑:
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }在looper的构造方法中会有帮我们造一个messageQueue,也就是消息队列。此消息队列是handler通过send或者post发送消息的目的地,试问若容器都不存在,如何装东西呢?这里做一个比喻,将messageQueue比作邮箱,把Hanlder比作邮递员,既然邮箱和邮递员都存在了,那么现在是不是就缺少邮件呢?也就是信。信在Android中也就是Message对象,这里为了节省信封,我们一般使用Message对象的obtain方法来循环利用信封,它的内部是是使用链表结构存储多个消息对象,最大长度为50,目的是为了重复的使用Message对象,避免每次都创建消息对象,避免造成大量的垃圾对象:
private static int sPoolSize = 0; private static final int MAX_POOL_SIZE = 50;
public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); }取得messenge对象之后,可以调用 setData()方法或arg参数等方式为消息携带一些数据,再借助Handler将消息发送出去就可以了
new Thread(new Runnable() { @Override public void run() { Message message = new Message(); message.arg1 = 1; Bundle bundle = new Bundle(); bundle.putString("data", "data"); message.setData(bundle); handler.sendMessage(message); } }).start();可是这里Handler到底是把Message发送到哪里去了呢?为什么之后又可以在Handler的handleMessage()方法中重新得到这条Message呢?看来又需要通过阅读源码才能解除我们心中的疑惑了,Handler中提供了很多个发送消息的方法,其中除了sendMessageAtFrontOfQueue()方法之外,其它的发送消息方法最终都会辗转调用到sendMessageAtTime()方法中,毕竟殊途同归麽。这个方法的源码如下所示:
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); }
sendMessageAtTime()方法接收两个参数,其中msg参数就是我们发送的Message对象,而uptimeMillis参数则表示发送消息的时间,它的值等于自系统开机到当前时间的毫秒数再加上延迟时间,如果你调用的不是sendMessageDelayed()方法,延迟时间就为0,然后将这两个参数都传递到MessageQueue的enqueueMessage()方法中。这个MessageQueue是一个消息队列,用于将所有收到的消息以队列的形式进行排列,并提供入队和出队的方法。这个类是在Looper的构造函数中创建的,因此一个Looper也就对应了一个MessageQueue。
那么我们来看一看发送时的入队方法:
final boolean enqueueMessage(Message msg, long when) { if (msg.when != 0) { throw new AndroidRuntimeException(msg + " This message is already in use."); } if (msg.target == null && !mQuitAllowed) { throw new RuntimeException("Main thread not allowed to quit"); } synchronized (this) { if (mQuiting) { RuntimeException e = new RuntimeException(msg.target + " sending message to a Handler on a dead thread"); Log.w("MessageQueue", e.getMessage(), e); return false; } else if (msg.target == null) { mQuiting = true; } msg.when = when; Message p = mMessages; if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; this.notify(); } else { Message prev = null; while (p != null && p.when <= when) { prev = p; p = p.next; } msg.next = prev.next; prev.next = msg; this.notify(); } } return true; }首先,MessageQueue并没有使用一个集合把所有的消息都保存起来,它只使用了一个mMessages对象表示当前待处理的消息。然后观察上面的代码我们就可以看出,所谓的入队其实就是将所有的消息按时间来进行排序,这个时间当然就是我们刚才介绍的uptimeMillis参数。具体的操作方法就根据时间的顺序调用msg.next,从而为每一个消息指定它的下一个消息是什么。当然如果你是通过sendMessageAtFrontOfQueue()方法来发送消息的,它也会调用enqueueMessage()来让消息入队,只不过时间为0,这时会把mMessages赋值为新入队的这条消息,然后将这条消息的next指定为刚才的mMessages,这样也就完成了添加消息到队列头部的操作。
现在入队操作我们就已经看明白了,那出队操作是在哪里进行的呢?这个就需要看一看Looper的另一个重要的作用了。Looper.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 block 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 final 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(); } }可以看到,这个方法从for语句开始,进入了一个死循环,然后不断地调用的MessageQueue的next()方法,我想你已经猜到了,这个next()方法就是消息队列的出队方法。它的简单逻辑就是如果当前MessageQueue中存在mMessages(即待处理消息),就将这个消息出队,然后让下一条消息成为mMessages,否则就进入一个阻塞状态,一直等到有新的消息入队。继续看loop()方法中的 msg.target.dispatchMessage(msg)方法,每当有一个消息出队,就将它传递到msg.target的dispatchMessage()方法中,那这里msg.target又是什么呢?其实就是Handler啦,你观察一下上面sendMessageAtTime()方法就可以看出来了。接下来当然就要看一看Handler中dispatchMessage()方法的源码了,如下所示:
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }在第5行进行判断,如果mCallback不为空,则调用mCallback的handleMessage()方法,否则直接调用Handler的handleMessage()方法,并将消息对象作为参数传递过去。这样我相信大家就都明白了为什么handleMessage()方法中可以获取到之前发送的消息了吧!
那么我们还是要来继续分析一下,为什么使用异步消息处理的方式就可以对UI进行操作了呢?这是由于Handler总是依附于创建时所在的线程,比如我们的Handler是在主线程中创建的,而在子线程中又无法直接对UI进行操作,于是我们就通过一系列的发送消息、入队、出队等环节,最后调用到了Handler的handleMessage()方法中,这时的handleMessage()方法已经是在主线程中运行的,因而我们当然可以在这里进行UI操作了。整个异步消息处理流程的示意图如下图所示:
(注:看了那么多图,还是觉得郭神的图最好,就盗过来用了)
另外除了发送消息之外,我们还有以下几种方法可以在子线程中进行UI操作:
还可以调用Handler的post()系列的方法:
还可以调用Handler的post()系列的方法:
post方法的源码如下:
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); }这里还是调用了sendMessageDelayed()方法去发送一条消息啊,并且还使用了getPostMessage()方法将Runnable对象转换成了一条消息,我们来看下这个方法的源码:
private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }在这个方法中将消息的callback字段的值指定为传入的Runnable对象。这个callback字段不就是handler方法的dispatchMessage()方法中的那个callback吗?在Handler的dispatchMessage()方法中原来有做一个检查,如果Message的callback等于null才会去调用handleMessage()方法,否则就调用handleCallback()方法。handleCallback()代码如下:
private static void handleCallback(Message message) { message.callback.run(); }也就是直接调用了一开始传入的Runnable对象的run()方法。虽然写法上相差很多,但是原理是完全一样的,我们在Runnable对象的run()方法里更新UI,效果完全等同于在handleMessage()方法中更新UI。
0 0
- 从源码的角度分析Handler机
- Handler机制-从源码角度分析
- 从源码角度分析java层Handler机制
- 从源码的角度分析Thread、Looper、MessageQueue、Handler的关系
- 从源码角度解析Handler
- 带你从源码的角度去理解Handler
- 从源码的角度解析Handler、Looper、Message和MessageQueue
- 重新从源码的角度看Handler消息通信机制
- Android中关于Handler Message Looper 异步消息处理机制的最完整分析 从源码角度进行剖析
- 从源码角度看Handler原理
- 从源码角度深入理解Handler
- 从源码角度剖析Handler 机制
- 从源码角度剖析Handler机制
- 【Android】从源码角度看Handler机制
- 从源码角度分析ViewDragHelper
- 从源码角度分析NestedScrolling
- Android 从源码角度分析消息处理机制(Handler,Looper,Message)
- 从源码角度理解handler,子线称中使用Handler
- [Leetcode] 45. Jump Game II 解题报告
- 16秋《计算机网络技术》作业2
- LeetCode 169——分治算法使用
- 16秋《计算机网络技术》作业1
- 442. Find All Duplicates in an Array
- 从源码的角度分析Handler机
- apache与tomcat的联系与区别
- Android 取消 上一个Toast
- mac上expect安装
- struts2搭建web服务器
- 16秋《网页制作》作业4
- Servlet快速入门
- 16秋《网页制作》作业3
- 自学C语言,从一章开始-数据类型