Handler使用小结
个人概念里面handler用来更新UI。一直有一个问题困恼我,为什么我在主线程里面创建一个Handler不需要传递传递Looper,而在一个子线程里面必须调用Looper.prepare, Looper.loop。今天看了看源码,终于知道里面的原委。个人觉得一切和ThreadLocal有关,关于ThreadLocal,请阅读如下博客:Android的消息机制之ThreadLocal的工作原理。 简而言之,ThreadLocal和当前线程绑定,如果handler在UI线程里面创建,ThreadLocal已经和主线程绑定,即使handler传入为空,也可以拿到主线程的looper,代码如下,
mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } public static @Nullable Looper myLooper() { return sThreadLocal.get(); }
我们知道,在app启动时候,在ActivityThread里面的main函数被执行:
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")); } // End of event ActivityThreadMain. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); Looper.loop();
综合上面代码,looper已经在app启动的时候创建,所以 Looper.myLooper()取得的是UI主线程对应的looper,没有错误抛出。如果handler实在子线城里面创建:
new Thread(new Runnable() { @Override public void run() { Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); } }; } }).start();
可以很容易得出结论,子线程没有和ThreadLocal绑定,所以ThreadLocal里面的looper为空,如下异常抛出:
if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); }
既然有上面问题,如何在子线程里面创建handler呢?
new Thread(new Runnable() { @Override public void run() { Looper.prepare(); Handler handler = new Handler(Looper.myLooper()) { public void handleMessage(android.os.Message msg) { // XXX } }; Looper.loop(); } }).start();
继续看Looper.prepare:
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的set方法如下, 可以看到内部ThreadLocalMap已当前线程为key进行绑定。
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
这样当前线程和 sThreadLocal就绑定了,Looper.myLooper()得到的就是上面创建的 new Looper(quitAllowed)。
当然聪明的google意识到这个问题,所以有个HandlerThread可以省去手动调用prepare和loop的烦恼,核心代码如下:
public class HandlerThread extends Thread { @Override public void run() { mTid = Process.myTid(); Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1; } }