为了更好地理解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对象,可以分如下两种情况处理。
- 主UI线程中,系统已经初始化了一个Looper对象,因此程序直接创建Handler即可,然后就可通过Handler来发送消息、处理消息。
- 程序员自己启动的子线程,程序员必须自己创建一个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的步骤如下。
- 调用Looper的prepare()方法为当前线程创建Looper对象,创建Looper对象时,它的构造器会创建于之配套的MessageQueue。
- 有了Looper之后,创建Handler子类的实例,重写handleMessage()方法,该方法负责处理来自于其他线程的消息。
- 调用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线程不会受影响。该线程运行效果如图所示。