Android异步消息处理之Thread+Handler

前言:之前看了网上很多关于Android异步消息处理机制的文章,对于这块知识从一般性的应用上升到了内部机制的理解,受益匪浅。本着“看过没总结过等于没收获过”的原则,也对Android异步消息处理这块做个总结,内容如有错误之处还望指正。

通过该blog可以了解到开发中为什么需要异步消息处理;怎样创建线程以及创建Handler;通过Handler和Looper的源码了解Handler与消息循环的内部沟通过程,Handler发送消息的两种方式以及如果获取消息并处理消息;最后是介绍线程造成内存泄露的情况。

Android异步消息处理概述

Android Developers中Keeping Your App Response一文中:
  • No response to an input event (such as key press or screen touch events) within 5 seconds.
  • BroadcastReceiver hasn't finished executing within 10 seconds.如果输入事件(点击或触屏)在5秒之内没有响应,BroadcastReceiver在10秒之内没有执行完毕,应用程序就会报ANR错误,用户只能选择等待或强制关闭,这种用户体验是非常差的。

    主线程(main thread/UI thread),它有一个消息队列(message queue),如果屏幕上发生了点击/触屏事件就会把它转化为一个消息(message)放到消息队列里,由looper不断地从message queue里取出消息派送给相应的handler进行处理。而如果某个消息执行时间非常耗时,就会阻碍其他消息的处理,如果该条消息在5秒之内没有得到响应的话,就会报ANR。

    避免ANR的方法是创建子线程,由子线程来完成耗时任务。例如点击某条新闻查看新闻详情这一事件,它的实现过程是用户点击了某条新闻,这个点击事件会通过网络请求去获取服务器端的手机接口数据(耗时操作),获取到的新闻详情的数据内容会显示到新闻详情页面中(更新用户界面)。由子线程完成耗时任务,再由子线程处理返回结果来更新界面,这种做法是错误的,因为应用只允许在主线程里对UI做修改,子线程没有权利对用户界面做任何修改。因此子线程处理完耗时操作,其获取到修改UI的内容需要在主线程里做处理。

    之前已经提到创建子线程处理耗时任务,那么怎样将子线程获取到的数据放到主线程中处理呢?这里就需要Handler+Message来做主线程和子线程之间的沟通。整个的异步消息处理的步骤如下:

    1. 在main thread里创建worker thread来异步执行耗时任务

    2.在main thread里创建Handler,并让worker thread持有handler的引用

    3.将worker thread执行结束后,创建Message,将获取到的数据结果存放到message中

    4.由worker thread持有的的handler引用将message发送出去

    5.main thread中的handler接受到message并处理消息,UI更新


    创建子线程

    1. 创建线程的两种方式

    一、继承(extends)Thread类(适用于单继承)
    二、实现(implements)Runnable接口(当一个类已经继承某个类,就只能用Runnable来创建线程类)

    通过run()方法来执行代码,通过调用start()方法启动线程。


    实现Runnable接口创建线程:
        /**
         * 点击按钮,创建子线程
         */
        private void excuteLongTimeOperation() {
            Thread workerThread = new Thread(new MyNewThread());
            workerThread.start();
        }
    
        class MyNewThread implements Runnable{
            @Override
            public void run() {
                //执行耗时操作
                ThreadUtil.logThreadSignature();
            }
        }


    通过继承Thread类来创建线程:
        /**
         * 点击按钮,创建子线程
         */
        private void excuteLongTimeOperation() {
            Thread workerThread = new MyNewThread();
            workerThread.start();
        }
    
        public class MyNewThread extends Thread{
            @Override
            public void run() {
                super.run();
            }
        }
    可以简化为匿名内部类,更加简洁:
     /**
         * 点击按钮,创建子线程
         */
        private void excuteLongTimeOperation() {
             new Thread(){
                @Override
                public void run() {
                    super.run();
                    //执行耗时操作
                }
            }.start();
        }
    在以下情况下可以使用匿名内部类:
    1.只用到类的一个实例
    2.类在定以后马上用到
    3.类非常小
    4.给类名并不会导致代码更容易被理解

    2. 利用ThreadUtil查看运行的哪个线程

    通过当前的线程id来查看执行代码运行在哪个线程里,下面的代码段中有一个按钮“点击创建子线程”,点击该按钮会创建一个workerThread,并打印出子线程的部分信息。
    package com.aliao.myandroiddemo.view.handler;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;
    
    import com.aliao.myandroiddemo.R;
    import com.aliao.myandroiddemo.utils.ThreadUtil;
    
    /**
     * Created by liaolishuang on 14-4-9.
     */
    public class TestHandlerActivity extends Activity implements View.OnClickListener{
    
        private TextView didSthTxt;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_handler);
            //打印当前线程的部分信息
            ThreadUtil.logThreadSignature();
            Button anrBtn = (Button) findViewById(R.id.btn_createthread);
            anrBtn.setOnClickListener(this);
            didSthTxt = (TextView) findViewById(R.id.tv_showsth);
        }
    
        @Override
        public void onClick(View view) {
            switch (view.getId()){
                case R.id.btn_createthread:
                   excuteLongTimeOperation();
                    break;
            }
        }
    
        /**
         * 点击按钮,创建子线程
         */
        private void excuteLongTimeOperation() {
            Thread workerThread = new Thread(new MyNewThread());
            workerThread.start();
        }
    
        class MyNewThread implements Runnable{
            @Override
            public void run() {
                //打印子线程的部分信息
                ThreadUtil.logThreadSignature();
                //执行耗时操作
            }
        }
    
    }
    
    我们在onCreate()中调用ThreadUtil.logThreadSignature();执行上面的代码后会打印出当前线程的信息:
    04-11 18:40:55.529  21410-21410/com.aliao.myandroiddemo D/ThreadUtils﹕ main:(id)1:(priority)5:(group)main
    当前的线程名是main,即主线程,其线程id是1。
    同样在子线程的run()方法中调用ThreadUtil.logThreadSignature();点击“点击创建子线程”按钮后,可以看到:
    04-11 18:41:02.019  21410-22024/com.aliao.myandroiddemo D/ThreadUtils﹕ Thread-74777:(id)74777:(priority)5:(group)main
    子线程名是Thread-74777,id是74777

    3. 使用线程的缺点

    1.子线程如果要更新用户界面,需要在主线程中更新用户界面
    2.线程生命周期不可控
    3.没有默认线程池
    4.在Android中默认情况下不会处理配置改变(configuration changes)


    从源码角度查看handler的内部机制

    Handler扮演了往MessageQueue中添加消息和处理消息的角色。通过post(runnable)和sendMessage(msg)等函数向MesssageQueue中添加消息,再通过handleCallback(在Handler类中直接调用runnable的run方法实现)和handleMessage(需要由programer在代码中覆写,自己处理)这两个方法处理消息。

    1. Handler源码
    我们常常会这样定义一个Handler对象:
    Handler handler = new Handler();
    Handler类中的构造函数public Handler()会做一些准备工作,先看下源码:
    package android.os;
    
    import android.util.Log;
    import android.util.Printer;
    
    import java.lang.reflect.Modifier;
    
    public class Handler {
        private static final boolean FIND_POTENTIAL_LEAKS = false;
        private static final String TAG = "Handler";
    	
        public Handler() {
            this(null, false);
        }
    
        public Handler(Callback callback) {
            this(callback, false);
        }
    
        public Handler(Looper looper) {
            this(looper, null, false);
        }
    
        public Handler(Looper looper, Callback callback) {
            this(looper, callback, false);
        }
    
        public Handler(boolean async) {
            this(null, async);
        }
    
        public Handler(Callback callback, boolean async) {
            if (FIND_POTENTIAL_LEAKS) {
                final Class<? extends Handler> klass = getClass();
                if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                        (klass.getModifiers() & Modifier.STATIC) == 0) {
                    Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                        klass.getCanonicalName());
                }
            }
    
            mLooper = Looper.myLooper();
            if (mLooper == null) {
                throw new RuntimeException(
                    "Can't create handler inside thread that has not called Looper.prepare()");
            }
            mQueue = mLooper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }
    
        public Handler(Looper looper, Callback callback, boolean async) {
            mLooper = looper;
            mQueue = looper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }
    //省略其他代码....
    }
    构造函数handler(Callback callback, boolean async){}中该句代码:
            mLooper = Looper.myLooper();
    
    继续查看Looper.myLooper()
        /**
         * Return the Looper object associated with the current thread.  Returns
         * null if the calling thread is not associated with a Looper.
         */
        public static Looper myLooper() {
            return sThreadLocal.get();
        }
    myLooper返回的是与当前线程关联的looper。Handler与当前线程的Looper相关联。
    Looper到底是什么,在异步消息处理机制中扮演什么角色?

    2. Looper源码分析

    Looper顾名思义是<循环者>的意思,他封装了消息循环。在Looper这个类中有一个for(;;)死循环,会在MessageQueue中不断的取出消息,然后派送给对应的handler进行处理。怎样派送到对应的handler呢?带着这个问题先看源码:
    package android.os;
    
    import android.util.Log;
    import android.util.Printer;
    import android.util.PrefixPrinter;
    
    
    public final class Looper {
        private static final String TAG = "Looper";
    
        // sThreadLocal.get() will return null unless you've called prepare().
        static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
        private static Looper sMainLooper;  // guarded by Looper.class
    
        final MessageQueue mQueue;//在looper内部维护了一个消息队列
        final Thread mThread;//当前线程
    
        private Printer mLogging;
    
         /** Initialize the current thread as a looper.
          * This gives you a chance to create handlers that then reference
          * this looper, before actually starting the loop. Be sure to call
          * {@link #loop()} after calling this method, and end it by calling
          * {@link #quit()}.
          */
        public static void prepare() {
            prepare(true);
        }
    
        private static void prepare(boolean quitAllowed) {
    	    //每个线程只能创建一个Looper,如果视图再次创建,报异常
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
    		//创建looper,并把该looper和当前线程关联在一起。取出looper用对应sThreadLocal.get();方法
            sThreadLocal.set(new Looper(quitAllowed));
        }
    	
    	private Looper(boolean quitAllowed) {
    	    //创建了一个Looper,也就创建了一个消息队列
            mQueue = new MessageQueue(quitAllowed);
            mThread = Thread.currentThread();
        }
    
        /**
         * 主线程所关联的looper(application's main looper)由应用程序自动创建
         */
        public static void prepareMainLooper() {
            prepare(false);
            synchronized (Looper.class) {
                if (sMainLooper != null) {
                    throw new IllegalStateException("The main Looper has already been prepared.");
                }
                sMainLooper = myLooper();
            }
        }
    	
        /** 返回运行在主线程中的looper—— the application's main looper
         */
        public static Looper getMainLooper() {
            synchronized (Looper.class) {
                return sMainLooper;
            }
        }
    
    	/**
         * 返回当前线程关联的looper对象,如果调用的thread没有关联一个looper,返回null
         */
        public static Looper myLooper() {
            return sThreadLocal.get();
        }
    	
        /**
         * Run the message queue in this thread. Be sure to call
         * {@link #quit()} to end the loop.
         */
        public static void loop() {
            final Looper me = myLooper();//得到当前looper
            if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            final MessageQueue queue = me.mQueue;//得到当前lopper关联的MessageQueue
    
            // Make sure the identity of this thread is that of the local process,
            // and keep track of what that identity token actually is.
            Binder.clearCallingIdentity();
            final long ident = Binder.clearCallingIdentity();
    
            for (;;) {//循环体
    		    //从消息队列中取出消息
                Message msg = queue.next(); // might block
    			//如果消息为空,就退出循环
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
    
                // This must be in a local variable, in case a UI event sets the logger
                Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                }
    
    			//非常关键的一句代码:将处理消息的工作交给msg.target即handler
                msg.target.dispatchMessage(msg);
    
                if (logging != null) {
                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                }
    
                // Make sure that during the course of dispatching the
                // identity of the thread wasn't corrupted.
                final long newIdent = Binder.clearCallingIdentity();
                if (ident != newIdent) {
                    Log.wtf(TAG, "Thread identity changed from 0x"
                            + Long.toHexString(ident) + " to 0x"
                            + Long.toHexString(newIdent) + " while dispatching to "
                            + msg.target.getClass().getName() + " "
                            + msg.callback + " what=" + msg.what);
                }
    
                msg.recycle();
            }
        }
    
        /**
         * Quits the looper.
         */
        public void quit() {
            mQueue.quit(false);
        }
    
        /**
         * Quits the looper safely.
         */
        public void quitSafely() {
            mQueue.quit(true);
        }
    //省略其他代码
    }
    在一个线程中,looper用来跑一个消息循环。默认情况线程并没有关联一个消息循环。为一个消息创建消息循环必须在线程中调用prepare()方法去创建一个looper来运行消息循环,然后调用loop()方法来启动循环,直到loop被停止。并且消息循环的大部分交互都是通过Handler类来进行。源码中给了一个例子,自己查看吧。应用程序会为主线程自动创建关联的looper。
    源代码中需要了解的地方:
    1. 通过prepare()方法创建消息循环,通过loop()方法启动循环
    2. 创建looper时也相应创建了消息队列
    3. 进入for循环体,从消息队列中读取消息,如果消息为null,结束这次循环
    4. msg.target.dispatchMessage(msg);msg.target就是handler,通过对应的handler把消息分发出去,由回调函数来处理消息。
    5. msg.recycle();"当处理完一次消息后,对消息进行回收处理。在Message有一个消息池用来避免不停的创建删除消息对象,内部只是把消息设置为空闲状态,以便重复利用"

    3. Handler发送消息和处理消息

    第4点中有两处需要引起注意:一是,当消息被取出,怎么分发给"相应"的Handler?二是,dispatchMessage()是如何分发消息的?
    对于第一个问题可以通过对第二点的源码分析过程中得知。
    对于第二点,dispatchMessage()发方法在Handler类中:
        /**
         * Subclasses must implement this to receive messages.
         */
        public void handleMessage(Message msg) {
        }
        
        /**
         * Handle system messages here.
         */
        public void dispatchMessage(Message msg) {
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg);
            }
        }
    dispatchMessage(msg)这个方式是用来分工的。handler通过以下两种方式发送消息:handler.post(runnable)以及handler.sendMessage(msg)。其中参数一个是runnable类型的,一个是Message类型的,所以如果msg.callback != null(在Message类中定义了Runnable callback;变量)即如果该消息是Runnable类型的话就交给handleCallback(msg)去处理。否则消息类型是一个纯粹的Message类型,就交给handleMessage(msg)去处理。之所以说纯粹是因为,post(runnable),最终还是会把runnable对象"包装"成一个Message对象。
    通过源码进一步了解:
    handler调用post(runnable)后,会把runnable赋值给Message对象的callback成员变量,然后把这个message添加到MessageQueue中。再通过msg.callback是否为空来分别处理不同的消息类型。
        public final boolean post(Runnable r)
        {
           return  sendMessageDelayed(getPostMessage(r), 0);
        }
        private static Message getPostMessage(Runnable r) {
            Message m = Message.obtain();
            m.callback = r;
            return m;
        }
    handler调用sendMessage(msg):
        public final boolean sendMessage(Message msg)
        {
            return sendMessageDelayed(msg, 0);
        }
    两个方法均调用了sendMessageDelayed(msg, 0);方法:
        public final boolean sendMessageDelayed(Message msg, long delayMillis)
        {
            if (delayMillis < 0) {
                delayMillis = 0;
            }
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
        }
        public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
            MessageQueue queue = mQueue;
            if (queue == null) {
                RuntimeException e = new RuntimeException(
                        this + " sendMessageAtTime() called with no mQueue");
                Log.w("Looper", e.getMessage(), e);
                return false;
            }
            return enqueueMessage(queue, msg, uptimeMillis);
        }
    
    回头看在Handler的构造函数中有这样一句代码:mQueue = mLooper.mQueue;在sendMessageAtTime()中的引用的queue正式Looper中的message queue。作为参数传给了enqueueMessage()
        private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            msg.target = this;
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    之前留下一个问题:looper类中当消息被取出,怎么分发给"相应"的Handler?Message类中定义了target变量,其类型正式Handler。在该方法的源码中可以看出msg.target = this;是把当前handler与该handler发送的消息一 一对应起来,那么在取出消息的时候,也就可以通过msg.target获取到相对应的handler了。
    queue.enqueueMessage(msg, uptimeMillis);将消息添加到消息队列里。
    到目前为止我们已经通过源码了解了handler添加消息的过程有了一个大致的了解,下面是具体看handler是如何处理消息的
    1. 通过handleCallback处理Runnable类型的消息:
        private static void handleCallback(Message message) {
            message.callback.run();
        }
    回调在代码中创建的Runnable类中的run方法。该实现Runnable的类作为Message的参数。
    2. 通过handleMessage(msg)处理消息:
        /**
         * Subclasses must implement this to receive messages.
         */
        public void handleMessage(Message msg) {
        }
    子类必须覆写handleMessage(msg)方法。
    run()方法和handleMessage()方法中的具体逻辑都由开发者自己去实现。

    4.handler与looper源码分析小结

    1. 系统默认情况下,只有主线程绑定looper对象,所以在主线程中可以直接创建Handler,该handler关联的是主线程。
    2. 每个handler都对应一个looper,一个looper对应一个线程和该线程的消息队列。
    3. 一个线程只能有一个looper,但是可以有多个handler。一个looper可以有多个handler。
    4. 如果要在一个线程里创建handler,需要创建looper实例(通过looper.prepare()和looper.loop()来实现),因为线程默认情况是没有消息循环的。
    5.handler的最主要的两个工作是发送消息和处理消息。
    6.当Handler被创建时,在Handler的构造函数中便持有了当前线程的Looper引用,通过该looper获取到消息队列的引用,当handler发送消息后,会通过该消息队列的引用调用MessageQueue类中的方法将消息插入到消息队列中。而Looper类中会循环取出该消息队列中的消息再派送给handler去处理。

    例子-利用Thread+Handler方式实现在子线程中发送消息通知主线程更新界面

    Android采用UI单线程模型,只能够在主线程中对UI元素进行操作。如果在子线程中直接对UI进行了操作,回报:
    CalledFromWrongThreadException:only the original thread that created a view hierarchy can touch its views
    我们可以利用消息循环的机制来实现线程之间的通信。
    package com.aliao.myandroiddemo.view.handler;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;
    
    import com.aliao.myandroiddemo.R;
    import com.aliao.myandroiddemo.utils.ThreadUtil;
    
    /**
     * Created by liaolishuang on 14-4-9.
     */
    public class TestHandlerActivity extends Activity implements View.OnClickListener{
    
        private TextView didSthTxt;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_handler);
            //打印当前线程的部分信息
            ThreadUtil.logThreadSignature();
            Button anrBtn = (Button) findViewById(R.id.btn_createthread);
            anrBtn.setOnClickListener(this);
            didSthTxt = (TextView) findViewById(R.id.tv_showsth);
        }
    
        @Override
        public void onClick(View view) {
            switch (view.getId()){
                case R.id.btn_createthread:
                   excuteLongTimeOperation();
                    break;
            }
        }
    
        /**
         * 点击按钮,创建子线程
         */
        private void excuteLongTimeOperation() {
            Thread workerThread = new Thread(new MyNewThread());
            workerThread.start();
        }
    
        class MyNewThread extends Thread{
            @Override
            public void run() {
                //打印子线程的部分信息
                ThreadUtil.logThreadSignature();
                //模拟执行耗时操作
                ThreadUtil.sleepForInSecs(5);
                Message message = handler.obtainMessage();
                Bundle bundle = new Bundle();
                bundle.putString("message","界面内容已更新");
                message.setData(bundle);
                handler.sendMessage(message);
            }
        }
    
        /**
         * 以匿名类的形式创建handler
         */
        private Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                //修改界面中TextView中的内容
                didSthTxt.setText(msg.getData().getString("message"));
            }
        };
    }

    线程与内存泄露

    摘自Android,谁动了我的内存 中第四点:
    线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码。
    public class MyActivity extends Activity {  
        @Override  
        public void onCreate(Bundle savedInstanceState) {  
            super.onCreate(savedInstanceState);  
            setContentView(R.layout.main);  
            new MyThread().start();  
        }  
      
        private class MyThread extends Thread{  
            @Override  
            public void run() {  
                super.run();  
                //do somthing  
            }  
        }  
    }

    这段代码很平常也很简单,是我们经常使用的形式。我们思考一个问题:假设MyThread的run函数是一个很费时的操作,当我们开启该线程后,将设备的横屏变为了竖屏,一般情况下当屏幕转换时会重新创建Activity,按照我们的想法,老的Activity应该会被销毁才对,然而事实上并非如此。

        由于我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThread的run函数没有结束时,MyThread是不会被销毁的,因此它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题。

        有些人喜欢用Android提供的AsyncTask,但事实上AsyncTask的问题更加严重,Thread只有在run函数不结束时才出现这种内存泄露问题,然而AsyncTask内部的实现机制是运用了ThreadPoolExcutor,该类产生的Thread对象的生命周期是不确定的,是应用程序无法控制的,因此如果AsyncTask作为Activity的内部类,就更容易出现内存泄露的问题。

        这种线程导致的内存泄露问题应该如何解决呢?

        第一、将线程的内部类,改为静态内部类。

        第二、在线程内部采用弱引用保存Context引用。

        解决的模型如下:

        public abstract class WeakAsyncTask<Params, Progress, Result, WeakTarget> extends  
                AsyncTask<Params, Progress, Result> {  
            protected WeakReference<WeakTarget> mTarget;  
          
            public WeakAsyncTask(WeakTarget target) {  
                mTarget = new WeakReference<WeakTarget>(target);  
            }  
          
            /** {@inheritDoc} */  
            @Override  
            protected final void onPreExecute() {  
                final WeakTarget target = mTarget.get();  
                if (target != null) {  
                    this.onPreExecute(target);  
                }  
            }  
          
            /** {@inheritDoc} */  
            @Override  
            protected final Result doInBackground(Params... params) {  
                final WeakTarget target = mTarget.get();  
                if (target != null) {  
                    return this.doInBackground(target, params);  
                } else {  
                    return null;  
                }  
            }  
          
            /** {@inheritDoc} */  
            @Override  
            protected final void onPostExecute(Result result) {  
                final WeakTarget target = mTarget.get();  
                if (target != null) {  
                    this.onPostExecute(target, result);  
                }  
            }  
          
            protected void onPreExecute(WeakTarget target) {  
                // No default action  
            }  
          
            protected abstract Result doInBackground(WeakTarget target, Params... params);  
          
            protected void onPostExecute(WeakTarget target, Result result) {  
                // No default action  
            }  
        }  

    好文分享

    Android Background Processing with Handlers and AsyncTask and Loaders - Tutorial
    Android异步消息处理
  • 你可能感兴趣的:(Android异步消息处理之Thread+Handler)