Android中的消息队列和线程队列机制

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zhoupenglei/article/details/73162340

本文是在各个著名博客的基础上总结而成,感谢!

1、简单例子

在分析Android消息机制之前,我们先来看一段代码:
public class MainActivity extends Activity implements View.OnClickListener {        
         private TextView stateText;
         private Button btn;     
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        stateText = (TextView) findViewById(R.id.tv);
        btn = (Button) findViewById(R.id.btn);
       
        btn.setOnClickListener(this);
    }
 
         @Override
         public void onClick(View v) {
                  new WorkThread().start();
         }
        
         //工作线程
         private class WorkThread extends Thread {
                  @Override
                  public void run() {
                          //......处理比较耗时的操作
                         
                          //处理完成后改变状态
                          stateText.setText("completed");
                  }
         }
}
这段代码似乎看上去很正常,但是当你运行时就会发现,它会报一个致命性的异常:
ERROR/AndroidRuntime(421): FATAL EXCEPTION: Thread-8
ERROR/AndroidRuntime(421): android.view.ViewRoot$CalledFromWrongThreadException:
Only the original thread that created a view hierarchy can touch its views.
到底是怎么回事呢?原因在于,Android系统中的视图组件并不是线程安全的,如果要更新视图,必须在主线程中更新,不可以在子线程中执行更新的操作。
既然这样,我们就在子线程中通知主线程,让主线程做更新操作吧。那么,我们如何通知主线程呢?我们需要使用到Handler对象。
我们稍微修改一下上面的代码:
public class MainActivity extends Activity implements View.OnClickListener {
        
         private static final int COMPLETED = 0;
        
         private TextView stateText;
         private Button btn;
        
         private Handler handler = new Handler() {
                  @Override
                  public void handleMessage(Message msg) {
                          if (msg.what == COMPLETED) {
                                   stateText.setText("completed");
                          }
                  }
         };
        
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        stateText = (TextView) findViewById(R.id.tv);
        btn = (Button) findViewById(R.id.btn);
       
        btn.setOnClickListener(this);
    }
 
         @Override
         public void onClick(View v) {
                  new WorkThread().start();
         }
        
         //工作线程
         private class WorkThread extends Thread {
                  @Override
                  public void run() {
                          //......处理比较耗时的操作
                         
                          //处理完成后给handler发送消息
                          Message msg = new Message();
                          msg.what = COMPLETED;
                          handler.sendMessage(msg);
                  }
         }
}
通过上面这种方式,我们就可以解决线程安全的问题,把复杂的任务处理工作交给子线程去完成,然后子线程通过handler对象告知主线程,由主线程更新视图,这个过程中消息机制起着重要的作用。

2、Android中的消息机制分析

        熟悉Windows编程的朋友知道Windows程序是消息驱动的,并且有全局的消息循环系统。Google参考了Windows的消息循环机制,也在Android系统中实现了消息循环机制。
       Android通过Looper、Handler来实现消息循环机制。Android的消息循环是针对线程的,每个线程都可以有自己的消息队列和消息循环。Android系统中的Looper负责管理线程的消息队列和消息循环。通过Looper.myLooper()得到当前线程的Looper对象,通过Looper.getMainLooper()得到当前进程的主线程的Looper对象。
    前面提到,Android的消息队列和消息循环都是针对具体线程的,一个线程可以存在一个消息队列和消息循环。但是创建的工作线程默认是没有消息队列和消息循环的,如果想让工作线程具有消息队列和消息循环,就需要在线程中先调用Looper.prepare()来创建消息队列,然后调用Looper.loop()进入消息循环。下面是我们创建的工作线程:
class WorkThread extends Thread {
               public Handler mHandler;
 
               public void run() {
                   Looper.prepare();
 
                   mHandler = new Handler() {
                       public void handleMessage(Message msg) {
                           // 处理收到的消息
                       }
                   };
 
                   Looper.loop();
               }
           }
这样一来,我们创建的工作线程就具有了消息处理机制了。那么,为什么前边的示例中,我们怎么没有看到Looper.prepare()和Looper.loop()的调用呢?原因在于,我们的Activity是一个UI线程,运行在主线程中,Android系统会在Activity启动时为其创建一个消息队列和消息循环。
前面提到最多的是消息队列(MessageQueue)和消息循环(Looper),但是我们看到每个消息处理的地方都有Handler的存在,它是做什么的呢?Handler的作用是把消息加入特定的Looper所管理的消息队列中,并分发和处理该消息队列中的消息。构造Handler的时候可以指定一个Looper对象,如果不指定则利用当前线程的Looper对象创建。注:写在Looper.loop()之后的代码不会被立即执行,当调用后mHandler.getLooper().quit()后,loop才会中止,其后的代码才能得以运行。Looper对象通过MessageQueue来存放消息和事件。

下面是消息机制中几个重要成员的关系图:


一个Activity中可以创建出多个工作线程,如果这些线程把他们消息放入Activity主线程的消息队列中,那么消息就会在主线程中处理了。因为主线程一般负责视图组件的更新操作,对于不是线程安全的视图组件来说,这种方式能够很好的实现视图的更新 。

    那么,子线程如何把消息放入主线程的消息队列中呢?只要Handler对象以主线程的Looper创建,那么当调用Handler的sendMessage方法,系统就会把消息主线程的消息队列,并且将会在调用handleMessage方法时处理主线程消息队列中的消息 。

对于子线程访问主线程的Handler对象,你可能会问,多个子线程都访问主线程的Handler对象,发送消息和处理消息的过程中会不会出现数据的不一致呢?答案是Handler对象不会出现问题,因为Handler对象管理的Looper对象是线程安全的,不管是添加消息到消息队列还是从消息队列中读取消息都是同步保护的,所以不会出现数据不一致现象。

Android中的Looper类,是用来封装消息循环和消息队列的一个类,用于在android线程中进行消息处理。handler其实可以看做是一个工具类,用来向消息队列中插入消息的。

    (1) Looper类用来为一个线程开启一个消息循环。     默认情况下android中新诞生的线程是没有开启消息循环的。(主线程除外,主线程系统会自动为其创建Looper对象,开启消息循环。)     Looper对象通过MessageQueue来存放消息和事件。一个线程只能有一个Looper,对应一个MessageQueue。 

(2) 通常是通过Handler对象来与Looper进行交互的。Handler可看做是Looper的一个接口,用来向指定的Looper发送消息及定义处理方法。     默认情况下Handler会与其被定义时所在线程的Looper绑定,比如,Handler在主线程中定义,那么它是与主线程的Looper绑定。 mainHandler = new Handler() 等价于new Handler(Looper.myLooper()). Looper.myLooper():获取当前进程的looper对象,类似的 Looper.getMainLooper() 用于获取主线程的Looper对象。 

(3) 在非主线程中直接new Handler() 会报如下的错误: E/AndroidRuntime( 6173): Uncaught handler: thread Thread-8 exiting due to uncaught exception E/AndroidRuntime( 6173): java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() 原因是非主线程中默认没有创建Looper对象,需要先调用Looper.prepare()启用Looper。 

(4) Looper.loop(); 让Looper开始工作,从消息队列里取消息,处理消息。 

    注意:写在Looper.loop()之后的代码不会被执行,这个函数内部应该是一个循环,当调用mHandler.getLooper().quit()后,loop才会中止,其后的代码才能得以运行。
    把下面例子中的mHandler声明成类成员,在主线程通过mHandler发送消息即可。         Android官方文档中Looper的介绍: Class used to run a message loop for a thread. Threads by default do not have a message loop associated with them; to create one, call prepare() in the thread that is to run the loop, and then loop() to have it process messages until the loop is stopped.
Most interaction with a message loop is through the Handler class. 

This is a typical example of the implementation of a Looper thread, using the separation of prepare() and loop() to create an initial Handler to communicate with the Looper.

class LooperThread extends Thread

{

public Handler mHandler;

public void run() 

{

Looper.prepare();

mHandler = new Handler() 

{

public void handleMessage(Message msg) 

{

// process incoming messages here

}

};

Looper.loop();

}

3、线程队列

Handler 就是实现队列的形式,一个 Handler 共有两个队列:一个是线程队列,另一个是消息队列 。

要应用 Handler 进行线程队列,其流程主要是:

1.        要定义一个 Handler 对象;

2.        定义一个线程,并覆写其 run 方法;

3.        通过 Handler 对象把第二的线程对象压到队列中等待执行;

4.        Handler 对象把第二的线程对象从队列中去除(当不想执行线程时 )。

注:如果需要循环执行线程,即可在处理线程后,再次利用 Handler 对象把线程对象压到队列中。

定义一个 Handler 对象的代码如下:

Handler myFirstHandler= new Handler  

定义一个线程的代码如下:

Runnable myThread = new Runnable() {

        public void run() {

            // TODO Auto-generated method stub

                  }

};
Handler 对象把线程压入队列的方法是:

        myFirstHandler.post(myThread);  //把一个线程放到队列里面去  

Handler 对象把线程从队列中去除的方法是:
     myFirstHandler.removeCallbacks(myThread);//把线程从队列中去除 

      通过上面我们的处理,虽然通过 Handler 对象往队列里面加入了一个新的线程,但实际上Handler 和它所属的 Activity 是处于同一个线程中。因为我们通过 Handler 把线程加到队列中,实际是直接执行了 Runable 里面的 run 方法,而且没有像 JAVA 经典多线程编程一样调用线程的start 方法,所以只是把线程对象加到 Activity 的线程中一起执行,而不是新开一个线程,即不是多真正的多线程。 

4、runOnUiThread更新主线程

更新UI采用Handle+Thread,需要发送消息,接受处理消息(在回调方法中处理),比较繁琐。除此之外,还可以使用runOnUiThread方法。

利用Activity.runOnUiThread(Runnable)把更新ui的代码创建在Runnable中,然后在需要更新ui时,把这个Runnable对象传给Activity.runOnUiThread(Runnable)。 

Runnable对像就能在ui程序中被调用。如果当前线程是UI线程,那么行动是立即执行。如果当前线程不是UI线程,操作是发布到事件队列的UI线程。

public class TestActivity extends Activity {

    Button btn;

    protected void onCreate(Bundle savedInstanceState) {

         super.onCreate(savedInstanceState);

         setContentView(R.layout.handler_msg);

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

         btn.setOnClickListener(new OnClickListener() {

             public void onClick(View view) {

                  new Thread(new Runnable() {

                      @Override

                      public void run() {

                              // 模拟耗时的操作。

                          try {

                               Thread.sleep(10000);

                          } catch (InterruptedException e) {

                               // TODO Auto-generated catch block

                               e.printStackTrace();

                          }

                          // 更新主线程UI

                          TestActivity.this.runOnUiThread(new Runnable() {

                               @Override

                               public void run() {

                                   // TODO Auto-generated method stub

                                   btn.setText("更新完毕!");

                               }

                          });

                      }

                  }).start();

             }

         });

    }

5、不是所有的Handler都能更新UI

Handler处理消息总是在创建Handler的线程里运行而我们的消息处理中,不乏更新UI的操作,不正确的线程直接更新UI将引发异常。因此,需要时刻关心Handler在哪个线程里创建的。如何更新UI才能不出异常呢?SDK告诉我们,有以下4种方式可以从其它线程访问UI线程(也即线程间通信)

·      Activity.runOnUiThread(Runnable)

·      View.post(Runnable)

·      View.postDelayed(Runnable, long)

·      UI线程中创建的Handler

其中,重点说一下的是View.post(Runnable)方法。在post(Runnableaction)方法里,View获得当前线程(即UI线程)的Handler,然后将action对象postHandler里。在Handler里,它将传递过来的action对象包装成一个MessageMessagecallbackaction),然后将其投入UI线程的消息循环中。在 Handler再次处理该Message时,有一条分支(未解释的那条)就是为它所设,直接调用runnablerun方法。而此时,已经路由到UI线程里,因此,我们可以毫无顾虑的来更新UI

几点小结

·      Handler的处理过程运行在创建Handler的线程里

·      一个Looper对应一个MessageQueue,一个线程对应一个Looper,一个Looper可以对应多个Handler

·      不确定当前线程时,更新UI时尽量调用View.post方法

·      handler应该由处理消息的线程创建。

·      handler与创建它的线程相关联,而且也只与创建它的线程相关联。handler运行在创建它的线程中,所以,如果在handler中进行耗时的操作,会阻塞创建它的线程

·      Android的线程分为有消息循环的线程和没有消息循环的线程,有消息循环的线程一般都会有一个Looper。主线程(UI线程)就是一个消息循环的线程。

·      Looper.myLooper();      //获得当前的Looper

        Looper.getMainLooper() //获得UI线程的Lopper

·      Handle的初始化函数(构造函数),如果没有参数,那么他就默认使用的是当前的Looper,如果有Looper参数,就是用对应的线程的Looper

·      如果一个线程中调用Looper.prepare(),那么系统就会自动的为该线程建立一个消息队列,然后调用Looper.loop();之后就进入了消息循环,这个之后就可以发消息、取消息、和处理消息。

6、HandlerThread

在上面的总结中指出,Android的线程分为有消息循环的线程和没有消息循环的线程,有消息循环的线程一般都会有一个Looper。事实上Android提供了一个封装好的带有looper的线程类,即为HandlerThread,具体可参见下面的代码:

public class HandlerThreadActivity extends Activity {
    private static final String TAG = "HandlerThreadActivity";
    private HandlerThreadmHandlerThread;
    private MyHandler mMyHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
       // TODO Auto-generatedmethod stub
       super.onCreate(savedInstanceState);
       TextView text = new TextView(this);
       text.setText("HandlerThreadActivity");
       setContentView(text);
      
       Log.d(TAG, "The mainthread id = " +      Thread.currentThread().getId());
      
       //生成一个HandlerThread对象,实现了使用Looper来处理消息队列的功能,
       //这个类由Android应用程序框架提供
       mHandlerThread = new HandlerThread("handler_thread");
      
       //在使用HandlerThread的getLooper()方法之前,必须先调用该类的start();
       mHandlerThread.start();
       //即这个Handler是运行在mHandlerThread这个线程中
       mMyHandler = new MyHandler(mHandlerThread.getLooper());
      
       mMyHandler.sendEmptyMessage(1);
    }
   
    private class MyHandler extends Handler {
      
       public MyHandler(Looper looper) {
           super(looper);
       }
 
       @Override
       public void handleMessage(Message msg) {
           Log.d(TAG, "MyHandler-->handleMessage-->threadid = " +Thread.currentThread().getId());
           super.handleMessage(msg);
       }
    }
   
}
展开阅读全文

没有更多推荐了,返回首页