当有一段处理耗时比较漫长的时候,我们就需要用线程来处理。Android中是用Handler类来处理线程的。
与Handler绑定的有两个队列,一个为消息队列,另一个为线程队列。Handler可以通过这两个队列来分别:
Android OS中,一个进程(即一个App)被创建之后,主线程(可理解为当前Activity)创建一个消息队列,这个消息队列维护所有顶层应用对象(Activities, Broadcast receivers等)以及主线程创建的窗口。
你可以在主线程中创建新的线程,这些新的线程都通过Handler与主线程进行通信。通信通过新线程调用 Handler的post()方法和sendMessage()方法实现,分别对应功能:
消息的处理,在主线程的Handler对象中进行。具体处理过程,需要在new Handler对象时使用匿名内部类重写Handler的handleMessage(Message msg)方法。
线程加入线程队列可以在主线程中也可以在子线程中进行,但都要通过主线程的Handler对象调用post()。
我们用一个简单的例子(隔3000毫秒就打印一行“UpdateThread”的Log)来说明Handler的基本使用方法,一些说明就直接写在注释里头了。
main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <Button android:id="@+id/startButton" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="start" /> <Button android:id="@+id/endButton" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="end" /> </LinearLayout>
package com.tianjf; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class Handler_01Activity extends Activity { private Button startButton = null; private Button endButton = null; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); startButton = (Button) findViewById(R.id.startButton); endButton = (Button) findViewById(R.id.endButton); startButton.setOnClickListener(new StartButtonListener()); endButton.setOnClickListener(new EndButtonListener()); } class StartButtonListener implements OnClickListener { @Override public void onClick(View v) { // 调用Handler的post方法,将要执行的线程对象添加到队列当中 handler.post(updateThread); } } class EndButtonListener implements OnClickListener { @Override public void onClick(View v) { //调用Handler的removeCallbacks()方法,删除队列当中未执行的线程对象 handler.removeCallbacks(updateThread); } } // 创建一个Handler对象 Handler handler = new Handler(); // Java实现线程有两种方法:①继承Thread类 ②实现Runnable接口 /* * 以下是用匿名内部类的方式实现Runnable接口 * 接口 XXXX = new 接口() { * @Override * 实现接口的方法 * } * 这种new一个接口的情况在Android中还是很常见的(省去了先创建一个类继承接口,再new这个创建的类这一步骤) */ // 将要执行的操作写在线程对象的run方法当中 Runnable updateThread = new Runnable() { @Override public void run() { System.out.println("UpdateThread"); //在run方法内部,执行postDelayed或者是post方法 //postDelayed方法的作用是:将要执行的线程对象放入到队列当中,待时间结束后,运行制定的线程对象 //第一个参数是Runnable类型:将要执行的线程对象 //第二个参数是long类型:延迟的时间,以毫秒为单位 handler.postDelayed(updateThread, 3000); } }; }
本例子涉及到的知识点:
同样用一个简单的例子来说明怎么使用Handler来更新ProgressBar。
main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <ProgressBar android:id="@+id/bar" style="?android:attr/progressBarStyleHorizontal" android:layout_width="200dp" android:layout_height="wrap_content" android:visibility="gone" /> <Button android:id="@+id/startButton" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="start" /> </LinearLayout>
Handler_02Activity.java
package com.tianjf; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ProgressBar; public class Handler_02Activity extends Activity { ProgressBar progressBar = null; Button startButton = null; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); progressBar = (ProgressBar) findViewById(R.id.bar); startButton = (Button) findViewById(R.id.startButton); startButton.setOnClickListener(new ButtonListener()); } class ButtonListener implements OnClickListener { @Override public void onClick(View v) { progressBar.setVisibility(View.VISIBLE); handler.post(updateThread); } } // 使用匿名内部类来复写Handler当中的handleMessage方法 Handler handler = new Handler() { // handleMessage方法会从消息队列中依次取消息,取出来之后做一系列处理 @Override public void handleMessage(Message msg) { progressBar.setProgress(msg.arg1); handler.post(updateThread); } }; // 线程类,该类使用匿名内部类的方式进行声明 Runnable updateThread = new Runnable() { int i = 0; @Override public void run() { System.out.println("Begin Thread"); i = i + 10; // 得到一个消息对象,Message类是有Android操作系统提供 Message msg = handler.obtainMessage(); // 将msg对象的arg1参数的值设置为i,用arg1和arg2这两个成员变量传递消息,优点是系统性能消耗较少 // 还可以对msg对象的obj参数设值,例如msg.obj = "abc" // 另外还可以用msg对象的setData()方法设值,这个要涉及到Bundle类,请看例子Handler_03 msg.arg1 = i; try { // 设置当前显示睡眠1秒 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 将msg对象加入到消息队列当中,消息能够被handleMessage(Message msg)方法接收到 handler.sendMessage(msg); if (i == 100) { // 如果当i的值为100时,就将线程对象从handler当中移除 handler.removeCallbacks(updateThread); } } }; }
本例子涉及到的知识点:
你可能会说:不可能吧。但是我要说:看下面的例子,我们来见证奇迹。
import android.app.Activity; import android.os.Bundle; import android.os.Handler; public class HandlerTest extends Activity { private Handler handler = new Handler(); public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); handler.post(runnable); setContentView(R.layout.main); System.out.println("activity--->" + Thread.currentThread().getId()); System.out.println("activityname--->" + Thread.currentThread().getName()); } Runnable runnable = new Runnable(){ @Override public void run() { System.out.println("handler--->" + Thread.currentThread().getId()); System.out.println("handlername--->" + Thread.currentThread().getName()); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } } }; }我们将setContentView(R.layout.main);放在handler.post(runnable);之后是为什么呢?
我们知道setContentView(R.layout.main);执行完了之后,画面的控件才会显示出来,而我们又在Runnable里面设置了Thread.sleep(10000);让程序睡10秒,如果创建出来的线程是在主线程运行的话,那么程序启动后会过10秒才看到画面,如果是两个线程的话,那么画面会立即显示。
除了通过UI来确认,我们还可以通过程序中的Log来确认。
下面是见证奇迹的时刻:
Log很明确的显示,都是在主线程执行的。如果你还是不相信,那么请看看UI,看是不是过了10秒画面才显示出来。
因为handler.post(runnable);是直接执行runnable的run()方法,没有另外起一个线程,所以还是在主线程执行。
我们把handler.post(runnable);注释掉,换成Java的标准启动子线程的方法
Thread thread = new Thread(runnable); thread.start();运行之后会发现
已经另起了一个线程了,而且画面也是立即显示。
上面的程序仅仅是另外启动了一个子线程,但是还没有讲到怎么传递消息。
如果要另起一线程并想传递消息并处理消息,那么就要用到Looper类,这个是用来封装消息循环和消息队列的一个类,也就是可以循环从消息队列中取消息。Looper会一直从消息队列中取消息并处理消息,如果没有消息了,线程就处于休眠状态。
在Android中,我们很少创建一个Looper对象,因为Android框架中的HandlerThread类本身就具备了循环处理消息的功能,我们只要调用HandlerThread类的对象的getLooper方法就可以了。
具体看下面代码
import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; public class HandlerTest2 extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); System.out.println("Activity-->" + Thread.currentThread().getId()); HandlerThread handlerThread = new HandlerThread("handler_thread"); handlerThread.start(); MyHandler myHandler = new MyHandler(handlerThread.getLooper()); Message msg = myHandler.obtainMessage(); Bundle b = new Bundle(); b.putInt("age", 20); b.putString("name", "Jhon"); msg.setData(b); msg.sendToTarget(); } class MyHandler extends Handler{ public MyHandler(){ } public MyHandler(Looper looper){ super(looper); } @Override public void handleMessage(Message msg) { Bundle b = msg.getData(); int age = b.getInt("age"); String name = b.getString("name"); System.out.println("age is " + age + ", name is" + name); System.out.println("Handler--->" + Thread.currentThread().getId()); System.out.println("handlerMessage"); } } }
需要了解的知识点如下
Looper类
Looper是封装消息循环和消息队列的一个类,线程默认是没有消息循环的,Looper用于在线程中建立一个消息循环。
也就是说Looper可以循环从消息队列中取消息。Looper会一直从消息队列中取消息并处理消息,如果没有消息了,线程就处于休眠状态。
在Android中,我们很少创建一个Looper对象,因为Android框架中的HandlerThread类本身就具备了循环处理消息的功能。我们只要调用HandlerThread类的对象的getLooper方法就可以了。需要注意的是,只有在handlerThread.start();之后才能调用getLooper方法,否在取出来的Looper对象为null。
public MyHandler(Looper looper)构造方法
这个构造函数目的就是接收一个Looper对象作为参数,那么这个Looper对象就绑定到了这个myHandler上面。我们在程序里面有这么一句话MyHandler myHandler = new MyHandler(handlerThread.getLooper());,传进去的looper是handlerThread里面的looper,既然这个looper绑定到了myHandler上面,那么也就相当于myHandler绑定到了handlerThread这个线程上面,那么也就相当于myHandler的一切操作就在handlerThread这个子线程上执行,也就实现了多线程。
前面我们介绍了两种传递消息的方法
- msg.arg1 = i;
- msg.obj = "abc";
第一种是传递int类型的方法,用arg1和arg2这两个成员变量传递消息,优点是系统性能消耗较少
第二种是传递object类型的方法
此例中用了第三种方法
Bundle b = new Bundle(); b.putInt("age", 20); b.putString("name", "Jhon"); msg.setData(b); myHandler.sendMessage(msg); msg.sendToTarget();此方法用于传递大量的数据
msg.setData(b);方法传递的是Bundle对象。其实Bundle就是一个数据存储的工具,我们可以将数据存储到Bundle里面然后传递到另外一个地方。
Bundle和HashMap类似,不同点如下
- Bundle的key都是String类型的
- Bundel的value是基本类型以及基本类型的数组
好了,over。