Android消息处理机制

一篇讲述Android消息处理机制的好文

转载自http://www.cnblogs.com/qingblog/archive/2012/06/27/2566021.html

Google参考了Windows的消息处理机制,在Android系统中实现了一套类似的消息处理机制。学习Android的消息处理机制,有几个概念(类)必须了解:

1.       Message

消息,理解为线程间通讯的数据单元。例如后台线程在处理数据完毕后需要更新UI,则可发送一条包含更新信息的Message给UI线程。

2.       Message Queue

消息队列,用来存放通过Handler发布的消息,按照先进先出执行。

3.       Handler

Handler是Message的主要处理者,负责将Message添加到消息队列以及对消息队列中的Message进行处理。

4.       Looper

循环器,扮演Message Queue和Handler之间桥梁的角色,循环取出Message Queue里面的Message,并交付给相应的Handler进行处理。

 

5.    线程

UI thread 通常就是main thread,而Android启动程序时会替它建立一个Message Queue。

每一个线程里可含有一个Looper对象以及一个MessageQueue数据结构。在你的应用程序里,可以定义Handler的子类别来接收Looper所送出的消息。

 

运行机理:

      每个线程都可以并仅可以拥有一个Looper实例,消息队列MessageQueue在Looper的构造函数中被创建并且作为成员变量被保存,也就是说MessageQueue相对于线程也是唯一的。Android应用在启动的时候会默认会为主线程创建一个Looper实例,并借助相关的Handler和Looper里面的MessageQueue完成对Activities、Services、Broadcase Receivers等的管理。而在子线程中,Looper需要通过显式调用Looper. Prepare()方法进行创建。Prepare方法通过ThreadLocal来保证Looper在线程内的唯一性,如果Looper在线程内已经被创建并且尝试再度创建"Only one Looper may be created per thread"异常将被抛出。

 

      Handler在创建的时候可以指定Looper,这样通过Handler的sendMessage()方法发送出去的消息就会添加到指定Looper里面的MessageQueue里面去。在不指定Looper的情况下,Handler绑定的是创建它的线程的Looper。如果这个线程的Looper不存在,程序将抛出"Can't create handler inside thread that has not called Looper.prepare()"。

 

      整个消息处理的大概流程是:1. 包装Message对象(指定Handler、回调函数和携带数据等);2. 通过Handler的sendMessage()等类似方法将Message发送出去;3. 在Handler的处理方法里面将Message添加到Handler绑定的Looper的MessageQueue;4. Looper的loop()方法通过循环不断从MessageQueue里面提取Message进行处理,并移除处理完毕的Message;5. 通过调用Message绑定的Handler对象的dispatchMessage()方法完成对消息的处理。

 

      在dispatchMessage()方法里,如何处理Message则由用户指定,三个判断,优先级从高到低:1. Message里面的Callback,一个实现了Runnable接口的对象,其中run函数做处理工作;2. Handler里面mCallback指向的一个实现了Callback接口的对象,由其handleMessage进行处理;3. 处理消息Handler对象对应的类继承并实现了其中handleMessage函数,通过这个实现的handleMessage函数处理消息。   

 

 

Android的消息机制(一)

 

android 有一种叫消息队列的说法,这里我们可以这样理解:假如一个隧道就是一个消息队列,那么里面的每一部汽车就是一个一个消息,这里我们先忽略掉超车等种种因素,只那么先进隧道的车将会先出,这个机制跟我们android 的消息机制是一样的。

一、    角色描述

1.Looper:(相当于隧道)一个线程可以产生一个Looper对象,由它来管理此线程里的Message Queue(车队,消息隧道)

2.Handler:你可以构造Handler对象来与Looper沟通,以便push新消息到Message Queue里;或者接收Looper(Message Queue取出)所送来的消息。

3. Message Queue(消息队列):用来存放线程放入的消息。

4.线程:UI thread通常就是main thread,而Android启动程序时会替它建立一个Message Queue

每一个线程里可含有一个Looper对象以及一个MessageQueue数据结构。在你的应用程序里,可以定义Handler的子类别来接收Looper所送出的消息。

 

在你的Android程序里,新诞生一个线程,或执行(Thread)时,并不会自动建立其Message Loop

Android里并没有GlobalMessage Queue数据结构,例如,不同APK里的对象不能透过Massage Queue来交换讯息(Message)

例如:线程AHandler对象可以传递消息给别的线程,让别的线程BC等能送消息来给线程A(存于AMessage Queue)

线程AMessage Queue里的讯息,只有线程A所属的对象可以处理。

使用Looper.myLooper可以取得当前线程的Looper对象。

使用mHandler = new EevntHandler(Looper.myLooper());可用来构造当前线程的Handler对象;其中,EevntHandler是自已实现的Handler的子类别。

使用mHandler = new EevntHandler(Looper.getMainLooper());可诞生用来处理main线程的Handler对象;其中,EevntHandler是自已实现的Handler的子类别。

 

这样描述可能太抽像,下面举几个实际的例子来说明:

二、    举例

1.  同线程内不同组件间的消息传递

Looper类用来管理特定线程内对象之间的消息交换(Message Exchange)。你的应用程序可以产生许多个线程。而一个线程可以有许多个组件,这些组件之间常常需要互相交换讯息。如果有这种需要,您可以替线程构造一个Looper对象,来担任讯息交换的管理工作。Looper对象会建立一个MessageQueue数据结构来存放各对象传来的消息(包括UI事件或System事件等)。如下图:

 

每一个线程里可含有一个Looper对象以及一个MessageQueue数据结构。在你的应用程序里,可以定义Handler的子类别来接收Looper所送出的消息。

同线程不同组件之间的消息传递:

publicclass Activity1extends Activityimplements OnClickListener{

       Buttonbutton =null;

       TextViewtext =null;

      @Override

      protectedvoid onCreate(Bundle savedInstanceState) {

             super.onCreate(savedInstanceState);

              setContentView(R.layout.activity1);        

             button = (Button)findViewById(R.id.btn);

             button.setOnClickListener(this);

             text = (TextView)findViewById(R.id.content);

       }

      publicvoid onClick(View v) {

             switch (v.getId()) {

             case R.id.btn:

                     Looper looper = Looper.myLooper();//取得当前线程里的looper

                     MyHandler mHandler =new MyHandler(looper);//构造一个handler使之可与looper通信

                    //buton等组件可以由mHandler将消息传给looper,再放入messageQueue,同时mHandler也可以接受来自looper消息

                     mHandler.removeMessages(0);

                     String msgStr ="主线程不同组件通信:消息来自button";

                     Message m = mHandler.obtainMessage(1, 1, 1, msgStr);//构造要传递的消息

                     mHandler.sendMessage(m);//发送消息:系统会自动调用handleMessage方法来处理消息

                    break;

               }            

       }     

      privateclass MyHandlerextends Handler{             

             public MyHandler(Looper looper){

                    super(looper);

              }

             @Override

             publicvoid handleMessage(Message msg) {//处理消息

                    text.setText(msg.obj.toString());

              }            

       }

}

 

说明:

此程序启动时,当前线程(即主线程, main thread)已诞生了一个Looper对象,并且有了一个MessageQueue数据结构。

    looper = Looper.myLooper (); 

调用Looper类别的静态myLooper()函数,以取得目前线程里的Looper对象.

mHandler = new MyHandler (looper);

构造一个MyHandler对象来与Looper沟通。Activity等对象可以藉由MyHandler对象来将消息传给Looper,然后放入MessageQueue里;MyHandler对象也扮演Listener的角色,可接收Looper对象所送来的消息。

Message m = mHandler.obtainMessage(1, 1, 1, obj);

先构造一个Message对象,并将数据存入对象里。

mHandler.sendMessage(m);

就透过mHandler对象而将消息m传给Looper,然后放入MessageQueue里。

此时,Looper对象看到MessageQueue里有消息m,就将它广播出去,mHandler对象接到此讯息时,会呼叫其handleMessage()函数来处理,于是输出"This my message!"于画面上,

Android消息处理机制(二)

角色综述(回顾):

   (1)UI thread通常就是main thread,而Android启动程序时会替它建立一个MessageQueue

(2)当然需要一个Looper对象,来管理该MessageQueue

(3)我们可以构造Handler对象来push新消息到Message Queue里;或者接收Looper(Message Queue取出)所送来的消息。

(4)线程AHandler对象可以传递给别的线程,让别的线程BC等能送讯息来给线程A(存于AMessage Queue)

(5)线程AMessage Queue里的消息,只有线程A所属的对象可以处理。

 

子线程传递消息给主线程

publicclass Activity2extends Activityimplements OnClickListener{

       Buttonbutton =null;

       TextViewtext =null;

       MyHandlermHandler =null;

       Threadthread ;

      @Override

      protectedvoid onCreate(Bundle savedInstanceState) {

             super.onCreate(savedInstanceState);

              setContentView(R.layout.activity1);        

             button = (Button)findViewById(R.id.btn);

             button.setOnClickListener(this);

             text = (TextView)findViewById(R.id.content);

       }

      publicvoid onClick(View v) {

             switch (v.getId()) {

             case R.id.btn:

                    thread =new MyThread();

                    thread.start();

                    break;

              }            

       }     

      privateclass MyHandlerextends Handler{             

             public MyHandler(Looper looper){

                    super(looper);

              }

             @Override

             publicvoid handleMessage(Message msg) {//处理消息

                    text.setText(msg.obj.toString());

              }            

       }

      privateclass MyThreadextends Thread{

             @Override

             publicvoid run() {

                     Looper curLooper = Looper.myLooper();

                     Looper mainLooper = Looper.getMainLooper();

                     String msg ;

                    if(curLooper==null){

                           mHandler =new MyHandler(mainLooper);

                            msg ="curLooper is null";

                     }else{

                           mHandler =new MyHandler(curLooper);

                            msg ="This is curLooper";

                     }

                    mHandler.removeMessages(0);

                     Message m =mHandler.obtainMessage(1, 1, 1, msg);

                    mHandler.sendMessage(m);

              }            

       }

}

说明:

Android会自动替主线程建立Message Queue。在这个子线程里并没有建立Message Queue。所以,myLooper值为null,而mainLooper则指向主线程里的Looper。于是,执行到:

mHandler = new MyHandler (mainLooper);

mHandler属于主线程。

   mHandler.sendMessage(m);

就将m消息存入到主线程的Message Queue里。mainLooper看到Message Queue里有讯息,就会作出处理,于是由主线程执行到mHandlerhandleMessage()来处理消息。

用Android线程间通信的Message机制

在Android下面也有多线程的概念,在C/C++中,子线程可以是一个函数,一般都是一个带有循环的函数,来处理某些数据,优先线程只是一个复杂的运算过程,所以可能不需要while循环,运算完成,函数结束,线程就销毁。对于那些需要控制的线程,一般我们都是和互斥锁相互关联,从而来控制线程的进度,一般我们创建子线程,一种线程是很常见的,那就是带有消息循环的线程。
消息循环是一个很有用的线程方式,曾经自己用C在Linux下面实现一个消息循环的机制,往消息队列里添加数据,然后异步的等待消息的返回。当消息队列为空的时候就会挂起线程,等待新的消息的加入。这是一个很通用的机制。
在Android,这里的线程分为有消息循环的线程和没有消息循环的线程,有消息循环的线程一般都会有一个Looper,这个事android的新概念。我们的主线程(UI线程)就是一个消息循环的线程。针对这种消息循环的机制,我们引入一个新的机制Handle,我们有消息循环,就要往消息循环里面发送相应的消息,自定义消息一般都会有自己对应的处理,消息的发送和清除,消息的的处理,把这些都封装在Handle里面,注意Handle只是针对那些有Looper的线程,不管是UI线程还是子线程,只要你有Looper,我就可以往你的消息队列里面添加东西,并做相应的处理。
但是这里还有一点,就是只要是关于UI相关的东西,就不能放在子线程中,因为子线程是不能操作UI的,只能进行数据、系统等其他非UI的操作。
那么什么情况下面我们的子线程才能看做是一个有Looper的线程呢?我们如何得到它Looper的句柄呢?
Looper.myLooper();获得当前的Looper
Looper.getMainLooper () 获得UI线程的Lopper
我们看看Handle的初始化函数,如果没有参数,那么他就默认使用的是当前的Looper,如果有Looper参数,就是用对应的线程的Looper。
如果一个线程中调用Looper.prepare(),那么系统就会自动的为该线程建立一个消息队列,然后调用 Looper.loop();之后就进入了消息循环,这个之后就可以发消息、取消息、和处理消息。这个如何发送消息和如何处理消息可以再其他的线程中通过Handle来做,但前提是我们的Hanle知道这个子线程的Looper,但是你如果不是在子线程运行 Looper.myLooper(),一般是得不到子线程的looper的。

public void run() {
            synchronized (mLock) {
                Looper.prepare();
               //do something
            }
            Looper.loop();
        }


所以很多人都是这样做的:我直接在子线程中新建handle,然后在子线程中发送消息,这样的话就失去了我们多线程的意义了。

class myThread extends Thread{
             private EHandler mHandler ;
             public void run() {
                 Looper myLooper, mainLooper;
                 myLooper = Looper.myLooper ();
                mainLooper = Looper.getMainLooper ();
                String obj;
                if (myLooper == null ){
                         mHandler = new EHandler(mainLooper);
                         obj = "current thread has no looper!" ;
                }
                else {
                     mHandler = new EHandler(myLooper);
                     obj = "This is from current thread." ;
                }
                mHandler .removeMessages(0);
                Message m = mHandler .obtainMessage(1, 1, 1, obj);
                mHandler .sendMessage(m);
             }
  }



可以让其他的线程来控制我们的handle,可以把 private EHandler mHandler ;放在外面,这样我们的发消息和处理消息都可以在外面来定义,这样增加程序代码的美观,结构更加清晰。
对如任何的Handle,里面必须要重载一个函数
public void handleMessage(Message msg)
这个函数就是我们的消息处理,如何处理,这里完全取决于你,然后通过 obtainMessage和 sendMessage等来生成和发送消息, removeMessages(0)来清除消息队列。Google真是太智慧了,这种框架的产生,我们写代码更加轻松了。
有的时候,我们的子线程想去改变UI了,这个时候千万不要再子线程中去修改,获得UI线程的Looper,然后发送消息即可。
我们看看Goole Music App的源代码。
在MediaPlaybackActivity.java中,我们可以看一下再OnCreate中的有这样的两句:

        mAlbumArtWorker = new Worker("album art worker");
        mAlbumArtHandler = new AlbumArtHandler(mAlbumArtWorker.getLooper());

很明显这两句,是构建了一个子线程。并且这个子线程还是Looper的子线程,这里很牛逼的使用了 mAlbumArtWorker.getLooper()这个函数,因为我们知道,我们能够得到子线程的Looper的途径只有一个:就是在子线程中调用 Looper.myLooper (),并且这个函数还要在我们perpare之后调用才能得到正确的Looper,但是他这里用了一个这样的什么东东 getLooper,不知道它是如何实现的?
这里有一个大概的思路,我们在子线程的的prepare之后调用 myLooper ()这个方法,然后保存在一个成员变量中,这个getLooper就返回这个东西,但是这里会碰到多线程的一个很突出的问题,同步。我们在父线程中调用 mAlbumArtWorker.getLooper(),但是想要这个返回正确的looper就必须要求我们的子线程运行了prepare,但是这个东西实在子线程运行的,我们如何保证呢?
我们看Google是如何实现的?
 

 private class Worker implements Runnable {
        private final Object mLock = new Object();
        private Looper mLooper;
        
        /**
         * Creates a worker thread with the given name. The thread
         * then runs a [email=%7B@link]{@link[/email] android.os.Looper}.
         * @param name A name for the new thread
         */
        Worker(String name) {
            Thread t = new Thread(null, this, name);
            t.setPriority(Thread.MIN_PRIORITY);
            t.start();
            synchronized (mLock) {
                while (mLooper == null) {
                    try {
                        mLock.wait();
                    } catch (InterruptedException ex) {
                    }
                }
            }
        }
        
        public Looper getLooper() {
            return mLooper;
        }
        
        public void run() {
            synchronized (mLock) {
                Looper.prepare();
                mLooper = Looper.myLooper();
                mLock.notifyAll();
            }
            Looper.loop();
        }
        
        public void quit() {
            mLooper.quit();
        }
    }



我们知道,一个线程类的构造函数是在主线程中完成的,所以在我们的 Worker的构造函数中我们创佳一个线程,然后让这个线程运行,这一这个线程的创建是指定一个 Runnabl,这里就是我们的Worker本身,在主线程调用 t.start();,这后,我们子线程已经创建,并且开始执行work的run方法。然后下面的代码很艺术:

synchronized (mLock) {
                while (mLooper == null) {
                    try {
                        mLock.wait();
                    } catch (InterruptedException ex) {
                    }
                }
            }

我们开始等待我们的子线程给mLooper赋值,如果不赋值我们就继续等,然后我们的子线程在运行run方法之后,在给 mLooper赋值之后,通知worker够着函数中的wait,然后我们的构造函数才能完成,所以我们说:
mAlbumArtWorker = new Worker("album art worker");
这句本身就是阻塞的,它创建了一个子线程,开启了子线程,并且等待子线程给mLooper赋值,赋值完成之后,这个函数才返回,这样才能保证我们的子线程的Looper的获取绝对是正确的,这个构思很有创意。值得借鉴

 

Android中Handler的使用方法——在子线程中更新界面

本文主要介绍Android的Handler的使用方法。Handler可以发送Messsage和Runnable对象到与其相关联的线程的消息队列。每个Handler对象与创建它的线程相关联,并且每个Handler对象只能与一个线程相关联。

1.    Handler一般有两种用途:1)执行计划任务,你可以再预定的实现执行某些任务,可以模拟定时器。2)线程间通信。在Android的应用启动时,会创建一个主线程,主线程会创建一个消息队列来处理各种消息。当你创建子线程时,你可以再你的子线程中拿到父线程中创建的Handler对象,就可以通过该对象向父线程的消息队列发送消息了。由于Android要求在UI线程中更新界面,因此,可以通过该方法在其它线程中更新界面。
◆ 通过Runnable在子线程中更新界面的例子

1.○ 在onCreate中创建Handler

public class HandlerTestApp extends Activity {
        Handler mHandler;
        TextView mText;
        /** Called when the activity is first created. */
       @Override
       public void onCreate(Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);
           setContentView(R.layout.main);
           mHandler = new Handler();//创建Handler
           mText = (TextView) findViewById(R.id.text0);//一个TextView
       } 


     ○ 构建Runnable对象,在runnable中更新界面,此处,我们修改了TextView的文字.此处需要说明的是,Runnable对象可以再主线程中创建,也可以再子线程中创建。我们此处是在子线程中创建的。 
  

  Runnable mRunnable0 = new Runnable()
    {
                @Override
                public void run() { 
                        mText.setText("This is Update from ohter thread, Mouse DOWN");
                }
    }; 

   ○ 创建子线程,在线程的run函数中,我们向主线程的消息队列发送了一个runnable来更新界面。

 

    private void updateUIByRunnable(){
          new Thread() 
         { 
               //Message msg = mHandler.obtainMessage(); 
              public void run() 
             { 

                   //mText.setText("This is Update from ohter thread, Mouse DOWN");//这句将抛出异常
                   mHandler.post(mRunnable0); 
             } 
         }.start();

     }

 

◆ 用Message在子线程中来更新界面

1.    用Message更新界面与Runnable更新界面类似,只是需要修改几个地方。
    ○ 实现自己的Handler,对消息进行处理

 private class MyHandler extends Handler
    { 

        @Override
        public void handleMessage(Message msg) { 
            super.handleMessage(msg);
            switch(msg.what)
            {
            case UPDATE://在收到消息时,对界面进行更新
                mText.setText("This update by message");
                break;
            }
        }
    }

    ○ 在新的线程中发送消息    

 

    private void updateByMessage()
    {
        //匿名对象
         new Thread()
         {
                public void run()
                {
                    //mText.setText("This is Update from ohter thread, Mouse DOWN");

                    //UPDATE是一个自己定义的整数,代表了消息ID
                    Message msg = mHandler.obtainMessage(UPDATE);
                    mHandler.sendMessage(msg);
                }
         }.start();
    }

 

 

-----------------------------------------------华丽的分割线-----------------------------------------------

 

android的消息处理有三个核心类:Looper,Handler和Message。其实还有一个Message Queue(消息队列),但是MQ被封装到Looper里面了,我们不会直接与MQ打交道,因此我没将其作为核心类。下面一一介绍:

线程的魔法师 Looper

Looper的字面意思是“循环者”,它被设计用来使一个普通线程变成Looper线程。所谓Looper线程就是循环工作的线程。在程序开发中(尤其是GUI开发中),我们经常会需要一个线程不断循环,一旦有新任务则执行,执行完继续等待下一个任务,这就是Looper线程。使用Looper类创建Looper线程很简单:

复制代码
  1. <pre name="code" class="java">public class LooperThread extends Thread {  
  2.   
  3.  @Override  
  4. public void run() {  
  5.         // 将当前线程初始化为Looper线程   
  6.         Looper.prepare();  
  7.         // ...其他处理,如实例化handler  
  8.         // 开始循环处理消息队列   
  9.         Looper.loop();  
  10.     }  
  11. }  
复制代码

通过上面两行核心代码,你的线程就升级为Looper线程了!!!是不是很神奇?让我们放慢镜头,看看这两行代码各自做了什么。

1)Looper.prepare()

通过上图可以看到,现在你的线程中有一个Looper对象,它的内部维护了一个消息队列MQ。注意,一个Thread只能有一个Looper对象,为什么呢?咱们来看源码。

复制代码
  1. public class Looper {  
  2.     // 每个线程中的Looper对象其实是一个ThreadLocal,即线程本地存储(TLS)对象  
  3.     private static final ThreadLocal sThreadLocal = new ThreadLocal();  
  4.   
  5.     // Looper内的消息队列   
  6.     final MessageQueue mQueue;  
  7.   
  8.     // 当前线程   
  9.     Thread mThread;  
  10.   
  11.     // 。。。其他属性   
  12.   
  13.     // 每个Looper对象中有它的消息队列,和它所属的线程  
  14.     private Looper() {  
  15.         mQueue = new MessageQueue();  
  16.         mRun = true;  
  17.         mThread = Thread.currentThread();  
  18.     }  
  19.   
  20.     // 我们调用该方法会在调用线程的TLS中创建Looper对象   
  21.     public static final void prepare() {  
  22.         if (sThreadLocal.get() != null) {  
  23.             // 试图在有Looper的线程中再次创建Looper将抛出异常  
  24.             throw new RuntimeException("Only one Looper may be created per thread");  
  25.         }  
  26.         sThreadLocal.set(new Looper());  
  27.     }  
  28.     // 其他方法   
  29. }  
复制代码

通过源码,prepare()背后的工作方式一目了然,其核心就是将looper对象定义为ThreadLocal。如果你还不清楚什么是ThreadLocal,请参考《理解ThreadLocal》

2)Looper.loop()

调用loop方法后,Looper线程就开始真正工作了,它不断从自己的MQ中取出队头的消息(也叫任务)执行。其源码分析如下:

复制代码
  1. public static final void loop() {  
  2.         Looper me = myLooper();  //得到当前线程Looper  
  3.         MessageQueue queue = me.mQueue;  //得到当前looper的MQ  
  4.         // 这两行没看懂= = 不过不影响理解   
  5.         Binder.clearCallingIdentity();  
  6.   
  7.         final long ident = Binder.clearCallingIdentity();  
  8.   
  9.         // 开始循环   
  10.         while (true) {  
  11.             Message msg = queue.next(); // 取出message  
  12.             if (msg != null) {  
  13.                 if (msg.target == null) {  
  14.                     // message没有target为结束信号,退出循环  
  15.                     return;  
  16.                 }  
  17.                 // 日志。。。   
  18.                 if (me.mLogging!= null) me.mLogging.println(  
  19.                         ">>>>> Dispatching to " + msg.target + " "  
  20.                         + msg.callback + ": " + msg.what  
  21.                         );  
  22.                 // 非常重要!将真正的处理工作交给message的target,即后面要讲的handler  
  23.                 msg.target.dispatchMessage(msg);  
  24.                 // 还是日志。。。   
  25.                 if (me.mLogging!= null) me.mLogging.println(  
  26.                         "<<<<< Finished to    " + msg.target + " "  
  27.                         + msg.callback);  
  28.                                 // 下面没看懂,同样不影响理解  
  29.                 final long newIdent = Binder.clearCallingIdentity();  
  30.                 if (ident != newIdent) {  
  31.                     Log.wtf("Looper""Thread identity changed from 0x"  
  32.                             + Long.toHexString(ident) + " to 0x"  
  33.                             + Long.toHexString(newIdent) + " while dispatching to "  
  34.                             + msg.target.getClass().getName() + " "+ msg.callback + " what=" + msg.what);  
  35.                 }  
  36.                 // 回收message资源   
  37.                 msg.recycle();  
  38.             }  
  39.         }  
  40.     }  
 

复制代码

除了prepare()和loop()方法,Looper类还提供了一些有用的方法,比如

Looper.myLooper()得到当前线程looper对象:

  1. public static final Looper myLooper() {  
  2.         // 在任意线程调用Looper.myLooper()返回的都是那个线程的looper  
  3.         return (Looper)sThreadLocal.get();  
  4. }  

getThread()得到looper对象所属线程:

  1. public Thread getThread() {  
  2.         return mThread;  
  3. }  

quit()方法结束looper循环:

复制代码
  1. public void quit() {  
  2.         // 创建一个空的message,它的target为NULL,表示结束循环消息  
  3.         Message msg = Message.obtain();  
  4.         // 发出消息   
  5.         mQueue.enqueueMessage(msg, 0);  
  6. }  
复制代码

到此为止,你应该对Looper有了基本的了解,总结几点:

1.每个线程有且最多只能有一个Looper对象,它是一个ThreadLocal

2.Looper内部有一个消息队列,loop()方法调用后线程开始不断从队列中取出消息执行

3.Looper使一个线程变成Looper线程。

那么,我们如何往MQ上添加消息呢?下面有请Handler!(掌声~~~)

异步处理大师 Handler

什么是handler?handler扮演了往MQ上添加消息和处理消息的角色(只处理由自己发出的消息),即通知MQ它要执行一个任务(sendMessage),并在loop到自己的时候执行该任务(handleMessage),整个过程是异步的。handler创建时会关联一个looper,默认的构造方法将关联当前线程的looper,不过这也是可以set的。默认的构造方法:

复制代码
  1. public class handler {  
  2.     final MessageQueue mQueue;  // 关联的MQ  
  3.   
  4.     final Looper mLooper;  // 关联的looper  
  5.   
  6.     final Callback mCallback;   
  7.   
  8.     // 其他属性   
  9.   
  10.     public Handler() {  
  11.         // 没看懂,直接略过,,,   
  12.         if (FIND_POTENTIAL_LEAKS) {  
  13.             final Class<? extends Handler> klass = getClass();  
  14.             if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&  
  15.                     (klass.getModifiers() & Modifier.STATIC) == 0) {  
  16.                 Log.w(TAG, "The following Handler class should be static or leaks might occur: " +  
  17.                     klass.getCanonicalName());  
  18.             }  
  19.         }  
  20.         // 默认将关联当前线程的looper   
  21.         mLooper = Looper.myLooper();  
  22.         // looper不能为空,即该默认的构造方法只能在looper线程中使用  
  23.         if (mLooper == null) {  
  24.             throw new RuntimeException(  
  25.                 "Can't create handler inside thread that has not called Looper.prepare()");  
  26.         }  
  27.         // 重要!!!直接把关联looper的MQ作为自己的MQ,因此它的消息将发送到关联looper的MQ上  
  28.         mQueue = mLooper.mQueue;  
  29.         mCallback = null;  
  30.     }  
  31.     // 其他方法   
  32. }  
复制代码

下面我们就可以为之前的LooperThread类加入Handler:

复制代码
  1. public class LooperThread extends Thread {  
  2.     private Handler handler1;  
  3.   
  4.     private Handler handler2;  
  5.   
  6.     @Override  
  7.     public void run() {  
  8.         // 将当前线程初始化为Looper线程   
  9.         Looper.prepare();  
  10.         // 实例化两个handler   
  11.         handler1 = new Handler();  
  12.         handler2 = new Handler();  
  13.         // 开始循环处理消息队列   
  14.         Looper.loop();  
  15.     }  
  16. }  
复制代码

加入handler后的效果如下图:

可以看到,一个线程可以有多个Handler,但是只能有一个Looper!

Handler发送消息

有了handler之后,我们就可以使用 post(Runnable),postAtTime(Runnable, long),postDelayed(Runnable, long),sendEmptyMessage(int),sendMessage(Message),sendMessageAtTime(Message, long)sendMessageDelayed(Message, long)这些方法向MQ上发送消息了。光看这些API你可能会觉得handler能发两种消息,一种是Runnable对象,一种是message对象,这是直观的理解,但其实post发出的Runnable对象最后都被封装成message对象了,见源码:

复制代码
  1. public final boolean post(Runnable r)  
  2. {  
  3.     // 注意getPostMessage(r)将runnable封装成message  
  4.     return  sendMessageDelayed(getPostMessage(r), 0);  
  5. }  
  6.   
  7. private final Message getPostMessage(Runnable r) {  
  8.   
  9.      Message m = Message.obtain();  //得到空的message  
  10.      m.callback = r;  //将runnable设为message的callback  
  11.      return m;  
  12. }  
  13.   
  14. public boolean sendMessageAtTime(Message msg, long uptimeMillis)  
  15. {  
  16.      boolean sent = false;  
  17.      MessageQueue queue = mQueue;  
  18.      if (queue != null) {  
  19.          msg.target = this;  // message的target必须设为该handler!  
  20.          sent = queue.enqueueMessage(msg, uptimeMillis);  
  21.      }  
  22.      else {  
  23.          RuntimeException e = new RuntimeException(  
  24.              this + " sendMessageAtTime() called with no mQueue");  
  25.          Log.w("Looper", e.getMessage(), e);  
  26.       }  
  27.       return sent;  
  28. }</span>  
复制代码

其他方法就不罗列了,总之通过handler发出的message有如下特点:

1.message.target为该handler对象,这确保了looper执行到该message时能找到处理它的handler,即loop()方法中的关键代码

msg.target.dispatchMessage(msg);

2.post发出的message,其callback为Runnable对象

Handler处理消息

说完了消息的发送,再来看下handler如何处理消息。消息的处理是通过核心方法dispatchMessage(Message msg)与钩子方法handleMessage(Message msg)完成的,见源码

复制代码
  1. // 处理消息,该方法由looper调用   
  2. public void dispatchMessage(Message msg) {  
  3.     if (msg.callback != null) {  
  4.         // 如果message设置了callback,即runnable消息,处理callback!  
  5.         handleCallback(msg);  
  6.     } else {  
  7.         // 如果handler本身设置了callback,则执行callback  
  8.         if (mCallback != null) {  
  9.              /* 这种方法允许让activity等来实现Handler.Callback接口,避免了自己编写handler重写handleMessage方法。见http://alex-yang-xiansoftware-com.iteye.com/blog/850865 */  
  10.             if (mCallback.handleMessage(msg)) {  
  11.                 return;  
  12.             }  
  13.         }  
  14.         // 如果message没有callback,则调用handler的钩子方法handleMessage  
  15.         handleMessage(msg);  
  16.     }  
  17. }  
  18.   
  19.     // 处理runnable消息   
  20. private final void handleCallback(Message message) {  
  21.     message.callback.run();  //直接调用run方法!  
  22. }  
  23.   
  24. // 由子类实现的钩子方法   
  25. public void handleMessage(Message msg) {  
  26. }  
复制代码

可以看到,除了handleMessage(Message msg)和Runnable对象的run方法由开发者实现外(实现具体逻辑),handler的内部工作机制对开发者是透明的。这正是handler API设计的精妙之处!

Handler的用处

我在小标题中将handler描述为“异步处理大师”,这归功于Handler拥有下面两个重要的特点:

1.handler可以在任意线程发送消息,这些消息会被添加到关联的MQ上。

              

2.handler是在它关联的looper线程中处理消息的。

这就解决了android最经典的不能在其他非主线程中更新UI的问题。android的主线程也是一个looper线程(looper在android中运用很广),我们在其中创建的handler默认将关联主线程MQ。因此,利用handler的一个solution就是在activity中创建handler并将其引用传递给worker thread,worker thread执行完任务后使用handler发送消息通知activity更新UI。(过程如图)

当然,handler能做的远远不仅如此,由于它能post Runnable对象,它还能与Looper配合实现经典的Pipeline Thread(流水线线程)模式。请参考此文《Android Guts: Intro to Loopers and Handlers》

封装任务 Message

在整个消息处理机制中,message又叫task,封装了任务携带的信息和处理该任务的handler。message的用法比较简单,这里不做总结了。但是有这么几点需要注意(待补充):

1.尽管Message有public的默认构造方法,但是你应该通过Message.obtain()来从消息池中获得空消息对象,以节省资源。

2.如果你的message只需要携带简单的int信息,请优先使用Message.arg1和Message.arg2来传递信息,这比用Bundle更省内存

3.擅用message.what来标识信息,以便用不同方式处理message。

 

 

-----------------------------------------------华丽的分割线-----------------------------------------------

 

        我们知道,Android应用程序是通过消息来驱动的,即在应用程序的主线程(UI线程)中有一个消息循环,负责处理消息队列中的消息。我们也知道,Android应用程序是支持多线程的,即可以创建子线程来执行一些计算型的任务,那么,这些子线程能不能像应用程序的主线程一样具有消息循环呢?这些子线程又能不能往应用程序的主线程中发送消息呢?本文将分析Android应用程序线程消息处理模型,为读者解答这两个问题

        在开发Android应用程序中,有时候我们需要在应用程序中创建一些常驻的子线程来不定期地执行一些不需要与应用程序界面交互的计算型的任务。如果这些子线程具有消息循环,那么它们就能够常驻在应用程序中不定期的执行一些计算型任务了:当我们需要用这些子线程来执行任务时,就往这个子线程的消息队列中发送一个消息,然后就可以在子线程的消息循环中执行我们的计算型任务了。我们在前面一篇文章Android系统默认Home应用程序(Launcher)的启动过程源代码分析中,介绍Launcher的启动过程时,在Step 15(LauncherModel.startLoader)中,Launcher就是通过往一个子线程的消息队列中发送一个消息(sWorker.post(mLoaderTask)),然后子线程就会在它的消息循环中处理这个消息的时候执行从PackageManagerService中获取系统中已安装应用程序的信息列表的任务,即调用Step 16中的LoaderTask.run函数。

        在开发Android应用程序中,有时候我们又需要在应用程序中创建一些子线程来执行一些需要与应用程序界面进交互的计算型任务。典型的应用场景是当我们要从网上下载文件时,为了不使主线程被阻塞,我们通常创建一个子线程来负责下载任务,同时,在下载的过程,将下载进度以百分比的形式在应用程序的界面上显示出来,这样就既不会阻塞主线程的运行,又能获得良好的用户体验。但是,我们知道,Android应用程序的子线程是不可以操作主线程的UI的,那么,这个负责下载任务的子线程应该如何在应用程序界面上显示下载的进度呢?如果我们能够在子线程中往主线程的消息队列中发送消息,那么问题就迎刃而解了,因为发往主线程消息队列的消息最终是由主线程来处理的,在处理这个消息的时候,我们就可以在应用程序界面上显示下载进度了。

        上面提到的这两种情况,Android系统都为我们提供了完善的解决方案,前者可以通过使用HandlerThread类来实现,而后者可以使用AsyncTask类来实现,本文就详细这两个类是如何实现的。不过,为了更好地理解HandlerThread类和AsyncTask类的实现,我们先来看看应用程序的主线程的消息循环模型是如何实现的。

        1. 应用程序主线程消息循环模型

        在前面一篇文章Android应用程序进程启动过程的源代码分析一文中,我们已经分析应用程序进程(主线程)的启动过程了,这里主要是针对它的消息循环模型作一个总结。当运行在Android应用程序框架层中的ActivityManagerService决定要为当前启动的应用程序创建一个主线程的时候,它会在ActivityManagerService中的startProcessLocked成员函数调用Process类的静态成员函数start为当前应用程序创建一个主线程:

  1. public final class ActivityManagerService extends ActivityManagerNative      
  2.         implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {      
  3.       
  4.     ......      
  5.       
  6.     private final void startProcessLocked(ProcessRecord app,      
  7.                 String hostingType, String hostingNameStr) {      
  8.       
  9.         ......      
  10.       
  11.         try {      
  12.             int uid = app.info.uid;      
  13.             int[] gids = null;      
  14.             try {      
  15.                 gids = mContext.getPackageManager().getPackageGids(      
  16.                     app.info.packageName);      
  17.             } catch (PackageManager.NameNotFoundException e) {      
  18.                 ......      
  19.             }      
  20.                   
  21.             ......      
  22.       
  23.             int debugFlags = 0;      
  24.                   
  25.             ......      
  26.                   
  27.             int pid = Process.start("android.app.ActivityThread",      
  28.                 mSimpleProcessManagement ? app.processName : null, uid, uid,      
  29.                 gids, debugFlags, null);      
  30.                   
  31.             ......      
  32.       
  33.         } catch (RuntimeException e) {      
  34.                   
  35.             ......      
  36.       
  37.         }      
  38.     }      
  39.       
  40.     ......      
  41.       
  42. }      

        这里我们主要关注Process.start函数的第一个参数“android.app.ActivityThread”,它表示要在当前新建的线程中加载android.app.ActivityThread类,并且调用这个类的静态成员函数main作为应用程序的入口点。ActivityThread类定义在frameworks/base/core/java/android/app/ActivityThread.java文件中:

  1. public final class ActivityThread {    
  2.     ......    
  3.     
  4.     public static final void main(String[] args) {    
  5.         ......  
  6.     
  7.         Looper.prepareMainLooper();    
  8.            
  9.         ......    
  10.     
  11.         ActivityThread thread = new ActivityThread();    
  12.         thread.attach(false);    
  13.     
  14.         ......   
  15.         Looper.loop();    
  16.     
  17.         ......   
  18.     
  19.         thread.detach();    
  20.         ......    
  21.     }    
  22.     
  23.     ......    
  24. }    

        在这个main函数里面,除了创建一个ActivityThread实例外,就是在进行消息循环了。

        在进行消息循环之前,首先会通过Looper类的静态成员函数prepareMainLooper为当前线程准备一个消息循环对象。Looper类定义在frameworks/base/core/java/android/os/Looper.java文件中:

  1. public class Looper {  
  2.     ......  
  3.   
  4.     // sThreadLocal.get() will return null unless you've called prepare().  
  5.     private static final ThreadLocal sThreadLocal = new ThreadLocal();  
  6.   
  7.     ......  
  8.   
  9.     private static Looper mMainLooper = null;  
  10.   
  11.     ......  
  12.   
  13.     public static final void prepare() {  
  14.         if (sThreadLocal.get() != null) {  
  15.             throw new RuntimeException("Only one Looper may be created per thread");  
  16.         }  
  17.         sThreadLocal.set(new Looper());  
  18.     }  
  19.   
  20.     ......  
  21.   
  22.     public static final void prepareMainLooper() {  
  23.         prepare();  
  24.         setMainLooper(myLooper());  
  25.         ......  
  26.     }  
  27.   
  28.     private synchronized static void setMainLooper(Looper looper) {  
  29.         mMainLooper = looper;  
  30.     }  
  31.   
  32.     public synchronized static final Looper getMainLooper() {  
  33.         return mMainLooper;  
  34.     }  
  35.   
  36.     ......  
  37.   
  38.     public static final Looper myLooper() {  
  39.         return (Looper)sThreadLocal.get();  
  40.     }  
  41.   
  42.     ......  
  43. }  

        Looper类的静态成员函数prepareMainLooper是专门应用程序的主线程调用的,应用程序的其它子线程都不应该调用这个函数来在本线程中创建消息循环对象,而应该调用prepare函数来在本线程中创建消息循环对象,下一节我们介绍一个线程类HandlerThread 时将会看到。

        为什么要为应用程序的主线程专门准备一个创建消息循环对象的函数呢?这是为了让其它地方能够方便地通过Looper类的getMainLooper函数来获得应用程序主线程中的消息循环对象。获得应用程序主线程中的消息循环对象又有什么用呢?一般就是为了能够向应用程序主线程发送消息了。

        在prepareMainLooper函数中,首先会调用prepare函数在本线程中创建一个消息循环对象,然后将这个消息循环对象放在线程局部变量sThreadLocal中:

  1. sThreadLocal.set(new Looper());  

        接着再将这个消息循环对象通过调用setMainLooper函数来保存在Looper类的静态成员变量mMainLooper中:

  1. mMainLooper = looper;  

       这样,其它地方才可以调用getMainLooper函数来获得应用程序主线程中的消息循环对象。

       消息循环对象创建好之后,回到ActivityThread类的main函数中,接下来,就是要进入消息循环了:

  1. Looper.loop();   

        Looper类具体是如何通过loop函数进入消息循环以及处理消息队列中的消息,可以参考前面一篇文章Android应用程序消息处理机制(Looper、Handler)分析,这里就不再分析了,我们只要知道ActivityThread类中的main函数执行了这一步之后,就为应用程序的主线程准备好消息循环就可以了。

        2. 应用程序子线程消息循环模型

        在Java框架中,如果我们想在当前应用程序中创建一个子线程,一般就是通过自己实现一个类,这个类继承于Thread类,然后重载Thread类的run函数,把我们想要在这个子线程执行的任务都放在这个run函数里面实现。最后实例这个自定义的类,并且调用它的start函数,这样一个子线程就创建好了,并且会调用这个自定义类的run函数。但是当这个run函数执行完成后,子线程也就结束了,它没有消息循环的概念。

        前面说过,有时候我们需要在应用程序中创建一些常驻的子线程来不定期地执行一些计算型任务,这时候就可以考虑使用Android系统提供的HandlerThread类了,它具有创建具有消息循环功能的子线程的作用。

        HandlerThread类实现在frameworks/base/core/java/android/os/HandlerThread.java文件中,这里我们通过使用情景来有重点的分析它的实现。

        在前面一篇文章Android系统默认Home应用程序(Launcher)的启动过程源代码分析中,我们分析了Launcher的启动过程,其中在Step 15(LauncherModel.startLoader)和Step 16(LoaderTask.run)中,Launcher会通过创建一个HandlerThread类来实现在一个子线程加载系统中已经安装的应用程序的任务:

  1. public class LauncherModel extends BroadcastReceiver {  
  2.     ......  
  3.   
  4.     private LoaderTask mLoaderTask;  
  5.   
  6.     private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");  
  7.     static {  
  8.         sWorkerThread.start();  
  9.     }  
  10.     private static final Handler sWorker = new Handler(sWorkerThread.getLooper());  
  11.   
  12.     ......  
  13.   
  14.     public void startLoader(Context context, boolean isLaunching) {    
  15.         ......    
  16.   
  17.         synchronized (mLock) {    
  18.             ......    
  19.   
  20.             // Don't bother to start the thread if we know it's not going to do anything    
  21.             if (mCallbacks != null && mCallbacks.get() != null) {    
  22.                 ......  
  23.   
  24.                 mLoaderTask = new LoaderTask(context, isLaunching);    
  25.                 sWorker.post(mLoaderTask);    
  26.             }    
  27.         }    
  28.     }    
  29.   
  30.     ......  
  31.   
  32.     private class LoaderTask implements Runnable {    
  33.         ......    
  34.   
  35.         public void run() {    
  36.             ......    
  37.   
  38.             keep_running: {    
  39.                 ......    
  40.   
  41.                 // second step    
  42.                 if (loadWorkspaceFirst) {    
  43.                     ......    
  44.                     loadAndBindAllApps();    
  45.                 } else {    
  46.                     ......    
  47.                 }    
  48.   
  49.                 ......    
  50.             }    
  51.   
  52.             ......    
  53.         }    
  54.   
  55.         ......    
  56.     }   
  57.   
  58.     ......  
  59. }  

        在这个LauncherModel类中,首先创建了一个HandlerThread对象:

  1. private static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");  

        接着调用它的start成员函数来启动一个子线程:

  1. static {  
  2.     sWorkerThread.start();  
  3. }  

        接着还通过这个HandlerThread对象的getLooper函数来获得这个子线程中的消息循环对象,并且使用这个消息循环创建对象来创建一个Handler:

  1. private static final Handler sWorker = new Handler(sWorkerThread.getLooper());  

        有了这个Handler对象sWorker之后,我们就可以往这个子线程中发送消息,然后在处理这个消息的时候执行加载系统中已经安装的应用程序的任务了,在startLoader函数中:

  1. mLoaderTask = new LoaderTask(context, isLaunching);    
  2. sWorker.post(mLoaderTask);    

        这里的mLoaderTask是一个LoaderTask对象,它实现了Runnable接口,因此,可以把这个LoaderTask对象作为参数传给sWorker.post函数。在sWorker.post函数里面,会把这个LoaderTask对象封装成一个消息,并且放入这个子线程的消息队列中去。当这个子线程的消息循环处理这个消息的时候,就会调用这个LoaderTask对象的run函数,因此,我们就可以在LoaderTask对象的run函数中通过调用loadAndBindAllApps来执行加载系统中已经安装的应用程序的任务了。

        了解了HanderThread类的使用方法之后,我们就可以重点地来分析它的实现了:

  1. public class HandlerThread extends Thread {  
  2.     ......  
  3.     private Looper mLooper;  
  4.   
  5.     public HandlerThread(String name) {  
  6.         super(name);  
  7.         ......  
  8.     }  
  9.   
  10.     ......  
  11.   
  12.     public void run() {  
  13.         ......  
  14.         Looper.prepare();  
  15.         synchronized (this) {  
  16.             mLooper = Looper.myLooper();  
  17.             ......  
  18.         }  
  19.         ......  
  20.         Looper.loop();  
  21.         ......  
  22.     }  
  23.   
  24.     public Looper getLooper() {  
  25.         ......  
  26.         return mLooper;  
  27.     }  
  28.   
  29.     ......  
  30. }  

        首先我们看到的是,Handler类继承了Thread类,因此,通过它可以在应用程序中创建一个子线程,其次我们看到在它的run函数中,会进入一个消息循环中,因此,这个子线程可以常驻在应用程序中,直到它接收收到一个退出消息为止。

        在run函数中,首先是调用Looper类的静态成员函数prepare来准备一个消息循环对象:

  1. Looper.prepare();  

        然后通过Looper类的myLooper成员函数将这个子线程中的消息循环对象保存在HandlerThread类中的成员变量mLooper中:

  1. mLooper = Looper.myLooper();  

        这样,其它地方就可以方便地通过它的getLooper函数来获得这个消息循环对象了,有了这个消息循环对象后,就可以往这个子线程的消息队列中发送消息,通知这个子线程执行特定的任务了。

        最在这个run函数通过Looper类的loop函数进入消息循环中:

  1. Looper.loop();  

        这样,一个具有消息循环的应用程序子线程就准备就绪了。

        HandlerThread类的实现虽然非常简单,当然这得益于Java提供的Thread类和Android自己本身提供的Looper类,但是它的想法却非常周到,为应用程序开发人员提供了很大的方便。
        3. 需要与UI交互的应用程序子线程消息模型

        前面说过,我们开发应用程序的时候,经常中需要创建一个子线程来在后台执行一个特定的计算任务,而在这个任务计算的过程中,需要不断地将计算进度或者计算结果展现在应用程序的界面中。典型的例子是从网上下载文件,为了不阻塞应用程序的主线程,我们开辟一个子线程来执行下载任务,子线程在下载的同时不断地将下载进度在应用程序界面上显示出来,这样做出来程序就非常友好。由于子线程不能直接操作应用程序的UI,因此,这时候,我们就可以通过往应用程序的主线程中发送消息来通知应用程序主线程更新界面上的下载进度。因为类似的这种情景在实际开发中经常碰到,Android系统为开发人员提供了一个异步任务类(AsyncTask)来实现上面所说的功能,即它会在一个子线程中执行计算任务,同时通过主线程的消息循环来获得更新应用程序界面的机会。

        为了更好地分析AsyncTask的实现,我们先举一个例子来说明它的用法。在前面一篇文章Android系统中的广播(Broadcast)机制简要介绍和学习计划中,我们开发了一个应用程序Broadcast,其中使用了AsyncTask来在一个线程在后台在执行计数任务,计数过程通过广播(Broadcast)来将中间结果在应用程序界面上显示出来。在这个例子中,使用广播来在应用程序主线程和子线程中传递数据不是最优的方法,当时只是为了分析Android系统的广播机制而有意为之的。在本节内容中,我们稍微这个例子作一个简单的修改,就可以通过消息的方式来将计数过程的中间结果在应用程序界面上显示出来。

        为了区别Android系统中的广播(Broadcast)机制简要介绍和学习计划一文中使用的应用程序Broadcast,我们将本节中使用的应用程序命名为Counter。首先在Android源代码工程中创建一个Android应用程序工程,名字就为Counter,放在packages/experimental目录下。关于如何获得Android源代码工程,请参考在Ubuntu上下载、编译和安装Android最新源代码一文;关于如何在Android源代码工程中创建应用程序工程,请参考在Ubuntu上为Android系统内置Java应用程序测试Application Frameworks层的硬件服务一文。这个应用程序工程定义了一个名为shy.luo.counter的package,这个例子的源代码主要就是实现在这个目录下的Counter.java文件中:

  1. package shy.luo.counter;  
  2.   
  3. import android.app.Activity;  
  4. import android.content.ComponentName;  
  5. import android.content.Context;  
  6. import android.content.Intent;  
  7. import android.content.IntentFilter;  
  8. import android.os.Bundle;  
  9. import android.os.AsyncTask;  
  10. import android.util.Log;  
  11. import android.view.View;  
  12. import android.view.View.OnClickListener;  
  13. import android.widget.Button;  
  14. import android.widget.TextView;  
  15.   
  16. public class Counter extends Activity implements OnClickListener {  
  17.     private final static String LOG_TAG = "shy.luo.counter.Counter";  
  18.   
  19.     private Button startButton = null;  
  20.     private Button stopButton = null;  
  21.     private TextView counterText = null;  
  22.   
  23.     private AsyncTask<Integer, Integer, Integer> task = null;  
  24.     private boolean stop = false;  
  25.   
  26.     @Override  
  27.     public void onCreate(Bundle savedInstanceState) {  
  28.         super.onCreate(savedInstanceState);  
  29.         setContentView(R.layout.main);  
  30.   
  31.         startButton = (Button)findViewById(R.id.button_start);  
  32.         stopButton = (Button)findViewById(R.id.button_stop);  
  33.         counterText = (TextView)findViewById(R.id.textview_counter);  
  34.   
  35.         startButton.setOnClickListener(this);  
  36.         stopButton.setOnClickListener(this);  
  37.   
  38.         startButton.setEnabled(true);  
  39.         stopButton.setEnabled(false);  
  40.   
  41.   
  42.         Log.i(LOG_TAG, "Main Activity Created.");  
  43.     }  
  44.   
  45.   
  46.     @Override  
  47.     public void onClick(View v) {  
  48.         if(v.equals(startButton)) {  
  49.             if(task == null) {  
  50.                 task = new CounterTask();  
  51.                 task.execute(0);  
  52.   
  53.                 startButton.setEnabled(false);  
  54.                 stopButton.setEnabled(true);  
  55.             }  
  56.         } else if(v.equals(stopButton)) {  
  57.             if(task != null) {  
  58.                 stop = true;  
  59.                 task = null;  
  60.   
  61.                 startButton.setEnabled(true);  
  62.                 stopButton.setEnabled(false);  
  63.             }  
  64.         }  
  65.     }  
  66.   
  67.     class CounterTask extends AsyncTask<Integer, Integer, Integer> {  
  68.         @Override  
  69.         protected Integer doInBackground(Integer... vals) {  
  70.             Integer initCounter = vals[0];  
  71.   
  72.             stop = false;  
  73.             while(!stop) {  
  74.                 publishProgress(initCounter);  
  75.   
  76.                 try {  
  77.                     Thread.sleep(1000);  
  78.                 } catch (InterruptedException e) {  
  79.                     e.printStackTrace();  
  80.                 }  
  81.   
  82.                 initCounter++;  
  83.             }  
  84.   
  85.             return initCounter;  
  86.         }  
  87.   
  88.         @Override  
  89.         protected void onProgressUpdate(Integer... values) {  
  90.             super.onProgressUpdate(values);  
  91.   
  92.             String text = values[0].toString();  
  93.             counterText.setText(text);  
  94.         }  
  95.   
  96.         @Override  
  97.         protected void onPostExecute(Integer val) {  
  98.             String text = val.toString();  
  99.             counterText.setText(text);  
  100.         }  
  101.     };  
  102. }  

        这个计数器程序很简单,它在界面上有两个按钮Start和Stop。点击Start按钮时,便会创建一个CounterTask实例task,然后调用它的execute函数就可以在应用程序中启动一个子线程,并且通过调用这个CounterTask类的doInBackground函数来执行计数任务。在计数的过程中,会通过调用publishProgress函数来将中间结果传递到onProgressUpdate函数中去,在onProgressUpdate函数中,就可以把中间结果显示在应用程序界面了。点击Stop按钮时,便会通过设置变量stop为true,这样,CounterTask类的doInBackground函数便会退出循环,然后将结果返回到onPostExecute函数中去,在onPostExecute函数,会把最终计数结果显示在用程序界面中。

       在这个例子中,我们需要注意的是:

       A. CounterTask类继承于AsyncTask类,因此它也是一个异步任务类;

       B. CounterTask类的doInBackground函数是在后台的子线程中运行的,这时候它不可以操作应用程序的界面;

       C. CounterTask类的onProgressUpdate和onPostExecute两个函数是应用程序的主线程中执行,它们可以操作应用程序的界面。

       关于C这一点的实现原理,我们在后面会分析到,这里我们先完整地介绍这个例子,以便读者可以参考做一下实验。

       接下来我们再看看应用程序的配置文件AndroidManifest.xml:

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  3.       package="shy.luo.counter"  
  4.       android:versionCode="1"  
  5.       android:versionName="1.0">  
  6.     <application android:icon="@drawable/icon" android:label="@string/app_name">  
  7.         <activity android:name=".Counter"  
  8.                   android:label="@string/app_name">  
  9.             <intent-filter>  
  10.                 <action android:name="android.intent.action.MAIN" />  
  11.                 <category android:name="android.intent.category.LAUNCHER" />  
  12.             </intent-filter>  
  13.         </activity>  
  14.     </application>  
  15. </manifest>  

       这个配置文件很简单,我们就不介绍了。

       再来看应用程序的界面文件,它定义在res/layout/main.xml文件中:

  1. <?xml version="1.0" encoding="utf-8"?>    
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    
  3.     android:orientation="vertical"    
  4.     android:layout_width="fill_parent"    
  5.     android:layout_height="fill_parent"     
  6.     android:gravity="center">    
  7.     <LinearLayout    
  8.         android:layout_width="fill_parent"    
  9.         android:layout_height="wrap_content"    
  10.         android:layout_marginBottom="10px"    
  11.         android:orientation="horizontal"     
  12.         android:gravity="center">    
  13.         <TextView      
  14.         android:layout_width="wrap_content"     
  15.             android:layout_height="wrap_content"     
  16.             android:layout_marginRight="4px"    
  17.             android:gravity="center"    
  18.             android:text="@string/counter">    
  19.         </TextView>    
  20.         <TextView      
  21.             android:id="@+id/textview_counter"    
  22.         android:layout_width="wrap_content"     
  23.             android:layout_height="wrap_content"     
  24.             android:gravity="center"    
  25.             android:text="0">    
  26.         </TextView>    
  27.     </LinearLayout>    
  28.     <LinearLayout    
  29.         android:layout_width="fill_parent"    
  30.         android:layout_height="wrap_content"    
  31.         android:orientation="horizontal"     
  32.         android:gravity="center">    
  33.         <Button     
  34.             android:id="@+id/button_start"    
  35.             android:layout_width="wrap_content"    
  36.             android:layout_height="wrap_content"    
  37.             android:gravity="center"    
  38.             android:text="@string/start">    
  39.         </Button>    
  40.         <Button     
  41.             android:id="@+id/button_stop"    
  42.             android:layout_width="wrap_content"    
  43.             android:layout_height="wrap_content"    
  44.             android:gravity="center"    
  45.             android:text="@string/stop" >    
  46.         </Button>    
  47.      </LinearLayout>      
  48. </LinearLayout>    

       这个界面配置文件也很简单,等一下我们在模拟器把这个应用程序启动起来后,就可以看到它的截图了。

       应用程序用到的字符串资源文件位于res/values/strings.xml文件中:

  1. <?xml version="1.0" encoding="utf-8"?>    
  2. <resources>    
  3.     <string name="app_name">Counter</string>    
  4.     <string name="counter">Counter: </string>    
  5.     <string name="start">Start Counter</string>    
  6.     <string name="stop">Stop Counter</string>    
  7. </resources>   

       最后,我们还要在工程目录下放置一个编译脚本文件Android.mk:

  1. LOCAL_PATH:= $(call my-dir)          
  2. include $(CLEAR_VARS)          
  3.           
  4. LOCAL_MODULE_TAGS :optional          
  5.           
  6. LOCAL_SRC_FILES := $(call all-subdir-java-files)          
  7.           
  8. LOCAL_PACKAGE_NAME :Counter          
  9.           
  10. include $(BUILD_PACKAGE)    

       接下来就要编译了。有关如何单独编译Android源代码工程的模块,以及如何打包system.img,请参考如何单独编译Android源代码中的模块一文。
       执行以下命令进行编译和打包:

  1. USER-NAME@MACHINE-NAME:~/Android$ mmm packages/experimental/Counter            
  2. USER-NAME@MACHINE-NAME:~/Android$ make snod  

       这样,打包好的Android系统镜像文件system.img就包含我们前面创建的Counter应用程序了。
       再接下来,就是运行模拟器来运行我们的例子了。关于如何在Android源代码工程中运行模拟器,请参考在Ubuntu上下载、编译和安装Android最新源代码一文。
       执行以下命令启动模拟器:

  1. USER-NAME@MACHINE-NAME:~/Android$ emulator   

       最后我们就可以在Launcher中找到Counter应用程序图标,把它启动起来,点击Start按钮,就会看到应用程序界面上的计数器跑起来了:

        这样,使用AsyncTask的例子就介绍完了,下面,我们就要根据上面对AsyncTask的使用情况来重点分析它的实现了。

        AsyncTask类定义在frameworks/base/core/java/android/os/AsyncTask.java文件中:

  1. public abstract class AsyncTask<Params, Progress, Result> {  
  2.     ......  
  3.   
  4.     private static final BlockingQueue<Runnable> sWorkQueue =  
  5.             new LinkedBlockingQueue<Runnable>(10);  
  6.   
  7.     private static final ThreadFactory sThreadFactory = new ThreadFactory() {  
  8.         private final AtomicInteger mCount = new AtomicInteger(1);  
  9.   
  10.         public Thread newThread(Runnable r) {  
  11.             return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());  
  12.         }  
  13.     };  
  14.   
  15.     ......  
  16.   
  17.     private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,  
  18.         MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);  
  19.   
  20.     private static final int MESSAGE_POST_RESULT = 0x1;  
  21.     private static final int MESSAGE_POST_PROGRESS = 0x2;  
  22.     private static final int MESSAGE_POST_CANCEL = 0x3;  
  23.   
  24.     private static final InternalHandler sHandler = new InternalHandler();  
  25.   
  26.     private final WorkerRunnable<Params, Result> mWorker;  
  27.     private final FutureTask<Result> mFuture;  
  28.   
  29.     ......  
  30.   
  31.     public AsyncTask() {  
  32.         mWorker = new WorkerRunnable<Params, Result>() {  
  33.             public Result call() throws Exception {  
  34.                 ......  
  35.                 return doInBackground(mParams);  
  36.             }  
  37.         };  
  38.   
  39.         mFuture = new FutureTask<Result>(mWorker) {  
  40.             @Override  
  41.             protected void done() {  
  42.                 Message message;  
  43.                 Result result = null;  
  44.   
  45.                 try {  
  46.                     result = get();  
  47.                 } catch (InterruptedException e) {  
  48.                     android.util.Log.w(LOG_TAG, e);  
  49.                 } catch (ExecutionException e) {  
  50.                     throw new RuntimeException("An error occured while executing doInBackground()",  
  51.                         e.getCause());  
  52.                 } catch (CancellationException e) {  
  53.                     message = sHandler.obtainMessage(MESSAGE_POST_CANCEL,  
  54.                         new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null));  
  55.                     message.sendToTarget();  
  56.                     return;  
  57.                 } catch (Throwable t) {  
  58.                     throw new RuntimeException("An error occured while executing "  
  59.                         + "doInBackground()", t);  
  60.                 }  
  61.   
  62.                 message = sHandler.obtainMessage(MESSAGE_POST_RESULT,  
  63.                     new AsyncTaskResult<Result>(AsyncTask.this, result));  
  64.                 message.sendToTarget();  
  65.             }  
  66.         };  
  67.     }  
  68.   
  69.     ......  
  70.   
  71.     public final Result get() throws InterruptedException, ExecutionException {  
  72.         return mFuture.get();  
  73.     }  
  74.   
  75.     ......  
  76.   
  77.     public final AsyncTask<Params, Progress, Result> execute(Params... params) {  
  78.         ......  
  79.   
  80.         mWorker.mParams = params;  
  81.         sExecutor.execute(mFuture);  
  82.   
  83.         return this;  
  84.     }  
  85.   
  86.     ......  
  87.   
  88.     protected final void publishProgress(Progress... values) {  
  89.         sHandler.obtainMessage(MESSAGE_POST_PROGRESS,  
  90.             new AsyncTaskResult<Progress>(this, values)).sendToTarget();  
  91.     }  
  92.   
  93.         private void finish(Result result) {  
  94.                 ......  
  95.                 onPostExecute(result);  
  96.                 ......  
  97.         }  
  98.   
  99.     ......  
  100.   
  101.     private static class InternalHandler extends Handler {  
  102.         @SuppressWarnings({"unchecked""RawUseOfParameterizedType"})  
  103.         @Override  
  104.         public void handleMessage(Message msg) {  
  105.             AsyncTaskResult result = (AsyncTaskResult) msg.obj;  
  106.             switch (msg.what) {  
  107.                 case MESSAGE_POST_RESULT:  
  108.                  // There is only one result  
  109.                  result.mTask.finish(result.mData[0]);  
  110.                  break;  
  111.                 case MESSAGE_POST_PROGRESS:  
  112.                  result.mTask.onProgressUpdate(result.mData);  
  113.                  break;  
  114.                 case MESSAGE_POST_CANCEL:  
  115.                  result.mTask.onCancelled();  
  116.                  break;  
  117.             }  
  118.         }  
  119.     }  
  120.   
  121.     private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {  
  122.         Params[] mParams;  
  123.     }  
  124.   
  125.     private static class AsyncTaskResult<Data> {  
  126.         final AsyncTask mTask;  
  127.         final Data[] mData;  
  128.   
  129.         AsyncTaskResult(AsyncTask task, Data... data) {  
  130.             mTask = task;  
  131.             mData = data;  
  132.         }  
  133.     }  
  134. }  

        从AsyncTask的实现可以看出,当我们第一次创建一个AsyncTask对象时,首先会执行下面静态初始化代码创建一个线程池sExecutor:

  1. private static final BlockingQueue<Runnable> sWorkQueue =  
  2.     new LinkedBlockingQueue<Runnable>(10);  
  3.   
  4. private static final ThreadFactory sThreadFactory = new ThreadFactory() {  
  5.     private final AtomicInteger mCount = new AtomicInteger(1);  
  6.   
  7.     public Thread newThread(Runnable r) {  
  8.         return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());  
  9.     }  
  10. };  
  11.   
  12. ......  
  13.   
  14. private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,  
  15.     MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);  

        这里的ThreadPoolExecutor是Java提供的多线程机制之一,这里用的构造函数原型为:

  1. ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,   
  2.     BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)  

        各个参数的意义如下:

        corePoolSize -- 线程池的核心线程数量

        maximumPoolSize -- 线程池的最大线程数量

        keepAliveTime -- 若线程池的线程数数量大于核心线程数量,那么空闲时间超过keepAliveTime的线程将被回收

        unit -- 参数keepAliveTime使用的时间单位

        workerQueue -- 工作任务队列

        threadFactory -- 用来创建线程池中的线程
        简单来说,ThreadPoolExecutor的运行机制是这样的:每一个工作任务用一个Runnable对象来表示,当我们要把一个工作任务交给这个线程池来执行的时候,就通过调用ThreadPoolExecutor的execute函数来把这个工作任务加入到线程池中去。此时,如果线程池中的线程数量小于corePoolSize,那么就会调用threadFactory接口来创建一个新的线程并且加入到线程池中去,再执行这个工作任务;如果线程池中的线程数量等于corePoolSize,但是工作任务队列workerQueue未满,则把这个工作任务加入到工作任务队列中去等待执行;如果线程池中的线程数量大于corePoolSize,但是小于maximumPoolSize,并且工作任务队列workerQueue已经满了,那么就会调用threadFactory接口来创建一个新的线程并且加入到线程池中去,再执行这个工作任务;如果线程池中的线程量已经等于maximumPoolSize了,并且工作任务队列workerQueue也已经满了,这个工作任务就被拒绝执行了。

        创建好了线程池后,再创建一个消息处理器:

  1. private static final InternalHandler sHandler = new InternalHandler();  

        注意,这行代码是在应用程序的主线程中执行的,因此,这个消息处理器sHandler内部引用的消息循环对象looper是应用程序主线程的消息循环对象,消息处理器的实现机制具体可以参考前面一篇文章Android应用程序消息处理机制(Looper、Handler)分析

        AsyncTask类的静态初始化代码执行完成之后,才开始创建AsyncTask对象,即执行AsyncTask类的构造函数:

  1. public AsyncTask() {  
  2.     mWorker = new WorkerRunnable<Params, Result>() {  
  3.         public Result call() throws Exception {  
  4.             ......  
  5.             return doInBackground(mParams);  
  6.         }  
  7.     };  
  8.   
  9.     mFuture = new FutureTask<Result>(mWorker) {  
  10.         @Override  
  11.         protected void done() {  
  12.             Message message;  
  13.             Result result = null;  
  14.   
  15.             try {  
  16.                 result = get();  
  17.             } catch (InterruptedException e) {  
  18.                 android.util.Log.w(LOG_TAG, e);  
  19.             } catch (ExecutionException e) {  
  20.                 throw new RuntimeException("An error occured while executing doInBackground()",  
  21.                     e.getCause());  
  22.             } catch (CancellationException e) {  
  23.                 message = sHandler.obtainMessage(MESSAGE_POST_CANCEL,  
  24.                     new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null));  
  25.                 message.sendToTarget();  
  26.                 return;  
  27.             } catch (Throwable t) {  
  28.                 throw new RuntimeException("An error occured while executing "  
  29.                     + "doInBackground()", t);  
  30.             }  
  31.   
  32.             message = sHandler.obtainMessage(MESSAGE_POST_RESULT,  
  33.                 new AsyncTaskResult<Result>(AsyncTask.this, result));  
  34.             message.sendToTarget();  
  35.         }  
  36.     };  
  37. }  

        在AsyncTask类的构造函数里面,主要是创建了两个对象,分别是一个WorkerRunnable对象mWorker和一个FutureTask对象mFuture。

        WorkerRunnable类实现了Runnable接口,此外,它的内部成员变量mParams用于保存从AsyncTask对象的execute函数传进来的参数列表:

  1. private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {  
  2.     Params[] mParams;  
  3. }  

        FutureTask类也实现了Runnable接口,所以它可以作为一个工作任务通过调用AsyncTask类的execute函数添加到sExecuto线程池中去:

  1. public final AsyncTask<Params, Progress, Result> execute(Params... params) {  
  2.     ......  
  3.   
  4.     mWorker.mParams = params;  
  5.     sExecutor.execute(mFuture);  
  6.   
  7.     return this;  
  8. }  

       这里的FutureTask对象mFuture是用来封装前面的WorkerRunnable对象mWorker。当mFuture加入到线程池中执行时,它调用的是mWorker对象的call函数:

  1. mWorker = new WorkerRunnable<Params, Result>() {  
  2.     public Result call() throws Exception {  
  3.            ......  
  4.            return doInBackground(mParams);  
  5.         }  
  6. };  

        在call函数里面,会调用AsyncTask类的doInBackground函数来执行真正的任务,这个函数是要由AsyncTask的子类来实现的,注意,这个函数是在应用程序的子线程中执行的,它不可以操作应用程序的界面。

        我们可以通过mFuture对象来操作当前执行的任务,例如查询当前任务的状态,它是正在执行中,还是完成了,还是被取消了,如果是完成了,还可以通过它获得任务的执行结果,如果还没有完成,可以取消任务的执行。

        当工作任务mWorker执行完成的时候,mFuture对象中的done函数就会被被调用,根据任务的完成状况,执行相应的操作,例如,如果是因为异常而完成时,就会抛异常,如果是正常完成,就会把任务执行结果封装成一个AsyncTaskResult对象:

  1. private static class AsyncTaskResult<Data> {  
  2.     final AsyncTask mTask;  
  3.     final Data[] mData;  
  4.   
  5.     AsyncTaskResult(AsyncTask task, Data... data) {  
  6.         mTask = task;  
  7.         mData = data;  
  8.     }  
  9. }  

        其中,成员变量mData保存的是任务执行结果,而成员变量mTask指向前面我们创建的AsyncTask对象。
        最后把这个AsyncTaskResult对象封装成一个消息,并且通过消息处理器sHandler加入到应用程序主线程的消息队列中:

  1. message = sHandler.obtainMessage(MESSAGE_POST_RESULT,  
  2.     new AsyncTaskResult<Result>(AsyncTask.this, result));  
  3. message.sendToTarget();  

        这个消息最终就会在InternalHandler类的handleMessage函数中处理了:

  1. private static class InternalHandler extends Handler {  
  2.     @SuppressWarnings({"unchecked""RawUseOfParameterizedType"})  
  3.     @Override  
  4.     public void handleMessage(Message msg) {  
  5.         AsyncTaskResult result = (AsyncTaskResult) msg.obj;  
  6.         switch (msg.what) {  
  7.         case MESSAGE_POST_RESULT:  
  8.             // There is only one result  
  9.             result.mTask.finish(result.mData[0]);  
  10.             break;  
  11.         ......  
  12.         }  
  13.     }  
  14. }  

        在这个函数里面,最终会调用前面创建的这个AsyncTask对象的finish函数来进一步处理:

  1. private void finish(Result result) {  
  2.        ......  
  3.        onPostExecute(result);  
  4.        ......  
  5. }  

        这个函数调用AsyncTask类的onPostExecute函数来进一步处理,AsyncTask类的onPostExecute函数一般是要由其子类来重载的,注意,这个函数是在应用程序的主线程中执行的,因此,它可以操作应用程序的界面。
        在任务执行的过程当中,即执行doInBackground函数时候,可能通过调用publishProgress函数来将中间结果封装成一个消息发送到应用程序主线程中的消息队列中去:

  1. protected final void publishProgress(Progress... values) {  
  2.     sHandler.obtainMessage(MESSAGE_POST_PROGRESS,  
  3.         new AsyncTaskResult<Progress>(this, values)).sendToTarget();  
  4. }  

        这个消息最终也是由InternalHandler类的handleMessage函数来处理的:

  1. private static class InternalHandler extends Handler {  
  2.     @SuppressWarnings({"unchecked""RawUseOfParameterizedType"})  
  3.     @Override  
  4.     public void handleMessage(Message msg) {  
  5.         AsyncTaskResult result = (AsyncTaskResult) msg.obj;  
  6.         switch (msg.what) {  
  7.         ......  
  8.         case MESSAGE_POST_PROGRESS:  
  9.                  result.mTask.onProgressUpdate(result.mData);  
  10.                  break;  
  11.         ......  
  12.         }  
  13.     }  
  14. }  

        这里它调用前面创建的AsyncTask对象的onPorgressUpdate函数来进一步处理,这个函数一般是由AsyncTask的子类来实现的,注意,这个函数是在应用程序的主线程中执行的,因此,它和前面的onPostExecute函数一样,可以操作应用程序的界面。

       这样,AsyncTask类的主要实现就介绍完了,结合前面开发的应用程序Counter来分析,会更好地理解它的实现原理。

       至此,Android应用程序线程消息循环模型就分析完成了,理解它有利于我们在开发Android应用程序时,能够充分利用多线程的并发性来提高应用程序的性能以及获得良好的用户体验。

posted @ 2016-09-14 15:11  iwiniwin  阅读(116)  评论(0编辑  收藏  举报