Android 消息机制及其源码分析
来源:互联网 发布:sublime text 3编译js 编辑:程序博客网 时间:2024/05/01 13:20
第一: 什么是Android消息机制
Android的消息机制主要是指,Handler的运行机制,Handler的运行机制还需要Looper和MessageQueue来构建消息循环系统.Handler运行机制并不是专门用来更新ui的,但是我们经常用来更新ui,前面的EventBus的这个传参框架也可以实现跟新ui的操作,具体链接:
EventBus的使用
为什么需要Android消息机制:
Android设备作为一台移动设备,不管是内存或者还是它的性能都会受到一定的限制:过多的使用内存会使内存溢出(OOM);另外一方面就是ANR异常(Application Not Responding)。在UI线程中如果5秒之内没有相应的输入事件或者是BroadcastReceiver中超过10秒没有完成返回的话就会触发ANR异常,这样就要求我们必须要在写代码的时候格外注意不能将大量耗时的操作放在UI线程中去执行,例如网络访问、数据库操作、读取大容量的文件资源、Handler的运行机制主要是为了方便开发者来在子线程中更新ui
第二;Handler的运行机制的大体描述
我们使用handler来更新ui的时候经常的操作在主线程中创建Handler,在子线程中通过handler来发送消息,最后在主线程中handlerMeaagae()来处理消息.我们根据这个过过程分析一边,首先是在Handler在主线程中创建的时候或获取当前线程的Looper(轮询器),而在主线程在mian()方法中已经为我们用Looper.prepareMainLooper()左边好了一个looper,Looper在创建的时候我们依据looper的源码可知,在looper创建会帮助我们new MessageQueue出来,并且保存了当期线程.Handler获取到looper之后通过Looper.looper()方法开启消息循环.
这里大家要注意几点:
Handler的主要作用:
将一个任务切换到某一个指定的线程中去执行.(后面会进一步的说明)
关于Looper
Looper是运行在创建Handler的所在的线程中,在这里是运行在主线程中,我们是在主线程中创建的Handler,而MessageQueue也是在主线程,最后子线程发送的消息其实是在MessageQueue中插入了一条消息,最后交给了Handler来分发消息.在此就是Handler完成了将子线程的任务切换到主线程中去执行了!.
第三: 依据源码分析Handler运行机制的
Handler运行机制需要Looper和MessageQueue;我们一一说明:
第一: 关于Looper的源码分析:
private Looper() {// 创建消息队列 mQueue = new MessageQueue(); mRun = true; mThread = Thread.currentThread(); }
我们可以看到,在创建Looper的同时完成的两件事情:
第一件:new MessageQueue
第二件事情: 获取当前线程
而在主线程中已经给我们准备好了,Looper,通过的就是Looper.prepareMainLooper(),在looper中还有一种准备Looper的方法,Looper.prepare()
/** Initialize the current thread as a looper. * This gives you a chance to create handlers that then reference * this looper, before actually starting the loop. Be sure to call * {@link #loop()} after calling this method, and end it by calling * {@link #quit()}. */ 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)); }
我们看出,Looper的prepare()方法实质上创建了Looper,并且将其存放到ThreadLocal中.
第二: 关于ThreadLocal的分析和线程单例的说明:
说说ThreadLocal这个类:进入源码看看:
这个类是一个泛型,主要的作用是线程中存贮数据的.
public class ThreadLocal<T> {//其他的代码}
优点是:指定的线程中存贮指定的数据,只用相应的线程能获取当初自己存贮的数据,其他线程获取不到,自己的理解就是,a线程存贮了数据b,线程c存贮了数据d,线程a在获取数据的时候只能获取数据b,而获取不到数据d,同理数据都只能是线程获取才能获取到.
线程内单例的实现原理: ThreadLocal提供两个方法: set和get方法我们看到上面的源码: 当准备好一个Looper的时候,其实是创建了一个Looper,而Looper的创建也完成了上面的两件事情,也就是new MessageQueue()和获取当前的线程.也就是ThreadLocal保存了当前线程的Looper,Looper中有保存了当前线程,根据ThreadLocal特点就实现了线程内的单例,相同线程获取的Looper是同一个对象.
消息循环的开启是通过Looper.looper()开启的,我们看一下子源码:
public static final void loop() { Looper me = myLooper(); 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(); while (true) {// 取消息,取不到就阻塞,取到就处理 Message msg = queue.next(); // might block //if (!me.mRun) { // break; //} if (msg != null) { if (msg.target == null) { // No target is a magic identifier for the quit message. return; } if (me.mLogging!= null) me.mLogging.println( ">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what ); msg.target.dispatchMessage(msg); if (me.mLogging!= null) me.mLogging.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("Looper", "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.recycle(); } } }
我们可以看出: looper()中是一个 while(true){},里面调用了MessageQueue的next()方法,
没有消息,next()会阻塞,间接导致了没有消息的时候looper()方法的阻塞.有消息会调用msg.target.dispatchMessage()方法来分发事件.
注意: msg.target就是sendMessge的那个handler对象,将消息交给dispatchMessage()来分发消息,而dispatchMessage是运行在创建Handler的使用的Looper中执行的,这样就是上面说的Handler的主要作用的实现.第三: 关于MessageQueue的分析:
俗称:消息队列;是指不是一个队列是一个单链表,具有在插入和删除上面有优势.提供两个方法插入消息的方法 enquenueMessage(),移除消息的方法:next()
源码如下:
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"); } final boolean needWake; 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; //Log.d("MessageQueue", "Enqueing: " + msg); Message p = mMessages;// 当前消息需要马上处理 if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; needWake = mBlocked; // new head, might need to wake up } else {// 当前消息不需要马上处理,根据时间重新排序 Message prev = null; while (p != null && p.when <= when) { prev = p; p = p.next; } msg.next = prev.next; prev.next = msg; needWake = false; // still waiting on head, no need to wake up } }// 根据消息是否需要马上处理,判断是否需要唤醒Looper取消息 if (needWake) { nativeWake(mPtr); } return true; }
子线程中sendMessage()其实是在MessageQueue中插入了一条消息.
next()方法源码:
final Message next() { int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } nativePollOnce(mPtr, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); final Message msg = mMessages;// 当前消息需要处理,返回消息 if (msg != null) { final long when = msg.when; if (now >= when) { mBlocked = false; mMessages = msg.next; msg.next = null; if (Config.LOGV) Log.v("MessageQueue", "Returning message: " + msg); return msg; } else {// 当前小时是延迟消息,计算下一次取消息的时间 nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE); } } else { nextPollTimeoutMillis = -1; }
有消息的使用,next返回消息,通过handler分发消息处理,没有将会阻塞.
第四: 关于Handler的分析
new Handler(){handleMessage(){}}public Handler() { // 给Looper赋值 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 = null; }
Handler在子线程中发送消息:
public final boolean sendEmptyMessage(int what) { return sendEmptyMessageDelayed(what, 0); }public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageDelayed(msg, delayMillis);}public final boolean sendMessageDelayed(Message msg, long delayMillis){ if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);}
在子线程中我们发送消息的调用:
senEmptyMessage()后面调用了 senEmptyMessageDelayed(),后调用了sendMessageDelayed(),这个会调用sendMessageAtTime();** * Causes the Runnable r to be added to the message queue. * The runnable will be run on the thread to which this handler is * attached. * * @param r The Runnable that will be executed. * * @return Returns true if the Runnable was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. */ public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); }再看一个public final boolean postDelayed(Runnable r, long delayMillis){ return sendMessageDelayed(getPostMessage(r), delayMillis);}public boolean sendMessageAtTime(Message msg, long uptimeMillis) { boolean sent = false; MessageQueue queue = mQueue; if (queue != null) { msg.target = this;// 把消息传递给消息队列 sent = queue.enqueueMessage(msg, uptimeMillis); } else { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); } return sent; }
最后都是调用sendMessageAtTimer();
Handler处理消息:public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
msg.callback就是上面的post的传递Runable对象;
mCallback是一个接口:/** * 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); }这也就是这种Handler的写法的由来:private Handler handler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { return false; } });
第四:主线程的消息循环模型(参考Android开发艺术探索):
主线程就是ActivityThread也就是我们经常说的UI,主线程的入口是main(),主主线程开启的是后帮助我们完成了几个关于消息循环的操作:
通过Looper.prepareMainLooper()创建主线程的Looper,Looper创建会new MessageQueue(),并且通过Looper.looper()开启了消息循环.
public static final void prepareMainLooper() { prepare(); setMainLooper(myLooper()); if (Process.supportsProcesses()) { myLooper().mQueue.mQuitAllowed = false; } }
主线程通过ApplicationThread和AMS进行进程见得通信,Ams以进程间通信的方式完成主线程的请求后回调ApplicationThread的Binder方法,之后ApplicationThread会像Handler发送消息,Handler的主要作用就是将一个任务切换到指定的线程中,也就是将ApplicationThread逻辑切换到主线程中,这个过程就是主线程的消息循环模型.
第五:常见的Handler消息机制的面试题和笔试题详解
问题一: 说一下子handler消息机制
回答: 上面的大体描述就是:
问题二;子线程中能不能new Handler?
回答:如果在子线程中直接new Handler()会抛出异常java.lang.RuntimeException: Can't create handler inside thread thathas not called 在没有调用Looper.prepare()的时候不能创建Handler,因为在创建Handler的时候需要获取Looper,出去主线程之外是不会提起创建好Looper的
问题三: 准备Looper的方法
回答: 第一使用一般的准备: Looper.perpare(),
第二: 获取主线程的looper getMainLooper()
问题四: MessageQueue存放在哪里
回答:java通过线程私有变量来保存looper信息。 也就是说一个线程最多有个一个Looper. 系统通过ThreadLocal方法来保存,在ThreadLocal中.
问题五: Handler和looper及其MessageQueue之间的对应关系:
回答:多个handler绑定了一个Looper, 一个looper绑定了一个MessageQueue.
问题六: 为什么我们不能直接在子线程中修改ui
回答: 这是因为Android中ui控件是线程不安全的,如果多线程中并发访问就可能导致UI控件处于不可预期的状态.
0 0
- Android 消息机制及其源码分析
- android消息机制源码分析
- Android 消息机制源码分析
- Android消息机制源码分析
- Android消息机制源码分析
- Android 消息机制源码分析
- Android消息机制源码分析
- Android消息机制源码分析
- Android消息机制源码分析
- Android中的消息机制-源码分析
- Android消息机制及源码分析
- Android消息机制 Handler源码分析
- Android异步消息机制及源码分析
- Android消息处理机制(源码分析为主)
- Android的消息处理机制源码分析
- 结合源码分析android的消息机制
- Android 消息循环机制源码分析
- Android 消息循环机制源码分析
- HTML5无插件多媒体Media——音频audio与视频video
- Android中dp,sp以及px之间的关系
- 制定个学习计划
- JavaScript图片轮播代码
- Oculus Rift CV1 驱动安装教程(内含Oculus官方Demo)
- Android 消息机制及其源码分析
- EL表达式
- Maven实践(三)---编写主代码
- 设计模式之装饰者模式(Decorator)
- 1.5输入若干个整数,求其最大值、最小值和平均值
- 深入浅出Java入门 之 运算符(2)
- Cookie的传递流程
- 最长回文子串—动态规划和Manacher算法(0(n)时间复杂度
- 17.03.04 字符串