安卓开发之Handler、HandlerThread学习篇

安卓开发之Handler、HandlerThread学习心得篇
 
        开篇说明:本文采用的都是最基础最简单的例子,目的只有一个:希望大家将学习的焦点放在Handler的理解和使用上,我不想给出复杂的案例来分析,切以为容易让人“误入歧途”,忽略文章的主题。所以力求用最简单的例子说明使用原理的机制。理解和掌握了基本东西,复杂的东西只需在此基础上去扩展罢了。
  其次我是初学者,所以难免理解的比较肤浅,写出来跟大家分享分享,如有不正确的地方,望大家多多指正。
 
基本概念知悉:
1、消息(Message):消息其实就是一个载体,它存储着一些信息,比如消息的ID号、数据等,作为函数的参数在各个函数中进行传递,以便做出相应的处理。
2、消息队列(Message Queue):消息队列其实就是一个链表,它按照FIFO(先进先出的顺序)存放着消息。每个线程都对应着一个消息队列。
        废话不多说,直接上例子。个人认为掌握基本概念之后,从例子中学习是加深理解最为有效的方式,也是最愉悦的方式。
 
例一、
package com.example.handlerbrightfirst;
 
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.widget.Button;
 
public class HandlerBrightFirst extends Activity {
Button mybutton = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_bright_first);
mybutton = (Button)findViewById(R.id.mybutton);
                // 新建一个Handler对象
Handler mHandler = new Handler();
runTest test = new runTest();    
mHandler.post(test);
}
 
        // 新建一个类,实现于Runnable接口
class runTest implements Runnable{
 
@Override
public void run() {
// TODO Auto-generated method stub
mybutton.setText(R.string.mybutton);
}
 
}
}
 
案例说明要点:
        1. 切记误解此处run方法是在一个新线程中执行的。Runnable本身只不过是一个接口,post一个runnable对象也只不过是将runnable对象放到handler所绑定的线程(这里指的即是UI主线程)的消息队列中而已。
        2. 如果想使runnable对象的run方法是在新开启的线程中执行的话,需要用到Thread类的start方法,案例二会介绍。
        3. 假如此例中run方法中处理操作的时间过长,比如大于5秒的话,UI界面就会提示强迫关闭。因为都是在同一个线程中,所以run方法处理时间不能过长。
 
 
例二、
package com.example.handlerbrightsecond;
 
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.Menu;
 
public class HandlerBrightSecond extends Activity {
 
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_bright_second);
final Handler myHandler = new Handler();
// 运行于主线程的runnable对象
class UpdateUI implements Runnable{
 
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("222222222222222222");
}
 
}
 
final UpdateUI updateUI = new UpdateUI();
 
// 子线程启动运行的runnabel对象
class runner implements Runnable{
 
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("0000000000000000000000");
myHandler.post(updateUI);
System.out.println("111111111111111111111");
}
}
 
runner mywork = new runner();
Thread myThread = new Thread(mywork, "mywork");
// 启动一个子线程,运行mywork对象的run方法
myThread.start();
}
}
 
        上例中输出结果为:0000000000000000000000000
                                     1111111111111111111111111
                                     2222222222222222222222222
        此例主要用来跟案例一进行比较分析。不同点在于案例二中采用的是利用产生的新线程,执行新线程的run方法,然后再调用handler.post将一个runnable对象加入到UI主线程中的消息队列中,这样就可以从子线程中切换到主线程去执行了。
 
案例说明要点:
        1.post方法的主要作用是将一个runnable对象发送到调用它的Handler对象所在线程的消息队列中去,而不是创建一个新的线程。
        2.可以利用post方法实现线程的通讯,因此可以得知Handler的主要用处之一:便是将子线程中对UI线程的操作更新到UI主线程中,因为Android系统是不允许在子线程中更新UI的。
 
        当然,Handler的用法有很多,出了post之外,利用handler发送消息也是可以让子线程与UI主线程进行通信的。那下面就我们一起看看Handler是如何发送和接收消息,并处理消息的。
        例二中我们post的是一个runnable对象,然后在handler所在线程中执行runnable的run方法。其实handler也可以发送一个message,然后利用重写handler的message方法在handler所在线程中处理消息。举例如下:
 
例三、
 
package com.example.handlerbrightthird;
 
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.TextView;
 
public class HandlerBrightThird extends Activity {
private TextView myText = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_bright_third);
myText = (TextView)findViewById(R.id.mytext);
                // 新建一个handler对象,重写其中的handlerMessage方法,用来对消息进行处理。
final Handler myhandler = new Handler(){
 
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
if(1 == msg.what){
myText.setText(R.string.hello_world);
}
}
 
};
// 实现Runnable接口,运行于新创建的线程。在子线程中发送一个消息。
class MyRun implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
Message msg = new Message();
msg.what = 1;
myhandler.sendMessage(msg);
}
 
}
 
MyRun myRun = new MyRun();
Thread myThread = new Thread(myRun);
myThread.start();
}
 
}
 
案例说明要点:
        1.本例在新启动的线程中发送消息,然后在主线程中接收和处理消息。问题是:新启动的线程何时发送消息?主线程何时去获取消息并处理消息呢?为了让主线程能够“适时”地处理新启动线程发送的消息,显然只能通过回调的方式来实现------我们只需重写Handler类中处理消息的方法,当新启动线程发送消息时,Handler类中处理消息的方法就会被自动回调。
        2.分析例二和例三,会发现这两个案例非常的相似。确实如此,一个是post runnable对象到消息队列中,然后在主线程中执行run方法;一个是sendMessage 发送一个消息msg对象,然后在主线程中执行消息处理方法,比如handlemessage。所以说本质其实是:handler对象通过它的方法将另外一个对象加入到handler对应线程的消息队列中去,然后与该handler和消息队列有关的一个Looper负责将消息从队列中取出并执行。post方法、sendmessage等方法都能够达到这个目的:即将一个对象加入到handler所在线程的消息队列中。
 
        接着又有一个问题:上面三个示例都有个共同点,便是新建的handler对象都在UI主线程中创建的。思考一下:假如我们希望在子线程创建一个handler对象,然后同样希望在子线程中也能接收其他线程发送过来的信息,仍然采用上述的方法可以吗?答案是不可以。原因是:Handler能够发送和接收并处理消息的原理跟三个元素有关,一个是消息Message对象,一个是Looper,一个是handler所在线程的消息队列。但是在new Thread()方法创建的子线程中创建的handler是没有对应的Looper的,所以该子线程中创建handler是不会做任何的消息处理的。那么又该如何处理呢?如果需要在子线程中使用Handler类,首先需要创建Looper类实例,这时可以通过Looper.prepare()和Looper.loop()函数来实现的。Android为我们提供了一个HandlerThread类,该类继承Thread类,并使用上面两个函数创建Looper对象,而且使用wait/notifyAll解决了多线程中子线程1获取子线程2的Looper对象为空的问题。
       这样我们就可以直接使用HandlerThread类来创建和启动一个新的线程,而不使用Thread即可。这样创建的子线程中的Handler是由Looper对象实例的,所以该Handler也是可以处理消息的。
        
        注意:Android应用中的消息循环由Looper和Handler配合完成,Looper类用于封装消息循环,类中有个MessageQueue消息队列;Handler类封装了消息投递和消息处理等功能。
        系统默认情况下只有主线程(即UI线程)绑定Looper对象,因此在主线程中可以直接创建Handler的实例,但是在子线程中就不能直接new出Handler的实例了,因为子线程默认并没有Looper对象,此时会抛出RuntimeException异常:
 
        下面看看如何通过HandlerThread启动一个新线程,并在该子线程中创建handler对象和使用handler对象来处理消息。
 
HandlerThread类官方说明:
        Handy class for starting a new thread that has a looper. The looper can then be used to create handler classes. Note that start() must still be called.
        HandlerThread类是用来启动一个新的线程,并且它所启动的新线程包含有对应的Looper实例。Looper对象能用来创建handler对象。注意:HandlerThread的start方法必须要调用,否则线程无法开启运行。

             HandlerThread继承于Thread,所以它本质就是个Thread。与普通Thread的差别就在于,它有个Looper成员变量。这个Looper其实就是对消息队列以及队列处理逻辑的封装,简单说就是 消息队列+消息循环。

 
        
        下面这个例子给出的是一个比较综合的例子,基本上包括了前面三个例子的所有知识。
 
例四、 
 
package com.example.handlerthreadtest;
 
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.widget.TextView;
 
public class HandlerThreadTest extends Activity {
private TextView myText = null;
String textDisplay;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_thread_test);
// 根据ID获取控件
myText = (TextView)findViewById(R.id.mytext);
// 实现一个Runnable接口,实现其中的run方法,以便在子线程3中执行。
class Runner implements Runnable{
 
@Override
public void run() {
// TODO Auto-generated method stub
// 子线程3中执行 随便产生一个随机数吧
double randomData = Math.random();
System.out.println("The random number is = " + randomData);
System.out.println("线程3的ID号是 = " + Thread.currentThread().getId());
}
 
}
// 创建一个Runna类的runnable对象,供handle03发送。
Runner run03 = new Runner();
 
// 实现一个Runnable接口,实现其中的run方法,以便完成子线程2要更新UI的操作。
class UpdateUI implements Runnable{
 
@Override
public void run() {
// TODO Auto-generated method stub
// 更新显示文本
myText.setText(textDisplay);
System.out.println("UI线程的ID号是 = " + Thread.currentThread().getId());
}
 
}
// 为了完成线程2的功能,此处需要创建UI线程的Handler。因为系统默认创建的Handler便是UI的handler,所以无需根据UI线程的Looper获取。
final Handler handlerUI = new Handler();
 
// 创建一个Runna类的runnable对象,供handle03发送。
final UpdateUI updateUI = new UpdateUI();
 
// 重写Handler的handlemsg方法,以便在不同的线程当中处理各个消息。
class MyHandler extends Handler{
 
public MyHandler(Looper looper) {
// TODO Auto-generated constructor stub
// 使用父类的构造函数,此项必须要有
super(looper);
}
// 别看仅仅只有一个消息处理函数,其实系统会fork几个线程各自执行自己的handlerMessage方法。并行处理的
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
// 所有消息的处理都在此处进行,此处对消息进行分类处理
Bundle data = msg.getData();
// 如果此消息是由handler01发送的,将会在子线程1中执行下面if的代码。此线程中没有关于UI的操作
if(data.getInt("threadNum") == 1){
String str = data.getString("name");
System.out.println(str);
System.out.println("线程1的ID号是 = " + Thread.currentThread().getId());
}
// 如果此消息是由handler02发送的,将会在子线程2中执行下面else if的代码。为了区分子线程1,线程2中加入了UI的操作。
// Android系统中是禁止子线程执行更新UI的操作的。所以如果想要将子线程的执行数据结果用来更新UI,那么可以借助UI线程
// 的handlerUI,这样就可以调去UI线程执行更新。只需用UI线程的handler post或send一个消息过去即可。
else if(data.getInt("threadNum") == 2){
textDisplay = data.getString("display") + data.getString("text");
//倘若直接执行myText.setText(textDisplay);便会发生异常
handlerUI.post(updateUI);
System.out.println("线程2的ID号是 = " + Thread.currentThread().getId());
}
}
 
}
 
// 以下每个线程都是有HandlerThread创建,所以3个子线程都有其Looper实例。如果采用Thread类创建新线程则没有,这是两个线程类最根本的区别。
HandlerThread subThread01 = new HandlerThread("子线程1");
HandlerThread subThread02 = new HandlerThread("子线程2");
HandlerThread subThread03 = new HandlerThread("子线程3");
 
// 在使用getLooper方法之前必须要先执行start方法,这是官方文档的重要说明。
subThread01.start();
subThread02.start();
subThread03.start();
 
 
 
// 要想将消息对象或runnable对象分发到各个子线程,并让子线程执行。必须要获得发送消息的工具:Handler对象。
// 注意:每个特定的handler对象只负责向一个特定的线程发送消息,让那个特定的线程来执行和处理消息。比如:UI线程的handler,它的post runnable
// 或者 sendmessage方法都只会将消息发送到主线程,让主线程来从其消息队列中取出并执行。
// 因此,下面对各个特定线程分别创建它们的handler对象
MyHandler handler01 = new MyHandler(subThread01.getLooper());
MyHandler handler02 = new MyHandler(subThread02.getLooper());
MyHandler handler03 = new MyHandler(subThread03.getLooper());
 
// 接下来我们便可以利用各个线程对应的handler对象实例来向各个线程分发消息或runnable对象了。分发完成以后,各个线程将在CPU的调度下,并行运行,
//各自在自己的线程中去处理自己的消息。
// 因为handler既能发送消息,也能发送runnable对象,所以下面将给出不同。
 
// handler01发送一个消息到子线程1中去处理消息
Message msg01 = new Message();
// 创建bundle对象,封装消息的数据信息
Bundle data01 = new Bundle();
data01.putString("name", "您好!我是李茂!");
data01.putInt("threadNum", 1);
msg01.setData(data01);
// 异步机制:此处发送完消息以后,sendMessage函数会立马返回。消息被放入到子线程1的Looper中的消息队列中了,等待Looper取出msg01执行。
handler01.sendMessage(msg01);
 
// handler02发送一个消息到子线程2中去处理消息
Message msg02 = new Message();
Bundle data02 = new Bundle();
data02.putString("display", "这是Handler02");
data02.putString("text", "发送的更新UI的消息");
data02.putInt("threadNum", 2);
msg02.setData(data02);
handler02.sendMessage(msg02);
 
// handler03发送一个runnable对象,然后在子线程三种执行该runnable对象中的run方法。
handler03.post(run03);
}
}
 
输出结果如下:
        代码中注释已经写的非常清楚了,从输出结果中也能看出消息处理和线程之间的关系,以及如何使用。
 
 
 
 
 
最后总结要点:
    1. 一个线程对应一个Looper,一个Looper封装着消息循环和消息队列,一个线程可以对应多个handler。
    2. 系统默认情况下UI主线程有Looper,但是其他由Thread类创建的且没有实现looper实例的子线程均没有Looper;但是
        由Thread类的子类HandlerThread直接创建的线程都有其对应的Looper实例。
    3. 如果想把不同的消息分发到不同的线程中去处理,可以创建几个含有Looper的线程(一般用HandlerThread创建即可),    
        然后根据线程的Looper对象来获取对应的handler对象。最后由各个线程的handler发送消息到各自的线程中去处理消                                  息。
 
    简单画了一个图,帮助理解。希望这篇文章有所帮助。
    
 
 
 
 
 
 
 
 
 
 
posted @ 2013-03-19 20:27  第五元素~MJ  阅读(1917)  评论(3编辑  收藏