前言:之前看了网上很多关于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 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异步消息处理

更多相关文章

  1. Android线程与异步消息处理机制
  2. android的消息处理机制(Looper,Handler,Message)
  3. android ui线程和数据的分离
  4. Android的消息机制分析
  5. android中实现消息推送
  6. Android消息传递之EventBus 3.0使用详解

随机推荐

  1. Android文本输入框EditText方法说明和属
  2. Tabhost+picture
  3. Android(安卓)布局优化之include与merge
  4. 【Tech-Android-View】Android中可以使用
  5. 第八章、理解Window和WindowManager
  6. Unable to execute dex: java.nio.Buffer
  7. android SystemUI 流程分析
  8. Android进阶 - 消息处理机制探索
  9. Android(安卓)Studio:layout-sw600dp文件
  10. Android系统架构介绍