Android中的Looper,Handler及HandlerThread简析
程序员文章站
2022-05-15 23:17:50
...
Can’t create handler inside thread that has not called Looper.prepare()
毕业后在深圳一家公司工作,目前公司在做的是与android相关的项目,有Android源码。
这几周,出现几次同事在使用Handler的时候,在调用时抛出RuntimeException的错误,异常消息如下:Can’t create handler inside thread that has not called Looper.prepare() ,代码是在Handler handler = new Handler()的这一行。
以下内容是我的理解,如果理解有误,还请指出以便修正,以免我误导到他人。
那么这个问题要怎么解决呢?
从异常信息来看,是说在线程里创建Handler对象前没有调用Looper.prepare()方法。
首先,我们得知道,Looper是用来为线程创建一个消息循环的。而在Thread对象当中,默认是不与Looper绑定的,也就是默认是没有Looper对象的。
Android当中,对线程和消息队列进行了封装,即Handler类。它是一个可以用来处理循环消息队列的线程对象,在其构造方法中,部分代码如下(正好公司项目里有android源码,就看下代码吧,在frameworkd/base/core/java/android/os/下):
它通过调用Looper.myLooper()来获取当前线程的Looper对象,如果获取的Looper对象为null,就抛出这个异常了。这里顺便说一下一个误区,我发现有一些人会以为Handler就是一个多了消息队列的Thread,以为Handler handler = new Handler(),会创建一个新线程。其实并不是的,上面已经提到,Handler通过无参构造方法构造出来的对象,使用的是当前线程的Looper对象,所以通过它执行的代码,其实是在当前进程执行。
既然如此,那么我就想到解决的方法了。就是通过HandlerThread类。代码如下:
果然问题解决。这里构造的Handler对象,它是在一个新的线程当中执行的。
现在再来看看,Looper.myLooper()方法,代码如下:
声明代码在Looper类当中,如下:
上面提到,Looper对象是用来为线程执行一个消息循环的,而本身具备消息队列的Handler需要有一个Looper对象,所以它会提示你要在创建Handler对象前调用Looper.prepare()方法。该方法代码如下:
该方法创建一个Looper对象,再存入ThreadLocal对象。调用它之后,再在Thread中创建Handler对象,也就不会有获取到的Looper对象为null的异常了。
但是在这里我还有疑问,疑问如下:
1,ThreadLocal是什么?
2,Handler和Looper如何实现消息循环队列?
3,HandlerThread在什么时候创建一个Looper对象?
于是继续探索。
查java api可知,ThreadLocal提供了线程局部变量,所以通过它来保存当前线程的消息循环队列Looper对象是再合适不过的了。
对于第二个问题,我们可以看Looper类的声明,代码如下:
里面有一个消息队列,要发送的消息都通过Handler被加入在里面。而在Looper类当中,则通过loop方法里的死循环,不断地去读这个消息队列,代码如下:
那么,哪里会调用Looper.loop()方法呢?我在frameworks/base/core/java/android/os目录下搜了一下,结果如下:
主线程的Looper对象的loop()通过ActivityThread来调用,ActivityThread是程序的入口,其提供了main静态方法。当loop()被调用之后,它便通过一个死循环不断读取消息队列中的对象(调用 MessageQueue.next()方法),直到它读取到的Message对象为null。通过调用Looper.quit()方法可以退出,因为该方法会调用消息队列的quit()方法,而消息队列MessageQueue对象当退出时会在next()方法中返回null。
而对于HandlerThread调用loop()方法,将在下面谈。
下面说一下HandlerThread类。HandlerThread类用来开启一个含Looper对象的线程。它继承自Thread,在run()方法中调用Looper.prepare()来初始化Looper对象。代码如下:
在run方法中调用Looper.prepare(),所以这也就是在上面代码中,为什么对HandlerThread对象调用start()方法之后,才去获得它的looper来创建Handler对象。而HandlerThread对象也在run方法结束前,调用Looper.loop()方法,开始消息循环。
毕业后在深圳一家公司工作,目前公司在做的是与android相关的项目,有Android源码。
这几周,出现几次同事在使用Handler的时候,在调用时抛出RuntimeException的错误,异常消息如下:Can’t create handler inside thread that has not called Looper.prepare() ,代码是在Handler handler = new Handler()的这一行。
以下内容是我的理解,如果理解有误,还请指出以便修正,以免我误导到他人。
那么这个问题要怎么解决呢?
从异常信息来看,是说在线程里创建Handler对象前没有调用Looper.prepare()方法。
首先,我们得知道,Looper是用来为线程创建一个消息循环的。而在Thread对象当中,默认是不与Looper绑定的,也就是默认是没有Looper对象的。
Android当中,对线程和消息队列进行了封装,即Handler类。它是一个可以用来处理循环消息队列的线程对象,在其构造方法中,部分代码如下(正好公司项目里有android源码,就看下代码吧,在frameworkd/base/core/java/android/os/下):
110 public Handler() { 111 this(null, false); 112 } ... 144 public Handler(Looper looper, Callback callback) { 145 this(looper, callback, false); 146 } ... 185 public Handler(Callback callback, boolean async) { 186 if (FIND_POTENTIAL_LEAKS) { 187 final Class<? extends Handler> klass = getClass(); 188 if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && 189 (klass.getModifiers() & Modifier.STATIC) == 0) { 190 Log.w(TAG, "The following Handler class should be static or leaks might occur: " + 191 klass.getCanonicalName()); 192 } 193 } 194 195 mLooper = Looper.myLooper(); 196 if (mLooper == null) { 197 throw new RuntimeException( 198 "Can't create handler inside thread that has not called Looper.prepare()"); 199 } 200 mQueue = mLooper.mQueue; 201 mCallback = callback; 202 mAsynchronous = async; 203 }
它通过调用Looper.myLooper()来获取当前线程的Looper对象,如果获取的Looper对象为null,就抛出这个异常了。这里顺便说一下一个误区,我发现有一些人会以为Handler就是一个多了消息队列的Thread,以为Handler handler = new Handler(),会创建一个新线程。其实并不是的,上面已经提到,Handler通过无参构造方法构造出来的对象,使用的是当前线程的Looper对象,所以通过它执行的代码,其实是在当前进程执行。
既然如此,那么我就想到解决的方法了。就是通过HandlerThread类。代码如下:
HandlerThread t = new HandlerThread("test"); t.start(); Handler handler = new Handler(t.getLooper());
果然问题解决。这里构造的Handler对象,它是在一个新的线程当中执行的。
现在再来看看,Looper.myLooper()方法,代码如下:
162 public static Looper myLooper() { 163 return sThreadLocal.get(); 164 }通过调用ThreadLocal对象的get()方法来返回Looper对象。
声明代码在Looper类当中,如下:
56 // sThreadLocal.get() will return null unless you've called prepare(). 57 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
上面提到,Looper对象是用来为线程执行一个消息循环的,而本身具备消息队列的Handler需要有一个Looper对象,所以它会提示你要在创建Handler对象前调用Looper.prepare()方法。该方法代码如下:
66 /** Initialize the current thread as a looper. 67 * This gives you a chance to create handlers that then reference 68 * this looper, before actually starting the loop. Be sure to call 69 * {@link #loop()} after calling this method, and end it by calling 70 * {@link #quit()}. 71 */ 72 public static void prepare() { 73 prepare(true); 74 } 75 76 private static void prepare(boolean quitAllowed) { 77 if (sThreadLocal.get() != null) { 78 throw new RuntimeException("Only one Looper may be created per thread"); 79 } 80 sThreadLocal.set(new Looper(quitAllowed)); 81 }
该方法创建一个Looper对象,再存入ThreadLocal对象。调用它之后,再在Thread中创建Handler对象,也就不会有获取到的Looper对象为null的异常了。
但是在这里我还有疑问,疑问如下:
1,ThreadLocal是什么?
2,Handler和Looper如何实现消息循环队列?
3,HandlerThread在什么时候创建一个Looper对象?
于是继续探索。
查java api可知,ThreadLocal提供了线程局部变量,所以通过它来保存当前线程的消息循环队列Looper对象是再合适不过的了。
对于第二个问题,我们可以看Looper类的声明,代码如下:
53 public class Looper { 54 private static final String TAG = "Looper"; 55 56 // sThreadLocal.get() will return null unless you've called prepare(). 57 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); 58 private static Looper sMainLooper; // guarded by Looper.class 59 60 final MessageQueue mQueue; 61 final Thread mThread; 62 volatile boolean mRun; 63 64 private Printer mLogging; 65 ... }
里面有一个消息队列,要发送的消息都通过Handler被加入在里面。而在Looper类当中,则通过loop方法里的死循环,不断地去读这个消息队列,代码如下:
107 /** 108 * Run the message queue in this thread. Be sure to call 109 * {@link #quit()} to end the loop. 110 */ 111 public static void loop() { 112 final Looper me = myLooper(); 113 if (me == null) { 114 throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); 115 } 116 final MessageQueue queue = me.mQueue; 117 118 // Make sure the identity of this thread is that of the local process, 119 // and keep track of what that identity token actually is. 120 Binder.clearCallingIdentity(); 121 final long ident = Binder.clearCallingIdentity(); 122 123 for (;;) { 124 Message msg = queue.next(); // might block 125 if (msg == null) { 126 // No message indicates that the message queue is quitting. 127 return; 128 } 129 130 // This must be in a local variable, in case a UI event sets the logger 131 Printer logging = me.mLogging; 132 if (logging != null) { 133 logging.println(">>>>> Dispatching to " + msg.target + " " + 134 msg.callback + ": " + msg.what); 135 } 136 137 msg.target.dispatchMessage(msg); 138 139 if (logging != null) { 140 logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); 141 } 142 143 // Make sure that during the course of dispatching the 144 // identity of the thread wasn't corrupted. 145 final long newIdent = Binder.clearCallingIdentity(); 146 if (ident != newIdent) { 147 Log.wtf(TAG, "Thread identity changed from 0x" 148 + Long.toHexString(ident) + " to 0x" 149 + Long.toHexString(newIdent) + " while dispatching to " 150 + msg.target.getClass().getName() + " " 151 + msg.callback + " what=" + msg.what); 152 } 153 154 msg.recycle(); 155 } 156 }
那么,哪里会调用Looper.loop()方法呢?我在frameworks/base/core/java/android/os目录下搜了一下,结果如下:
phoenix@rtkPhoenix:~/branch/workspace/kernel/android/JB/frameworks/base/core/java/android$ jgrep "loop()" ./app/ActivityThread.java:5039: Looper.loop(); ./os/Looper.java:49: * Looper.loop(); ./os/Looper.java:69: * {@link #loop()} after calling this method, and end it by calling ./os/Looper.java:111: public static void loop() { ./os/HandlerThread.java:60: Looper.loop(); ./webkit/WebSyncManager.java:90: Looper.loop(); ./webkit/WebCoreThreadWatchdog.java:224: Looper.loop(); ./webkit/WebViewCore.java:812: Looper.loop();
主线程的Looper对象的loop()通过ActivityThread来调用,ActivityThread是程序的入口,其提供了main静态方法。当loop()被调用之后,它便通过一个死循环不断读取消息队列中的对象(调用 MessageQueue.next()方法),直到它读取到的Message对象为null。通过调用Looper.quit()方法可以退出,因为该方法会调用消息队列的quit()方法,而消息队列MessageQueue对象当退出时会在next()方法中返回null。
而对于HandlerThread调用loop()方法,将在下面谈。
下面说一下HandlerThread类。HandlerThread类用来开启一个含Looper对象的线程。它继承自Thread,在run()方法中调用Looper.prepare()来初始化Looper对象。代码如下:
51 public void run() { 52 mTid = Process.myTid(); 53 Looper.prepare(); 54 synchronized (this) { 55 mLooper = Looper.myLooper(); 56 notifyAll(); 57 } 58 Process.setThreadPriority(mPriority); 59 onLooperPrepared(); 60 Looper.loop(); 61 mTid = -1; 62 }
在run方法中调用Looper.prepare(),所以这也就是在上面代码中,为什么对HandlerThread对象调用start()方法之后,才去获得它的looper来创建Handler对象。而HandlerThread对象也在run方法结束前,调用Looper.loop()方法,开始消息循环。
上一篇: 美女们的各种搞笑姿势,舒服是最重要滴!
下一篇: 给力的爆笑图片,给春节添点乐趣。
推荐阅读
-
Android中的Handler、Looper、Message、MessageQueue之间流程关系
-
android中message、messageQueue、Handler、looper的关系
-
Android系统中Thread,Looper,MessageQueue,Message,Handler相互关系的简单分析
-
android Looper HandlerThread MessageQueue Handler Message(完善中......)
-
Android中的消息系统————Handler,MessageQueue与Looper
-
Android中的消息机制一(Handler、Looper、ThreadLocal、MessageQueue)
-
Android中Handler、Thread、HandlerThread三者的区别
-
Android中的Looper,Handler及HandlerThread简析
-
Android中Handler、Thread、HandlerThread三者的区别