一、Handler消息传递机制初步认识:
(一)引入:
子线程没有办法对UI界面上的内容进行操作,如果操作,将抛出异常:CalledFromWrongThreadException为了实现子线程中操作UI界面,Android中引入了Handler消息传递机制,目的是打破对主线程的依赖性。
handler通俗一点讲就是用来在各个线程之间发送数据的处理对象。在任何线程中,只要获得了另一个线程的handler,则可以通过handler.sendMessage(message)方法向那个线程发送数据。基于这个机制,我们在处理多线程的时候可以新建一个thread,这个thread拥有UI线程中的一个handler。当thread处理完一些耗时的操作后通过传递过来的handler向UI线程发送数据,由UI线程去更新界面。
主线程:运行所有UI组件,它通过一个消息队列来完成此任务。设备会将用户的每项操作转换为消息,并将它们放入正在运行的消息队列中。主线程位于一个循环中,并处理每条消息。如果任何一个消息用时超过5秒,Android将抛出ANR。所以一个任务用时超过5秒,应该在一个独立线程中完成它,或者延迟处理它,当主线程空闲下来再返回来处理它。
(二)常用类:(Handler、Looper、Message、MessageQueue)
Message:消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理。
Handler:处理者,负责Message的发送及处理。使用Handler时,需要实现handleMessage(Message msg)方法来对特定的Message进行处理,例如更新UI等。Handler类的主要作用:(有两个主要作用)1)、在工作线程中发送消息;2)、在主线程中获取、并处理消息。
MessageQueue:消息队列,用来存放Handler发送过来的消息,并按照FIFO规则执行。当然,存放Message并非实际意义的保存,而是将Message串联起来的,等待Looper的抽取。
Looper:消息泵,不断地从MessageQueue中抽取Message执行。因此,一个MessageQueue需要一个Looper。
Thread:线程,负责调度整个消息循环,即消息循环的执行场所。
(三)、Handler、Looper、Message、MessageQueue之间的关系:
Handler,Looper和MessageQueue的关系
Looper和MessageQueue一一对应,创建一个Looper的同时,会创建一个MessageQueue;
而Handler与它们的关系,只是简单的聚集关系,即Handler里会引用当前线程里的特定Looper和MessageQueue;
在一个线程中,只能有一个Looper和MessageQueue,但是可以有多个Handler,而且这些Handler可以共享一个Looper和MessageQueue;
Message被存放在 MessageQueue中,一个 MessageQueue中可以包含多个Message对象。
【备注】
Looper对象用来为一个线程开启一个消息循环,从而操作MessageQueue;
默认情况下,Android创建的线程没有开启消息循环Looper,但是主线程例外。
系统自动为主线程创建Looper对象,开启消息循环;
所以主线程中使用new来创建Handler对象。而子线程中不能直接new来创建Handler对象就会异常。
子线程中创建Handler对象,步骤如下:
Looper.prepare();
Handler handler = new Handler() {
//handlemessage(){}
}
Looper.loop();
(四)、Handler类中常用方法:
handleMessage() 用在主线程中,构造Handler对象时,重写handleMessage()方法。该方法根据工作线程返回的消息标识,来分别执行不同的操作。
sendEmptyMessage() 用在工作线程中,发送空消息。
sendMessage() 用在工作线程中,立即发送消息。
代码执行顺序:sendMessage()-->sendMessageDelay()-->sendMessageAtTime()-->enqueneueMessage().;
(五)、Message消息类中常用属性:
arg1 用来存放整型数据
arg2 用来存放整型数据
obj 用来存放Object数据
what 用于指定用户自定义的消息代码,这样便于主线程接收后,根据消息代码不同而执行不同的相应操作。
使用Message需要注意4点:
1、Message虽然也可以通过new来获取,但是通常使用Message.obtain()或Handler.obtainMessage()方法来从消息池中获得空消息对象,以节省资源;
2、如果一个Message只需要携带简单的int型数据,应优先使用arg1和arg2属性来传递数据,这样比其他方式节省内存;
3、尽可能使用Message.what来标识信息,以便用不同的方式处理Message;
4、如果需要从工作线程返回很多数据信息,可以借助Bundle对象将这些数据集中到一起,然后存放到obj属性中,再返回到主线程。
二、Handler、Looper源码分析:
(一)、Handler的概念:
Handler是用于发送和处理消息和一个线程的MessageQueue相关联的Runable对象。
每个Handler实例关联到一个单一线程和线程的messagequeue。
当创建一个Handler,从你创建它的时候开始,它就绑定到创建它的线程以及对应的消息队列,handler将发送消息到消息队列,并处理从消息队列中取出的消息。
Handler的主要用途有两个:
(1)、在将来的某个时刻执行消息或一个runnable;
(2)、为运行在不同线程中的多个任务排队。
主要依靠以下方法来完成消息调度:
post(Runnable)、
postAtTime(Runnable, long)、
postDelayed(Runnable, long)、
sendEmptyMessage(int)、
sendMessage(Message)、
sendMessageAtTime(Message)、
sendMessageDelayed(Message, long)
【备注】
post方法是当到Runable对象到达就被插入到消息队列;
sendMessage方法允许你把一个包含有信息的Message插入消息队列,它会在Handler的handlerMessage(Message)方法中执行(该方法要求在Handler的子类中实现)。
当Handler post或者send消息的时候,可以在消息队列准备好的时候立刻执行,或者指定一个延迟处理或绝对时间对它进行处理,后两个是实现了timeout、ticks或者其他timing-based的行为。
当你的应用创建一个进程时,其主线程(UI线程)会运行一个消息队列,负责管理优先级最高的应用程序对象(Activity、广播接收器等)和任何他们创建的windows。你也可以创建自己的线程,通过handler与主线程进行通信,在新创建的线程中handler通过调用post或sendMessage方法,将传入的Runnable或者Message插入到消息队列中,并且在适当的时候得到处理。
(二)、Handler的用法:
当你实例化一个Handler的时候可以使用Callback接口来避免写自定义的Handler子类。这里的机制类似与Thread与runable接口的关系。
在Handler里面,子类要处理消息的话必须重写handleMessage()这个方法,因为在handler里面它是个空方法。
(三)、源码分析:
A、Handler.java:(3个属性,9个方法)
3个属性:
final MessageQueue mQueue;
final Looper mLooper;
final Callback mCallback;
9个方法:
public boolean handleMessage(Message msg);
public final Message obtainMessage()
public final boolean sendMessage(Message msg)
public final boolean sendEmptyMessage(int what)
public final boolean post(Runnable r)
public final boolean postAtTime(Runnable r, long uptimeMillis)
public void dispatchMessage(Message msg)
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
public final boolean sendMessageDelayed(Message msg, long delayMillis)
B、Looper.JAVA:(4个属性,4个方法)
每个ThreadLocal中只能有一个Looper,也就是说一个Thread中只有一个Looper
4个属性:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
final MessageQueue mQueue;
final Thread mThread;
private static Looper mMainLooper = null;
4个方法:
public static void prepare()
public static void prepareMainLooper()
public static void loop()
public static Looper myLooper()
C、Message.java:(8个属性,5个方法)
8个属性:
public int what;
public int arg1;
public int arg2;
public Object obj;
Handler target;
Message sPool;
int sPoolSize;
int MAX_POOL_SIZE=10;
5个方法:
public static Message obtain()
public void recycle()
public void setTarget(Handler target)
public Handler getTarget()
public void sendToTarget()
Tips:
MessageQueneue
Handle.sendMessage() -->sendMessageDelay()--> sendMessageAtTime()
-->enqueneueMessage()(消息加入消息队列)
以链表的形式,首先进来的判空赋值,next为空,以后进来的,根据delay的时间判断,写一个死循环,如果前面的时间更大就继续循环插到前面去,直到前面一个的时间小于当前消息的时间,跳出循环,以这种形式,将队列按照延时时间排列顺序。
Looper
在子线程中使用handle,new 对象之前必须 Looper.prepare(); 否则会报错“不能在没有looper的时候创建handler”,(源码先判断Looper是否为空,为空抛错),然后后面要Looper.loop();handler与looper是成对出现的,主线程new handler不报错是因为应用启动的时候,ActivityThread主线程会默认写了Looper.prepareMainLooper()
message.Obtain();取消息池里的空闲消息,如果没有空闲消息再创建message,可以复用
在子线程中调用handle的发送消息方法,处理消息是在创建handle的线程。
looper是跟handler成对出现的,利用ThredLocal,ThreadLocalMap以键值对的形式存储,key为线程对象Thread.currentThread()能保证唯一,value为设置的值。保证唯一性。
总结
由sendMessage开始,最后都是调用enqueueMessage方法,到此为止我们一般都是在新线程调用;
接下来Looper类的loop方法会在当前线程(如果是更新UI则在主线程)从MessageQueue中获取最新消息,通过msg.target.dispatchMessage(msg);调用到Handler中来,然后调用到handlemessage(msg);
由此,Handler跨线程最重要的是在当前线程(初始化Handler的线程)进行loop轮询,而变量是可以在不同线程访问的,所以Handler可以在其他线程向MessageQueue中插入数据,而loop则在当前线程不断去取数据,取得数据就回调,达到跨线程的目的。
网友评论