为什么在子线程中创建handler时会报以下错误
Can’t create handler inside thread that has not called Looper.prepare()
正如我们常常使用Handler从子线程中发送一个消息到UI线程中去修改UI界面,同样,也可以在子线程中创建Handler,从主线程中获取数据后利用handler发送并处理消息,让上传数据等耗时操作运行在同一条子线程中。不过,在子线程中创建Hanler有一点小小的不同:
public void init(Context con) {
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
mHandler = new Handler(Looper.myLooper());
Looper.loop();
}
}).start();
}
这里在创建hander的前后分别调用了两个方法Looper.prepare()和Looper.loop();这两个方法分别是在当前线程中初始化一个Looper轮询器和使Looper轮询器开始轮询,Looper的作用是不断的轮询当前线程MessageQueue消息队列中的消息,然后交给Handler去处理。要使handler消息机制开始运作,就必须使当前线程生成一个Looper(一个线程只能有一个Looper与之关联),那为什么平时我们通过主线程中创建的handler去修改UI界面的时候只需要初始化一个handler就可以,而从来没管过Looper呢,这要从app的入口说起。
初学Android的时候以为一个应用的入口就是Activity的OnCreate方法,其实,android应用程序的入口理解为ActivityThread中的main方法更为合适一点(还是不准确)。ActivityThread就是应用程序的主线程,打开它的main方法可以看到
public static void main(String[] args) {
SamplingProfilerIntegration.start();
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
Security.addProvider(new AndroidKeyStoreProvider());
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
无关的代码暂且不看,代码的第14行和第26行,在应用程序初始化的时候,系统早已经帮我们调用了这两个方法为主线程关联了一个Looper并使它轮询。我们进一步打开Looper.prepareMainLooper();方法
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
在这个方法中,第二行代码的作用是初始化一个Looper,接下来进行判断,如果主线程中已经有一个Looper与它相关联,则会抛出异常(所以一个线程只能有一个Looper与之关联),如果没有,则调用myLooper方法将主线程与当前线程的Looper相关联。我们先看Looper的初始化:prepare(false);方法的实现
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));
}
ThreadLocal是一个方便解决多线程并发问题的工具类,ThreadLocal详解,这里不用对它理解多透彻,简单来说,它可以为每一个使用它的线程提供一个独立的副本,而不会被其他线程影响。比如在线程A中调用ThreadLocal的set方法,它会将我们set方法中传递参数对象的引用做一个记录,然后在线程A中,可以通过ThreadLocal的get方法将其取出,但在线程B中就无法获取。然后再看Looper的prepare()方法,可以看出执行流程为:如果当前线程存在Looper对象,则抛出异常,如果不存在,则初始化一个Looper对象并保存到ThreadLocal。
最后看sMainLooper = myLooper();方法:
public static Looper myLooper() {
return sThreadLocal.get();
}
很简单的一行代码,将当前线程的Looper对象取出,赋值给sMainLooper作为主线程的Looper。至此,ActivityThread中的Looper.prepareMainLooper()方法执行完成,系统调用Looper.Loop()方法使Looper轮询器开始轮询消息。