Android中的Handler注意事项

一:Handler的作用

1.实现线程之间的通信:在非UI线程(子线程)中完成耗时操作,在UI线程(主线程)中更新UI操作。

2.通过postDelayed(Runnable r,millies m)方法在主线程中发送延迟消息。

 

二:Handler、Looper、Message、MessageQueue

1.在一个线程中只能创建一个Looper,一个Looper只能创建一个MessageQueue。但是在一个线程中可以创建多个Handler实例。

2.一个Handler实例只能绑定一个Looper和MessageQueue。

3.主线程会默认自动创建一个Looper和MessageQueue,所以可以直接在主线程创建Handler实例。而子线程中不会默认创建Looper和MessageQueue,所以在子线程中创建Handler实例时需要先调用Looper.prepare()方法在子线程创建一个Looper。

4.在创建Handler实例时如果不指定绑定的Looper,则系统默认绑定当前线程(创建Handler实例的线程)的Looper。所以在主线程创建Handler实例时可以直接使用"Handler handler = new Handler()"语句,其构造参数可以为空。在子线程创建Handler实例时需要先调用Looper.prepare()方法和Looper.loop()方法创建并打开一个Looper,然后再使用"Handler handler = new Handler(Looper.myLooper()/getMainLooper())"语句,其中构造参数Looper.myLooper()表示绑定该线程自己的Looper,getMainLooper()表示绑定主线程的Looper。

5.在子线程中调用handler.sendMessage()系列方法会将Message消息对象发送到该handler实例绑定的Looper对象对应的MessageQueue中,Looper从自己的MessageQueue中将Message消息对象取出,并通过Message的target标志识别出发送该Message的Handler(因为一个Looper可以绑定多个Handler实例),最后将该Message消息对象发送给该Handler的handleMessage()方法进行处理。

6.调用handler.post(Runnable r)系列方法会将Runnable对象打包封装成一个消息对象,并将该消息对象发送给该handler实例绑定的Looper对象对应的MessageQueue中,Looper从MessageQueue中将该消息对象取出时,当前线程(Looper绑定的线程)会直接执行Runnable对象里面的run()方法中的逻辑代码。

7.当Looper从MessageQueue中取出一个Message消息时:(1)如果msg.callback属性不为空,则代表使用了post(Runnable r)方法发送消息,直接执行Runnable对象内的run()方法。(2)如果msg.callback属性为空,则代表使用了sendMessage(Message msg)方法发送消息,此时通过msg.target标志判断该消息属于哪一个handler实例,然后调用该实例的handleMessage(msg)方法。

8.将Message加入到MessageQueue中是通过调用MessageQueue对象的enqueueMessage()方法。Looper从MessageQueue中取出一个Message后,通过Message.target来判断该Message所属的handler实例,target是对该handler实例的一个引用,通过调用target.dispatchMessage(msg)方法回调到该handler实例的handleMessag()方法。

 

三:扩展

1.为什么必须在UI线程(主线程)中进行UI操作?

由于Android中的UI操作是没有加锁的(加锁即一次只能允许一个对象进行某种操作),多个线程进行同一个UI操作就有可能会出现冲突问题,因此UI操作是线程不安全的,所以Android中规定只能在UI线程(主线程)中进行UI操作。

2.什么时候在非UI线程(子线程)进行UI操作不会报错?

在主线程的Activity调用onResume()方法之前的生命周期内创建线程进行UI操作时不会报错,即onCreate()—>onStart()—>onResume()这个生命时间段内。所以如果在onCreate()方法内创建子线程更新UI操作并不会报错。

原因:之所以在子线程进行UI操作会报错,是因为在更新UI操作时ViewRootImpI会调用checkThread()方法判断当前线程是否是UI线程(主线程),如果不是就会弹出异常。而ViewRootIMPI是在调用onResume()方法之后才会被创建,所以在onResume()方法之前并不会进行线程检查操作,从而可以更新UI。

3.当extends Thread或implements Runnable时是创建了一个子线程,如果只是使用匿名Runnable对象并不一定是创建了子线程,比如在主线程中调用handler.post(Runnable r)方法只是把Runnable对象封装成Message消息对象,并将其post到主线程的Looper对应的MessageQueue中,当Looper取出该消息对象时直接在主线程执行该消息对象内的run()方法逻辑代码。

 

四:盗用一张图来表述handler运行过程

Android中的Handler注意事项