从子线程不能直接新建一个Handler对象来剖析android的Handler机制

来源:互联网 发布:爬虫软件干嘛用 编辑:程序博客网 时间:2024/05/18 01:57

从子线程不能直接新建一个Handler对象来剖析android的Handler机制

前言

错误案例:在子线程中新建Handler报错:

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

代码案例:

@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);    new Thread(){        @Override        public void run() {            new Handler(){                @Override                public void handleMessage(Message msg) {                    super.handleMessage(msg);                }            };        }    }.start();}

一、异常提示定位

首先我们到Handler类中去寻找错误提示的语句,在Handler的构造函数

 public Handler(Callback callback, boolean async)

里面看到

mLooper = Looper.myLooper();    if (mLooper == null) {        throw new RuntimeException(            "Can't create handler inside thread that has not called Looper.prepare()");    }

发现之所以不能再子线程中新建Handler对象的原因是用为Looper对象为null

再点开Looper.myLooper();的源代码: 
发现

 public static Looper myLooper() {    return sThreadLocal.get();}

Looper对象是存放在

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); 

中这个对象是以线程的ID为键值,所以get方法取出的都是当前线程对应的Looper对象

所以之所以不能再子线程中定义Handler对象的原因就是用为我们没有主动的新建一个Looper对象并放到sThreadLocal 中


进一步思考

如何在子线程中新建一个Looper对象?我们去查看Looper的public static void 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));}

发现调用这个方法即可新建一个Looper对象并放入该线程的sThreadLocal中

而sThreadLocal,是一个静态变量。在Looper.class被加载进内存的时候就已经初始化了,所以要想在子线程中新建一个Handler对象,可以用一下写法。

class LooperThread extends Thread {   public Handler mHandler;   public void run() {       Looper.prepare();       mHandler = new Handler() {           public void handleMessage(Message msg) {              // process incoming messages here          }        };         Looper.loop();  }

当我们调用Looper.looper()方法开始,Looper就会不断了去轮询MessageQueue.这是一个先入先出的栈结构。

public static void loop() {      …… for (;;) {         //此处如果queue没有更新的msg了就会阻塞线程        Message msg = queue.next(); // might block        if (msg == null) {            // No message indicates that the message queue is quitting.            return;        }       ……      //执行msg        msg.target.dispatchMessage(msg);      ……      //msg重置        msg.recycleUnchecked();    }    }

注释表明当消息池为空的时候,轮询器会阻塞在那里,不会有额外的性能支出。在此还提出几个问题待以后再来讨论。 
问题一:轮询器被阻塞后又是谁来负责唤醒呢? 
问题二:轮询器所在的线程是哪个线程,这个线程有什么特点,主要用来干什么? 
问题三:为什么在主线程就可以直接新建Handler,主线程的Looper又是在什么地方初始化的呢?

0 0