Handler机制学习小结
一、 Handler的作用
先说明为何需要Handler。主线程需要执行某个耗时操作,并根据操作的结果相应地更新UI。但是当耗时的操作在5秒内无法完成时,Android会报出ANR错误。导致此ANR错误的原因是主线程在5秒内只干了一件事(而且还可能这一件事也没完成),那就不能响应用户的其它输入事件,解决的办法是我们可以让子线程来干这件耗时的事,然后把结果返回给主线程,主线程根据返回的结果来更新相应的UI(注:子线程并不与客户交互,所以它并不直接处理用户的输入事件,所以干一件事超过5秒是没有关系的)。
这里我们的问题转化为一个Android中线程通信的问题:主线程需要发个消息告诉子线程该干什么,子线程干完之后需要把工作成果反馈给主线程,主线程需要根据子线程反馈的结果来做相应的处理。这些工作都是Handler来完成的,这就是Handler存在的其中一个理由。
二、 Handler使用的流程
如何通过Handler来完成线程间的通信呢?分为以下几步:创建实例,发送事件消息,接受事件处理结果并更新UI。
1. 我们应该在主线程中创建一个Handler实例
Handler handler= new Handler();
Handler handler= new Handler(loop); //loop是一个Loop对象
Handler还有两个构造方法,这里没有列举。这里需要指出,这里列举出的两个方法有本质的区别,我们会在后面再讨论这里的区别。
2. 我们把要做的事封装成一个Message对象,把这个Message对象通过Handler的方法发送出去。
Handler中分发消息的一些方法
post(Runnable)
postAtTime(Runnable,long)
postDelayed(Runnable long)
sendEmptyMessage(int)
sendMessage(Message)
sendMessageAtTime(Message,long)
sendMessageDelayed(Message,long)
3. public voidhandleMessage(Message msg)
在这里我们可以接受到子线程完成事件后返回的结果,我们可以override这个方法来更新UI,当然如果不需要的话也可以不用这个方法,这取决于你的应用程序具体的需要。
这里可以看到,Handler的使用非常简单,下面我们就来看一个实例:我们的UI界面有两个Button,一个用来下载,将非常耗时,我们通过Thread的sleep方法来模拟这个耗时的过程,我们让它睡眠6秒钟,另一个Button非常简单,只是通过Toast显示信息而已。
Layout文件非常简单,略过不提。代码如下:
public class Handler01Activityextends Activity {
private int index = 0;
@Override
public void onCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//两个Button按键,dnloadBt和caculateBt
Button dnloadBt =(Button)findViewById(R.id.download);
Button caculateBt =(Button)findViewById(R.id.caculate);
//为一个Button绑定事件:事件到内容在下面的Runnable runnable的run方法中,我们把这个事件交给handler,利用post方法发送出去。
dnloadBt.setOnClickListener(newView.OnClickListener() {
public void onClick(View v){
handler.post(runnable);//第二步:通过Handler把消息发送出去,消息中携带需要处理到耗时事件
}
});
//为这个Button绑定事件:通过Toast显示一句话
caculateBt.setOnClickListener(newView.OnClickListener() {
public void onClick(View v){
// TODOAuto-generated method stub
Toast.makeText(Handler01Activity.this,"ok " + (index++), Toast.LENGTH_SHORT).show();
}
});
}
//第一步:在主线程中创建Handler实例,注意这里调用的构造方法!!
Handler handler = new Handler();
//这里描述了需要处理到耗时事件:Thread.sleep(6000),然后通过Toast显示一句话
Runnable runnable = new Runnable() {
public void run() {
// TODO Auto-generatedmethod stub
try {
Thread.sleep(6000);
} catch (InterruptedExceptione) {
// TODOAuto-generated catch block
System.out.println(e);
}
}
};
}
简单分析这个例子。
如果用户如下操作:先按dnloadBt,然后立刻按下caculateBt,也就是先命令执行很耗时的操作,然后立刻执行并不耗时的操作。我们知道,如果我们不使用Handler,那么这种操作一定会报ANR的错误,因为应用程序执行第一个操作需要6s,而相应第二个操作只能在6s之后,用户的第二个操作是不能够在5s内得到响应的,尽管第二个操作很简单。
现在我们使用了Handler机制,那么是否就可以避免ANR的错误出现呢?结果也不能,这让我非常疑惑,因为它并没有发挥它应有的作用。要想解决这个疑问,那么我们还需要对Handler机制进行更深入的学习和理解。
三、 Handler的消息管理机制
这里先提几个问题:主线程通过Hanler的消息分发方法把消息发送出去,发送到了哪里?子线程如何获得这些消息?获得这些消息后又如何处理,以完成主线程交代的任务?任务完成之后,子线程需要把结果反馈给主线程,并且主线程会用handleMessage方法去更新UI,但是谁来做这些事?怎么知道子线程已经完成了任务……
Fig.1 简单Handler示意图
图1中描述了如下信息:在主线程中创建Handler实例之后,Handler就获得了一个Looper对象,该对象也唯一对应地拥有了一个消息队列。Handler在主线程调用的所有发送消息的方法,都最终把消息发送到了Looper中的消息队列中。Looper的loop方法会从消息队列的队首取出消息,并对消息进行分类,交给相应的方法去处理(详细过程略)。这里存在一个需要仔细考虑的问题,那就是Handler中的Looper是怎么来的。重点考虑对比两种创建Handler的方法。
1. Handler()构造方法
publicHandler() {
//获取本线程的looper对象,默认将关联当前线程的looper
//如果本线程还没有设置looper,则会抛出异常
mLooper = Looper.myLooper();
// 重要,直接把关联looper的MQ作为自己的MQ,因此它的消息将发送到关联looper的MQ上
mQueue = mLooper.mQueue;
mCallback = null;
}
可以看到,该方法创建Handler的过程中,looper对象的获得是通过Looper.myLooper()方法。该方法的注释如下:Return theLooper object associated with the current thread. Returns null if the calling thread is notassociated with a Looper. 也就是它会返回与当前线程关联的Looper,如果当前线程没有关联任何Looper,那么则会返回空。有一点需明确:主线程一定关联了一个Looper(关联的过程是由系统完成的,具体可参考ActivityThread.java),而子线程一般是没有关联Looper的,所以我们可以在主线程中调用这个Handler的构造方法,但是在一个子线程中调用此法可能会抛出异常。
现在可以解释前面的实例程序中的疑问了:由于程序中是通过这个构造方法创建Handler实例的,所以Handler获取到的Looper其实是主线程的,那么消息队列也是主线程的,所以Handler只是把消息交到了主线程的消息队列中,实际还是主线程在处理这个耗时的消息,所以会有ANR报错。
2.Handler(Looper looper)构造方法
public Handler(Looper looper) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = null;
}
这个构造方法很简单,是有外部传递了一个looper对象,那么自然这个Handler的消息都会发送到这个looper对象的消息队列中,处理消息也是由这个looper对象所在的进程来处理。所以,这才是我们真正想要的异步处理方法。只有这样才能避免我们前面的实例碰到的ANR报错。接下来我们就来分析如何使用这种方式来解决我们的问题。
四、 利用Handler实现异步处理
既然已经明确我们应该调用Handler(Looperlooper)构造方法去创建Handler实例,同时此Looper对象应该是非主线程的。那么我们的想法应该是,首先创建一个子线程,然后为此子线程关联一个Looper对象,然后把这个Looper对象交给Handler,这样Handler发送的消息就能交给子线程来处理了。
创建子线程简单,关键是创建Looper对象。
private Looper() {
mQueue = new MessageQueue();
mRun = true;
mThread = Thread.currentThread();
}
Looper类只有这一个构造方法,但它是私有的,所以不能通过这个方法创建Looper对象。该类提供了另一个方法可以帮助我们,它的方法体内调用了这个唯一的构造方法。
public static void prepare() {
if (sThreadLocal.get() != null) {
//试图在有Looper的线程中再次创建Looper将抛出异常
throw new RuntimeException("Only one Looper may be created perthread");
}
sThreadLocal.set(new Looper());//核心语句
}
这样只是对该线程设置了looper,还需要调用Looper.myLooper()方法才能真正获得该子线程的looper对象。此时,还没有结束,我们还需要调用Looper的loop方法,这样这个looper才能够使用,它才可以不断地从消息队列中抽取消息并执行。
事实上,我们并不需要做的这么复杂,因为Android已经提供了一个类,在这个类里这些复杂的工作已经都完成了,我们只需要使用就可以。这个类就是HandlerThread,它从Thread继承。
HandlerThread handlerThread = newHandlerThread("yutao");
handlerThread.start();
Handler UIhandler = new Handler(handlerThread.getLooper());
只要通过这三行代码我们就创建了子线程,且将子线程有了自己的looper对象,并且把这个对象传递给了Handler,当Handler再发送消息时,都会交给子线程来进行异步处理啦。
五、 小结
下面简单总结一下Thread、Handler、Looper、MessageQueue之间的关联。
首先,一个线程最多只能有一个Looper,也可以没有Looper;主线程一般都具有Looper,是由系统帮助我们创建完成的,可以直接使用;我们自己新建的线程是没有Looper的,需要我们自己去创建,同时需要调用loop方法去启动;Android提供了HandlerThread类来方便我们实现Handler的异步处理,它是一个已经具备Looper的线程。
其次,一个线程可以创建一个或多个Handler;而每个Handler必须具备一个Looper,且在创建Handler的过程中就为Handler绑定了一个Looper;在线程中创建Handler有两种方式(其实有4个构造方法,这里只关心我们讨论的问题,故不考虑另外两种):new Handler()和new Handler(Loop looper);下面第一幅图中指出,以第一种方式创建时,由于未指定looper,故默认选择创建此Handler的线程的Looper,但是如果这个线程没有关联Looper,则会抛出异常,且这种方式的Handler不能实现异步处理,那么这种方式有什么用途,我暂时也不懂,网上说可以安排消息或Runnable 在某个主线程中某个地方执行;以第二种方式创建时,可以绑定另一个线程的Looper,那么这样就可以实现异步处理,这种方式的应用更常见,当然前提是那个线程已经关联Looper,否则也会抛出异常,更常见的我们会写一个子线程继承HandlerThread。
第三,一个Looper与一个MessageQueue一一对应,是一对一的关系;与Looper绑定的Handler的所有消息都会发送到这个消息队列中,Handler能够通过handleMessage方法处理的所有消息都是从这个消息队列里获取的。
第四,前面已经提到,一个Handler必须绑定一个Looper,且在创建Handler时就已绑定;但一个Looper可以为多个Handler共用;这里可能存在这样的问题,那就是如果多个Handler把消息发送到同一个Looper中,也就是发送到同一个消息队列中,那么在Handler的handleMessage方法得到的消息会是自己应该得到的吗?这个问题是这么处理的:每一个Message都有一个成员变量Handler target,所以当某个Handler发送消息时,都会在这个消息中指明这个消息的target对象是这个Handler,这样我们就能够判断某个消息是哪个Handler的了。
Fig.2通过newHandler()方法创建 Fig.3通过newHandler(Looper)方法创建
最后,讨论一下Handler中的消息。Handler中的Message可以分为两种,一种是有Runnable的,一种是没有Runnable。当Handler去post一个Runnable对象时,它会被封装成一个消息,封装的过程很简单:新建一个Message有对象,并为它的成员变量Runnablecallback赋值即可。这两种消息的根本区别在于,在消息队列中排队,轮到自己时,带Runnable的消息接下来会执行它的run方法,然后就结束了,而不带Runnable的消息将直接发送到Handler的handleMessage方法中去处理。我的理解是,当主线程有耗时工作需要处理时,把它放在Runnable中放到消息队列中交给子线程去处理,而子线程处理结束后,可以发送不带Runnable的消息到队列中,主线程可以在handleMessage中获取到子线程的工作成果并相应更新UI(此说法未得到验证,纯属个人理解)。