Android源码解析Handler系列第(一)篇 --- Message全局池
来源:互联网 发布:树木测高仪软件 编辑:程序博客网 时间:2024/04/28 05:20
我们常说UI需要在主线程中进行更新,子线程就不能更新UI吗?不是,我们并不是说不能在子线程中更新UI,而是说UI必须要在它的创建线程中进行更新,比如下面一段代码在子线程更新UI就不会报错。
new Thread(new Runnable() { @Override public void run() { TextView textView=new TextView(MainActivity.this); textView.setText("Hello"); } }).start();
OK,那我偏偏不在创建线程中进行更新,如下!view在主线程中创建,在子线程中add一个Button。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final LinearLayout view = (LinearLayout) LayoutInflater.from(this).inflate(R.layout.activity_main, null, false); setContentView(view); new Thread(new Runnable() { @Override public void run() { Button btn=new Button(MainActivity.this); btn.setText("Hello"); view.addView(btn); } }).start(); }
运行后,Button被顺利的加到了根布局中。尼玛,“UI必须要在它的创建线程中进行更新”这种说法也不对啊!!!不是不对,而是欠妥,我修改一下代码!
new Thread(new Runnable() { @Override public void run() { Button btn=new Button(MainActivity.this); btn.setText("Hello"); SystemClock.sleep(2000); view.addView(btn); } }).start();
就是在addView之前,睡眠了一会,结果还是抛出了 android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.这个错误!通常这种错误,就要用Handler向UI线程发送消息来更新!这个后面会对源码进行分析。说了半天,进入正题,我今天要讨论的是Message。要掌握Handler,先对Handler发送的Message有所了解。
2、Message的初步认识
Message就是一个实现了Parcelable的java类,我的第一感觉是它可以可以用在进程间通信(IPC),可以用在网络中传输,看一下它比较重要的几个字段。先来几个熟悉的。
//当有多个Handler发消息的时候,或者一个Handler发多个消息的时候,可以用what标识一个唯一的消息 public int what; //当传递整形数据的时候,不需要使用setData(Bundle)去设置数据,使用arg1,arg2开销更小。 public int arg1; public int arg2; //Message所携带的数据对象 public Object obj;
这几个都是很常用的,要深入了解Message,不得不了解下面几个。
//flags表示这个Message有没有在使用,1表示在池中,等待复用,0表示正在被使用, int flags; //Message发送之后,何时才能被处理 long when; // Message所携带的数据 Bundle data; //表示这个消息被哪个Handler处理 Handler target; //我们用Handler.post一个Runnable,这个Runnable也是被包装成Message对象的 Runnable callback; // 作为消息链表所用的一个成员 Message next; //sPoolSync是对象锁,因为Message.obtain方法会在任意线程调用 private static final Object sPoolSync = new Object(); //sPool代表接下来要被重用的Message对象 private static Message sPool; //sPoolSize表示有多少个可以被重用的对象 private static int sPoolSize = 0; //MAX_POOL_SIZE是pool的上限,这里hardcode是50 private static final int MAX_POOL_SIZE = 50;
对于上面列举的,挑几个重点分析一下。
Runable是怎么被封装到Message中的?我们常为会延迟做某个操作写出下面的代码
handler.postDelayed(new Runnable() { @Override public void run() { } },1000);跟踪进去发现在postDelayed中调用了getPostMessage
private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
所以这个run方法只是一个普通的回调而已,千万不要认为他是在另外一个线程中。刚说了Message中的target表示了这个消息要被哪一个Handler所处理,对于Message是怎么被处理的呢?尽管后面要分析Handler源码,这里还是提一下。
Message的处理顺序
Looper.loop()方法中Message被从MessageQueue取出来后会调用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); } }
在dispatchMessage方法中,检查是否有由Runnable封装的消息,如果有,首先处理;其次处理的是mCallback,mCallback是什么?mCallback是Callback的对象,Callback是Handler中的一个内部接口,这个接口的作用是,当你实例化一个Handler对象的时候,可以避免不去实现Handler的handleMessage方法。
/** * Callback interface you can use when instantiating a Handler to avoid * having to implement your own subclass of Handler. * * @param msg A {@link android.os.Message Message} object * @return True if no further handling is desired */ public interface Callback { public boolean handleMessage(Message msg); }
从消息的分发可以看到,如果返回了true,那么handler中的handleMessge方法是不会被执行的,如果返回false或者mCallback没有被赋值,那么就会回调Handlerd的handleMessge,这就是一个Message的分发流程。了解了Message的分发之后,那么Message是怎么被创建的呢。
3、Message全局池
ANDROID系统中很多操作都是靠发消息完成的,比如按下返回键就是一个消息,这样系统会不会构建大量的Message对象呢?上面看到getPostMessage方法中,以 Message.obtain()的方式获取一个消息,而不是通过它的构造函数,获取消息的代码很简单。
/** * Return a new Message instance from the global pool. Allows us to * avoid allocating new objects in many cases. */ 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(); }
有一个关键词是sPool,初步感觉是池的意思,别忘记了,在第一节Message的初步认识中,sPool是一个Message对象 ,这里有点蒙逼,一个对象起个名字跟池有什么关系。回顾一下,Message还有一个成员是 next。
// sometimes we store linked lists of these things Message next;
这样每一个Message对象都有一个next指针指向下一个可用的Message,这不就是大学数据结构中的链表嘛!所以一个消息池的结构是这样的。
既然知道Message有一个消息池(我们通常称这个消息池为全局池)的机制,那么它设计初衷肯定是要做到复用的,那么还有一个问题,Message何时被入池,何时出池?(MessageQueue虽然是存储消息的,但要弄清楚,这里说的消息池跟MessageQueue并没有什么关系。)
消息要入池,也就是这个消息被回收到池中,等待复用,所以我们大胆猜测这个消息肯定不在使用之中,如果这个消息正在使用之中,是肯定不会把它放到全局池里面的,也就是说只有这个消息完成了它的使命,系统才能把它回收到全局池中。通过这样的分析,消息入池不是在它的创建阶段,而是在回收阶段。直接看代码吧。Message中有一个recycle的方法。
/** * Return a Message instance to the global pool. * <p> * You MUST NOT touch the Message after calling this function because it has * effectively been freed. It is an error to recycle a message that is currently * enqueued or that is in the process of being delivered to a Handler. * </p> */ public void recycle() { if (isInUse()) { if (gCheckRecycle) { throw new IllegalStateException("This message cannot be recycled because it " + "is still in use."); } return; } recycleUnchecked(); }
首先判断消息是否在使用之中
boolean isInUse() { return ((flags & FLAG_IN_USE) == FLAG_IN_USE); }
如果在使用之中,继续判断gCheckRecycle,gCheckRecycle的默认值是true,这个一个与版本相关的常量,在5.0之后的版本这个值是false,不知道作什么使用,有谁知道可以告诉我一下。在此不纠结了。如果不在使用之中,最后会走进recycleUnchecked。
/** * Recycles a Message that may be in-use. * Used internally by the MessageQueue and Looper when disposing of queued Messages. */ 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++; } } }
这段代码也不是很长,很好理解,一开始,把这个消息所有成员赋值成最初的状态,FLAG_IN_USE的值是1一开始说了Message的flags表示这个Message有没有在使用,1表示在池中,等待复用,0表示正在被使用。重点看同步锁中的代码。
假设全局池没有元素时,我们将第一个消息放到池中,sPool一开始是NULL,next指向了sPool,所以此时的消息的sPool和next都是NULL,然后sPool指向当前的Message对象,最后池的数量加1。大致如下图。
假设有来个消息m2,在走一遍同步锁中的代码,此时全局池的状态如下图所示。
同理,三个消息的时候,是这样
看到这,相信你已经知道了消息是怎么加到全局池中的,那么何时出池呢?再看消息的获取代码,尝试找出消息何时出池的。
/** * Return a new Message instance from the global pool. Allows us to * avoid allocating new objects in many cases. */ 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(); }
obtain经常使用在多线程之中,所以用sPoolSync作为同步锁,第一次sPool为空,会new一个消息返回,这new的消息会在回收的时候,会被加到全局池中。如果sPool不为空,sPool是什么?sPool是指向全局池的头指针,sPool不为空,说明了全局池中有元素。把sPool赋值给一个Message对象m,同时全局池的头指针向后移,指向下一个被复用的消息,然后把m的flags赋值为0,表示这个消息被复用了,池中元素数量减1。经过这个逻辑,上图中的消息m1是不是就被出池了呢?
OK,本文分析到这里,一个Message大部分内容都被分析了,我们知道了Message内部有一个全局池,保证了开发者可以不构建大量的消息,提高性能。我们也知道了一个消息何时入池,何时出池。OK,Message复用机制到此结束。
原文链接:http://www.jianshu.com/p/f6f357b3db89/comments/6135059
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
- Android源码解析Handler系列第(一)篇 --- Message全局池
- Android源码解析Handler系列第(一)篇 --- Message全局池
- Android源码解析Handler系列第(四)篇 --- 打破Handler那些困惑事儿
- Android源码解析Handler系列第(四)篇 --- 打破Handler那些困惑事儿
- Android源码解析之消息机制系列(一)Message
- Android源码解析Handler系列第(三)篇---深入了解Android的消息机制
- Android源码解析Handler系列第(二)篇--- ThreadLocal详解
- Android源码解析Handler系列第(五)篇 ---HandlerThread你用过吗?
- Android源码解析Handler系列第(二)篇--- ThreadLocal详解
- Android源码解析Handler系列第(五)篇 ---HandlerThread你用过吗?
- Android消息队列源码解析(Handler、Looper、Message、MessageQueue)
- Android Looper、Handler、Message源码解析
- Android中Handler源码解析(一)
- Handler,message,Looper源码解析
- handler发送message 源码解析
- Handler、Looper、Message源码解析
- 深入源码解析Android中的Handler,Message,MessageQueue,Looper
- 深入源码解析Android中的Handler,Message,MessageQueue,Looper
- 关于easyui combobox下拉框实现多选框的实现
- 新手求介绍ios开发视屏教程
- Shell echo命令
- 断点调试程序执行过程中执行的方法
- docker 基础安装配置以及swarm 基本操作使用
- Android源码解析Handler系列第(一)篇 --- Message全局池
- 51单片机串口通讯接收一串字符串
- coreplot 中怎样给固定点设置符号symbol(就是圆圈、五星、雪花那些),它里面是全部设置,我之需要Y值较大的设置,有没有好的方法,谢谢!
- Linux环境下安装RocketMQ(MetaQ)
- Shell printf 命令
- dpdk自动化编译设置脚本
- OC 与 JS 交互总结
- iOS培训之手机开发好学么?
- Ubuntu中安装redis及redis桌面管理器