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所示。

Looper和HandlerThread
▲图8-14 debug模式运行图一

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

Looper和HandlerThread

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

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

Looper和HandlerThread
▲图8-16 debug模式运行图三

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

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在哪个线程,哪个线程就可以接收消息和计划任务了。

Looper和HandlerThread
▲图8-18 debug模式运行图五

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、付费专栏及课程。

余额充值