Handler消息传递机制——Handler、Loop、MessageQueue的工作原理

    为了更好地理解Handler的工作原理,先介绍一下与Handler一起工作的几个组件。

  • Message:Handler接收和处理的消息对象。
  • Looper:每个线程只能拥有一个Looper。它的loop方法负责读取MessageQueue中的消息,读取到消息之后就把消息交给发送该消息的Handler进行处理。
  • MessageQueue:消息队列,它采用先进先出的方式来管理Message。程序创建Looper对象时会在它的构造器中创建MessageQueue对象。Looper提供的构造器源代码如下:   

     

    private Looper(boolean quitAllowed) {
                 mQueue = new MessageQueue(quitAllowed);
                 mRun = true;
                 mThread = Thread.currentThread();
             }

        该构造器使用了private修饰,表明程序员无法通过构造器创建Looper对象。从上面的代码不难看出,程序在初始化Looper时会创建一个与之关联的MessageQueue,这个MessageQueue就负责管理消息。

  • Handler:它的作用有两个——发送消息和处理消息,程序使用Handler发送消息,被Handler发送的消息必须被送到指定的MessageQueue。也就是说,如果希望Handler正常工作,必须在当前线程中有一个MessageQueue,否则消息就没有MessageQueue进行保存了。不过MessageQueue是由Looper负责管理的,也就是说,如果希望Handler正常工作,必须在当前线程中有一个Looper对象。为了保证当前线程中有Looper对象,可以分如下两种情况处理。
  1.  主UI线程中,系统已经初始化了一个Looper对象,因此程序直接创建Handler即可,然后就可通过Handler来发送消息、处理消息。
  2. 程序员自己启动的子线程,程序员必须自己创建一个Looper对象,并启动它 。创建Looper对象调用它的prepare()方法即可。

       prepare()方法保证每个线程最多只有一个Looper对象。prepare()方法的源代码如下:

     

  private static void prepare(boolean quitAllowed) {
         if (sThreadLocal.get() != null) {

             throw new RuntimeException("Only one Looper may be created per thread");

         }
         sThreadLocal.set(new Looper(quitAllowed));
     }

      然后调用Looper的静态loop()方法来启动它。loop()方法使用一个死循环不断取出MessageQueue中的消息,并将取出的消息分给该消息对应的Handler进行处理。下面是Looper类的loop()方法的源代码:

  

public static void loop() {
     final Looper me = myLooper();
     if (me == null) {
         throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
     }
     final MessageQueue queue = me.mQueue;

     // 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.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();
     }
    }

归纳起来,Looper、MessageQueue、Handler各自的作用如下。

  •  Looper:每个线程只有一个Looper,它负责管理MessageQueue,会不断地从MessageQueue中取出消息,并将消息分给对应的Handler处理。
  • MessageQueue:由Looper负责管理。它采用先进先出的方式来管理Message。
  • Handler:它能把消息发送给Looper管理的MessageQueue,并负责处理Looper分给它的消息。

    在线程中使用Handler的步骤如下。

  1. 调用Looper的prepare()方法为当前线程创建Looper对象,创建Looper对象时,它的构造器会创建于之配套的MessageQueue。
  2. 有了Looper之后,创建Handler子类的实例,重写handleMessage()方法,该方法负责处理来自于其他线程的消息。
  3. 调用Looper的loop()方法启动Looper。

    下面通过实例来介绍Looper与Handler的用法。

     实例:使用新线程计算质数        

    该实例允许用户输入一个数值上限,当用户单击“计算”按钮时,该应用会将上限数值发送到新启动的线程,该线程来计算该范围内的所有质数。

     之所以不直接在UI线程中计算该范围的所有质数,是因为UI线程需要相应用户动作,如果在UI线程中执行一个”耗时“操作,将会导致UI线程被阻塞,从而让应用程序失去响应。比如在该实例中,如果用户输入的数值太大,系统可能需要较长时间才能计算出所有质数,这就可能导致UI线程失去响应。

     为了将用户在UI界面输入的数值上限动态的传给新启动的线程,本实例将会在线程中创建一个Handler对象,然后UI线程的事件处理方法就可以通过该Handler向新线程发送消息了。

     该实例的界面布局文件如下:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <EditText
        android:id="@+id/etNum"
        android:inputType="number"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入上限"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="cal"
        android:text="计算"/>    
</LinearLayout>

该实例的Activity代码如下。

package com.example.studyevent;

import java.util.ArrayList;
import java.util.List;

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

public class CalPrime extends Activity {
     private final String UPPER_NUM="upper";
     EditText etNum;
     CalThread calThread;
     //定义一个线程类
     class CalThread extends Thread{
         public Handler mHandler;
         public void run()
         {
             Looper.prepare();
             mHandler=new Handler()
             {
                 //定义处理消息的方法
                @Override
                public void handleMessage(Message msg) {
                    // TODO Auto-generated method stub
                    if(msg.what==0x123)
                    {
                        int upper=msg.getData().getInt(UPPER_NUM);
                        List<Integer> nums=new ArrayList<Integer>();
                        //计算从2开始、到upper的所有质数
                        outer:
                            for(int i=2;i<=upper;i++)
                            {
                                //用i除以从2开始、到i的平方根的所有数
                                for(int j=2;j<=Math.sqrt(i);j++)
                                {
//如果可以整除,则表明这个数不是质数
                                    if(i!=2&&i%j==0)
                                    {
                                        continue outer;
                                    }
                                }
                                nums.add(i);
                            }
                        //使用Toast显示统计出来的所有质数
                        Toast.makeText(CalPrime.this,nums.toString(),Toast.LENGTH_LONG).show();
                    }
                }
                
             };
             Looper.loop();
         }
         
     }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_cal_prime);
        etNum=(EditText)findViewById(R.id.etNum);
        calThread=new CalThread();
        //启动新线程
        calThread.start();
    }
    //为按钮的点击事件提供事件处理函数
    public void cal(View source)
    {
        //创建消息
        Message msg=new Message();
        msg.what=0x123;
        Bundle bundle=new Bundle();
        bundle.putInt(UPPER_NUM, Integer.parseInt(etNum.getText().toString()));
        msg.setData(bundle);
        //向新线程中的Handler发送消息
        calThread.mHandler.sendMessage(msg);
        
    }
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.cal_prime, menu);
        return true;
    }

}

   上面的粗体字代码是实例的关键代码,这些粗体字代码在新线程内创建了一个Handler,由于在新线程中创建Handler时必须创建Looper,因此程序先调用Looper()的prpare()方法为当前线程创建了一个Looper实例,并创建配套的MessageQueue,新线程有了Looper对象之后,接下来程序创建了一个Handler对象,该Handler可以处理其他线程发送过来的消息。程序还调用了Looper的loop()方法。

    运行该程序,无论用户输入多大的数值,计算该范围的质数将会交给新线程完成,而前台UI线程不会受影响。该线程运行效果如图所示。

 

                    

posted @ 2013-11-05 21:27  TealerProg  Views(672)  Comments(0Edit  收藏  举报