Android中的Handler和AsyncTask的区别

目录(?)[+]

问题的由来

最近老看见有人问Android中的Thread与AsyncTask的区别,因此研究了一下,总结如下。 
第一,如果真心想弄清楚这两个的具体用法,最好的方法是把Message、MessageQueue、Looper、Handler、HandlerThread和AsyncTask这六个类的源代码全部看懂。 
第二,如果觉得困难,那么就来看看我的这篇总结文章吧。

Handler类,可以理解为消息/任务处理者,Handler类是为了与其他几个类一起完成Android的消息循环处理过程,是必不可少的一个类。它主要有两个用途:一是为了在将来某个时间点处理一个消息或者执行一个任务;二是将一个任务放入队列,以便它可以在另外的线程中执行。

AsyncTask类,可以理解为异步任务执行者;这个类的设计目的很明确,就是为了“执行一个较为耗时的异步任务(最多几秒钟),然后更新界面”。这种需求本可以使用Handler和Thread来实现,但由于编码较为复杂,因此Android提供了AsyncTask类。

几个规则

正式开始讨论Handler和AsyncTask之前,有几个Android的规则要再强调一次:

  1. 只能在UI线程中访问界面;
  2. UI线程被阻塞(大概5秒钟)后会导致ANR(Application Not Responding)错误。

因此我们要使用Handler来创建消息循环,或者使用AsyncTask来创建异步任务来进行操作。

Handler类的使用场景

消息循环三兄弟Handler、Looper和HandlerThread

Handler用来和Looper、HandlerThread一起建立一个具有消息循环的子线程。如果这三个类中有一个你不理解,那么你三个都不会理解,也不会理解Android的消息循环与处理机制。

Looper环形使者(顺便说一下这个电影很好看):Looper中带有一个MessageQueue,即消息队列,Looper负责轮询此消息队列,将消息取出后交由Handler来处理。 
HandlerThread消息循环线程:即Handler执行的线程,此线程大部分时间都在运行Looper.loop()方法,即消息轮询方法;它会通过getLooper方法返回一个Looper对象,Handler需要使用此对象作为参数创建对象。 
Handler处理者:负责接收消息、发送消息(sendMessage等方法)和处理消息(handleMessage方法),我们需要重载handleMessage方法来处理各种消息。

一个具有完整消息循环的线程必然包括Looper、Handler和HandlerThread对象(或者你自己用Thread类来写一个线程),其中Looper对象内部还包含一个MessageQueue对象,我们这样来建立一个消息循环子线程。

   Looper anotherLooper ;
   AnotherHandler anotherHandler;
   HandlerThread handlerThread;
   class AnotherHandler extends Handler {
        public AnotherHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == 1234) {
            Toast.makeText(MainActivity.this, String.format("Another Handler : ProcessId=%d,ThreadId=%d",getProcId(),getThreadId()), Toast.LENGTH_LONG).show();
            Log.i(TAG,"message in Another Handler");
            }
        }
    }
       handlerThread = new HandlerThread("handlerThread",Process.THREAD_PRIORITY_BACKGROUND);
       handlerThread.start();
       anotherLooper = handlerThread.getLooper();
       anotherHandler = new AnotherHandler(anotherLooper);
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

记住,此处HandlerThread类不是为了运行一段自定义的代码来完成某个任务,而是为了让把消息循环运行起来,即运行Looper.loop()方法。

记住几个原则: 
HandlerThread中创建了Looper对象,可以使用getLooper方法来得到这个Looper对象; 
使用该Looper对象作为参数来创建Handler,才能使得此Handler能够处理消息;如果Handler对象创建时不带有对象,那么Handler会自动获取当前线程关联的Looper对象。因此若Handler是在主线程(即UI线程)中创建的,则Handler就会处理UI线程中的消息队列;如果Handler是在其他线程(此线程未创建Looper对象)中创建的,则此Handler不能处理消息。

因此,若我们不使用HandlerThread,而是直接使用Thread类,则代码如下:

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();
         }
     }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

我们会发现上面的代码例子中不能得到那个mHandler,因此收发消息时没有Handler对象可以引用,若对多线程的并发很熟悉的人可以扩展上面的代码,安全的取得该Handler对象,否则还是直接使用HandlerThread为好。

其实消息循环是五兄弟,还要加上消息(Message类)和消息队列(MessageQueue类),不过由于Message较为简单,而MessageQueue又被隐藏得很好,几乎不会被普通用户见到,因此就省略不介绍了,可以自己去看相关文档。

在主线程中使用Handler

好了,相信很多不熟悉的人看上面的消息循环三/五兄弟已经看晕了,会问“为什么我用了好久的Handler,却根本就没有见过Looper和HandlerThread呢?” 
其实,那是因为绝大部分时间,你只在主线程(也就是UI线程)中使用Handler,此时Handler会默认使用主线程的Looper和Thread。可以这样理解,普通的Android应用一旦运行起来,就会创建主线程和一个主Looper,它们默默的处理着界面消息,这时你new了一个Handler并重载了它的handMessage方法,你的这个Handler对象就加入到消息的处理中来了。

具体的用法,请看我的一篇blog:Android中的消息处理实例与分析 
http://blog.csdn.net/logicteamleader/article/details/46591499

在子线程使用Handler

在子线程中使用Handler则比主线程复杂,因此此时就必须使用Looper和Thread/HandlerThread类了,例子仍可以参考上一篇blog。

AsycnTask怎么来的?

好了,终于说到AsycnTask了。前面已经说过,它是一个辅助类,就是为了将Handler、Thread等封装为一个异步执行框架,供Android Coder可以方便的使用。其主要目的是为了“在其他线程中执行一个耗时操作,并随时报告执行进度给UI线程,执行完成后将结果报告给UI线程”。

AsyncTask使用方法

AsyncTask的使用方法其实Android developer中已经说得非常清楚了,我自认不能比他说得更好,因此择重点翻译一次: 
AsyncTask使用时必须作为基类被扩展,子类至少重载一个方法doInBackground,另一个方法onPostExecute也经常被重载,代码例子如下:

   private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
       protected Long doInBackground(URL... urls) {
           int count = urls.length;
           long totalSize = 0;
           for (int i = 0; i < count; i++) {
               totalSize += Downloader.downloadFile(urls[i]);
               publishProgress((int) ((i / (float) count) * 100));
               // Escape early if cancel() is called
               if (isCancelled()) break;
           }
           return totalSize;
       }

       protected void onProgressUpdate(Integer... progress) {
           setProgressPercent(progress[0]);
       }

       protected void onPostExecute(Long result) {
           showDialog("Downloaded " + result + " bytes");
       }
   }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

AsyncTask含有三个泛型参数: 
Params, 异步任务执行所需的参数类型; 
Progress, 异步任务执行进度的类型; 
Result, 异步任务执行结果的类型。 
这三个参数不必全部使用,不使用的参数置为Void即可,例如:

 private class MyTask extends AsyncTask<Void, Void, Void> { ... }
   
   
  • 1

AsyncTask的四个重要方法: 
当一个异步任务被执行时,要经历四步: 
onPreExecute(),在UI线程中执行,它会在异步任务开始前执行,一般用来设置任务参数; 
doInBackground, 最重要的方法,在子线程中执行(事实上,只有它在子线程中执行,其他方法都在UI线程中执行)。当onPreExecute结束后,本方法立刻执行,它用来进行后台的耗时计算,异步任务的参数会被传给它,执行完成的结果会被送给第四步;执行途中,它还可以调用publishProgress 方法来通知UI线程当前执行的进度; 
onProgressUpdate, 当publishProgress 被调用后,它在UI线程中执行,刷新任务进度,一般用来刷新进度条等UI部件; 
onPostExecute, 当后台的异步任务完成后,它会在UI线程中被调用,并获取异步任务执行完成的结果。

小结

通过上面的分析,我们可以看到,其实Handler和AsyncTask的关系不太大,可以说不是同一个层面上的东西。只不过,它们有着一些共同的目的,就是都可以用来异步执行一些代码,避免阻塞UI线程。另外,AsyncTask实际上是使用Handler和线程池技术来实现的。 
要想真正的了解它们之间的异同,还是那句话:RTFSC – Read The Fucking Source Code。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值