Android线程的Looper相关知识
Android线程的Looper,Handler相关知识
Android中的Looper
类,是用来封装消息循环和消息队列的一个类,用于在android线程中进行消息处理。Handler
其实可以看做是一个工具类,用来向消息队列中插入消息的。
Android官方文档中Looper的介绍: Class used to run a message loop for a thread. Threads by
default do not have a message loop associated with them; to create one, call prepare() in
the thread that is to run the loop, and then loop() to have it process messages until the loop is stopped.
Most interaction with a message loop is through the Handler class.
This is a typical example of the implementation of a Looper thread, using the separation
of prepare() and loop() to create an initial Handler to communicate with the Looper.
Looper实现原理
1. Looper可以理解为一个类似轮询器
2. Looper在创建的时候,会自动创建一个MessageQueue(消息队列)。
3. 将内部线程对象指向自动创建的线程。
4. 然后当Looper开启的时候,去不断遍历“询问”消息队列,如果没有消息,队列为空,那么就继续轮询
。如果有消息进入队列,则对消息进行处理,回调handler的handlemessage方法进行处理
Looper创建的流程
-
Looper类用来为一个线程开启一个消息循环。 默认情况下android中新诞生的线程是没有开启消息循环的。(主线程除外,主线程系统会自动为其创建Looper对象,开启消息循环。) Looper对象通过MessageQueue来存放消息和事件。一个线程只能有一个Looper,对应一个MessageQueue。
-
通常是通过Handler对象来与Looper进行交互的。Handler可看做是Looper的一个接口,用来向指定的Looper发送消息及定义处理方法。 默认情况下Handler会与其被定义时所在线程的Looper绑定,比如,Handler在主线程中定义,那么它是与主线程的Looper绑定。
mainHandler = new Handler()
等价于new Handler(Looper.myLooper())
.Looper.myLooper()
:获取当前进程的looper对象,类似的Looper.getMainLooper()
用于获取主线程的Looper对象。 -
在非主线程中直接new Handler() 会报如下的错误:
E/AndroidRuntime( 6173): Uncaught handler: thread Thread-8 exiting due to uncaught exception E/AndroidRuntime( 6173): Java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
原因是非主线程中默认没有创建Looper对象,需要先调用
Looper.prepare()
启用Looper。
Looper.prepare()
相关代码:/** * 初始化Looper,调用loop()方法开始循环,调用quit()退出 * 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) {//一个线程只能有一个Looper throw new RuntimeException("Only one Looper may be created per thread"); } //保存Looper sThreadLocal.set(new Looper(quitAllowed)); } /** * 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()} * 初始化主线程的Looper,不要调用。因为开启主线程的时候系统已经默认开启Looper了再次调用会报异常 */ public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }
//构造函数 private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed);//创建消息队列 mThread = Thread.currentThread();//绑定当前线程 }
-
Looper.loop()
让Looper开始工作,从消息队列里取消息,处理消息。注意:写在
Looper.loop()
之后的代码不会被执行,这个函数内部应该是一个循环,当调用mHandler.getLooper().quit
()后,loop才会中止,其后的代码才能得以运行。
Looper.loop()
源码:/** * 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 (;;) { // 从消息队列中获取新的消息,当没有新消息的时候会在queue.next()方法中进行循环遍历 //直到有新的消息或者调用Looper.quit() Message msg = queue.next(); if (msg == null) {//如果返回的消息为空就表示已经调用MessageQueue.quit();并且已经MessageQueue.dispose() // No message indicates that the message queue is quitting. //Return here if the message loop has already quit and been disposed. return; } //msg.target 是Handler对象,这里进行消息的分发 msg.target.dispatchMessage(msg); // 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); } //Message对象回收 msg.recycleUnchecked(); } }
Looper.quit()
源码/** * Quits the looper.退出(会有消息没有处理完毕就退出) * <p> * Causes the {@link #loop} method to terminate without processing any * more messages in the message queue. * </p><p> * Any attempt to post messages to the queue after the looper is asked to quit will fail. * For example, the {@link Handler#sendMessage(Message)} method will return false. * </p><p class="note"> * Using this method may be unsafe because some messages may not be delivered * before the looper terminates. Consider using {@link #quitSafely} instead to ensure * that all pending work is completed in an orderly manner. * </p> * * @see #quitSafely */ public void quit() { mQueue.quit(false); } /** * Quits the looper safely.安全退出(消息处理完毕退出) * <p> * Causes the {@link #loop} method to terminate as soon as all remaining messages * in the message queue that are already due to be delivered have been handled. * However pending delayed messages with due times in the future will not be * delivered before the loop terminates. * </p><p> * Any attempt to post messages to the queue after the looper is asked to quit will fail. * For example, the {@link Handler#sendMessage(Message)} method will return false. * </p> */ public void quitSafely() { mQueue.quit(true); }
-
基于以上知识,可实现主线程给子线程(非主线程)发送消息。把下面例子中的mHandler声明成类成员,在主线程通过mHandler发送消息即可。
class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here } }; //这里可以做两个修改UI的操作 //1,Toast可在这里显示 //2,Dialog对话框可以显示 //3,Snackbar可在非UI线程中调用显示,不需要Looper.perpare().因为它的Hander调用的主线程Looper Looper.loop(); //在调用looper.quit()之前是不会被调用的 } }