Android多线程:Looper和HandlerThread

    更新UI的时候主线程必须是目标线程,如何掌握这个主动性?是通过Looper和HandlerThread实现的。Android中每一个线程都跟着一个Looper,Looper可以帮助线程维护一个消息队列,Looper对象的执行需要初始化Looper.prepare方法,使用Looper.loop方法启动消息队列管理机制,退出时还要使用Looper.release方法释放资源,下面代码为在Android中创建一个Thread类线程:

以下是代码片段:

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;
	}
}

    Android使用Looper机制才能接收消息和处理计划任务,上面的代码编写起来实在是麻烦,所以Android提供了一个线程类——HanderThread类,HanderThread类继承了Thread类,它封装了Looper对象,使我们不用关心Looper的开启和释放的细节问题。HandlerThread对象中可以通过getLooper方法获取一个Looper对象引用。

    不管是主线程(一般是我们的UI线程)还是子线程,只要有Looper的线程,别的线程就可以向这个线程的消息队列中发送消息和计划任务,然后做相应的处理。

    我们通过debug方式看看案例chapter8_5线程情况,在程序的39行和56行设置断点,39行是handleMessage方法用于接收消息的目标线程,56行是发出消息的线程。程序以debug模式运行,在断点56行挂起了,如图8-14所示。

Android多线程:Looper和HandlerThread

▲图8-14 debug模式运行图一

从图8-14可以看出,发送消息的程序隶属于Thread-8线程,它不是主线程。接着运行程序挂起在39行,如图8-15所示

Android多线程:Looper和HandlerThread

在图8-15中我们可以看到,接收消息的处理方法隶属于main线程,即主线程(UI线程),在8-15中还可以看到Looper对象。

比较发送消息和提交计划任务的不同之后,再以同样的方式debug一下程序chapter8_6代码,在程序chapter8_6的42行和50行设定断点,42行执行计划任务的目标线程,并以debug模式运行,如图8-16所示。

Android多线程:Looper和HandlerThread

▲图8-16 debug模式运行图三

如图8-16所示,程序挂起在50行,提交计划任务请求的程序隶属于main线程(UI线程),接着运行程序挂起在42行,如图8-17所示。

Android多线程:Looper和HandlerThread

▲图8-17 debug模式运行图四

图8-17所示程序挂起在42行,run方法是接收计划任务的程序,它也隶属于main线程(UI线程)。事实上chapter8_6程序只有一个UI主线程,它们在UI主线程中提交计划任务请求,再由UI线程作为目标线程接收计划任务执行。

但是在chapter8_5和chapter8_6的程序代码中并没有看到有关Looper和HandlerThread代码,这是因为默认情况下系统会创建一个无参构造方法的Handler对象,利用这种方法Handler可以自动与当前运行线程(UI线程)的Looper关联。

如果使用HandlerThread类替代Thread类创建线程对象,就可以直接使用HandlerThread线程中的Looper了,再使用这个Looper对象构造Handler对象,这样接收消息的目标线程就不是UI线程了。但就本例而言这种处理会有问题,我们先看看代码清单8-8,完整代码请参考chapter8_6工程中 chapter8_6_2代码部分。

【代码清单8-8】

public class chapter8_6_2 extends Activity {

	private String TAG = "chapter8_6_2";
	private Button btnEnd;
	private TextView labelTimer;
	private boolean isRunning = true;
	private Handler handler;
	private int timer = 0;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		btnEnd = (Button) findViewById(R.id.btnEnd);
		btnEnd.setOnClickListener(new OnClickListener {
			@Override
			public void onClick(View v) {
				isRunning = false;
			}
		});
		labelTimer = (TextView) findViewById(R.id.labelTimer);
		HandlerThread thread = new HandlerThread("MyHandlerThread");
		thread.start;
		handler = new Handler(thread.getLooper);
		Runnable r = new Runnable {
			public void run {
				if (isRunning) {
					labelTimer.setText("逝去了 " +timer + " 秒");
					timer++;
					handler.postDelayed(this, 1000);
				}
			}
		};
		handler.postDelayed(r, 1000);
	}
}
chapter8_6_2中用new HandlerThread("MyHandlerThread")创建HandlerThread线程,通过thread.start方法启动该线程。HandlerThread的getLooper方法可以获得与HandlerThread线程对象关联的Looper对象。再用Looper对象构建new Handler(looper)。

我们再通过debug看看chapter8_6_2的线程情况,在程序chapter8_6_2的46行和53行设定断点,并以debug模式运行,如图8-18所示。

如图8-18所示,程序挂起在53行,提交计划任务请求的程序隶属于main线程(UI线程),接着运行程序挂起在46行,如图8-19所示。

如图8-19所示,程序挂起在46行,run方法是接收计划任务的程序,它隶属于MyHandlerThread线程(非UI线程)。这样接收计划任务的线程体run就放到一个子线程中了,可见Looper是关键,Looper在哪个线程,哪个线程就可以接收消息和计划任务了。

Android多线程:Looper和HandlerThread

▲图8-18 debug模式运行图五


Android多线程:Looper和HandlerThread

▲图8-19 debug模式运行图六

事实上chapter8_6_2代码执行是有问题的,会出现错误“Only the original thread that created a view hierarchy can touch its views.”。这个错误应该不陌生,它是由于在非主线程(UI线程)中更新了UI,这是因为chapter8_6_2中把两个线程的职责掉换了,就这个案例本身而言这样修改没有意义,只是想通过这个例子让大家熟悉Looper和HandlerThread的使用。

小结

通过计时器案例向大家介绍了Java线程和Android中的线程。关键是Android线程,它与一般的Java多线程处理方式是不同的,其中重点是消息发送和计划任务,接受消息发送和计划任务的处理是目标线程,它是通过Looper机制维护消息队列,如果应用中有包含更新UI处理,则要把更新UI的处理代码放置在目标线程中,这个时候还要保障更新UI的线程是主线程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值