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 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 sThreadLocal = new ThreadLocal();
    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 extends  
            AsyncTask {  
        protected WeakReference mTarget;  
      
        public WeakAsyncTask(WeakTarget target) {  
            mTarget = new WeakReference(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)