Android中的线程

  在Android平台中多线程应用很广泛,在UI更新、游戏开发和耗时处理(网络通信等)等方面都需要多线程。Android线程涉及的技术有:Handler;Message;MessageQueue;Looper;HandlerThread。

  Android线程应用中的问题与分析

  为了介绍这些概念,我们把计时器的案例移植到Android系统上,按照在Frame方式修改之后的代码清单8-4,完整代码请参考chapter8_3工程中 chapter8_3代码部分。

  【代码清单8-4】


public 
  class chapter8_3 extends Activity {

      
 private 
   
 String 
  TAG  
 = 
   
 " 
 chapter8_3 
 " 
 ;
      
 private 
  Button btnEnd;
      
 private 
  TextView labelTimer;
      
 private 
  Thread clockThread;
      
 private 
   
 boolean 
  isRunning  
 = 
   
 true 
 ;
      
 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);

          
 /* 
  线程体是Clock对象本身,线程名字为 
 " 
 Clock 
 " 
   
 */ 
 
         clockThread  
 = 
   
 new 
  Thread( 
 new 
  Runnable() {
             @Override
              
 public 
  void run() {
                  
 while 
  (isRunning) {
                     try {
                         Thread.currentThread().sleep( 
 1000 
 );
                          
 timer 
 ++ 
 ;
                         labelTimer.setText( 
 " 
 逝去了  
 " 
   
 + 
   
 timer 
   
 + 
   
 " 
  秒 
 " 
 );
                          
 Log 
 .d(TAG,  
 " 
 lost  time  
 " 
   
 + 
   
 timer 
 );
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
             }

         });

         clockThread.start();  
 /* 
  启动线程  
 */ 
 

     }
     } 
   程序打包运行结果出现了异常,如图8-8所示。
 

 ▲图8-8 运行结果异常图
   我们打开LogCat窗口,出错日志信息如图8-9所示。
 

 ▲图8-9 出错日志
   系统抛出的异常信息是“Only the original thread that created a view hierarchy can touch its views”,在Android中更新UI处理必须由创建它的线程更新,而不能在其他线程中更新。上面的错误原因就在于此。
   现在分析一下上面的案例,在上面的程序中有两个线程:一个主线程和一个子线程,它们的职责如图8-10所示。
   由于labelTimer是一个UI控件,它是在主线程中创建的,但是它却在子线程中被更新了,更新操作在clockThread线程的run()方法中实现,代码如下:
 

 ▲图8-10 线程职责 
/*   线程体是Clock对象本身,线程名字为  "  Clock 
 " 
   
 */ 
 
 clockThread   =     new 
  Thread( 
 new 
  Runnable() {
     @Override
       public   void run() {
           while   (isRunning) {
             try {
                 Thread.currentThread().sleep(  1000  );
                   timer  ++  ;
                 labelTimer.setText(  "  逝去了   " 
   
 + 
   
 timer 
   
 + 
   
 " 
  秒 
 " 
 );
                   Log  .d(TAG,   " 
 lost  time  
 " 
   
 + 
   
 timer 
 );
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
     }
 }); 
   这样的处理违背了Android多线程编程规则,系统会抛出异常“Only the original thread that created a view hierarchy can touch its views”。
   要解决这个问题,就要明确主线程和子线程的职责。主线程的职责是创建、显示和更新UI控件、处理UI事件、启动子线程、停止子线程;子线程的职责是计算逝去的时间和向主线程发出更新UI消息,而不是直接更新UI。它们的职责如图8-11所示。
 

 ▲图8-11 线程职责
   主线程的职责是显示UI控件、处理UI事件、启动子线程、停止子线程和更新UI,子线程的职责是计算逝去的时间和向主线程发出更新UI消息。但是新的问题又出现了:子线程和主线程如何发送消息、如何通信呢?
   在Android中,线程有两个对象—消息(Message)和消息队列(MessageQueue)可以实现线程间的通信。下面再看看修改之后的代码清单8-5,完整代码请参考chapter8_4工程中chapter8_4代码部分。
   【代码清单8-5】
 
public   class chapter8_4 extends Activity {

       private     String   TAG  
 = 
   
 " 
 chapter8_3 
 " 
 ;
       private   Button btnEnd;
       private   TextView labelTimer;
       private   Thread clockThread;
       private     boolean   isRunning  
 = 
   
 true 
 ;
       private   Handler handler;

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

         handler   =     new   Handler() {

             @Override
               public   void handleMessage(Message msg) {
                 switch (msg.what) {
                   case     0  :
                     labelTimer.setText(  "  逝去了   "    
 + 
  msg.obj  
 + 
   
 " 
  秒 
 " 
 );
                 }
             }

         };

         labelTimer   =   (TextView) findViewById(R.id.labelTimer);

           /*   线程体是Clock对象本身,线程名字为  "  Clock 
 " 
   
 */ 
 
         clockThread   =     new   Thread( 
 new 
  Runnable() {
             @Override

               public   void run() {
                   int     timer    
 = 
   
 0 
 ;
                   while   (isRunning) {
                     try {
                         Thread.currentThread().sleep(  1000  );
                           timer  ++  ;
                           /*   labelTimer.setText(  "  逝去了  
 " 
   
 + 
   
 timer 
   
 + 
   
 " 
  秒 
 " 
 );  
 */ 
 
                         Message msg   =     new   Message();
                         msg.obj   =     timer  ;
                         msg.what   =     0  ;
                         handler.sendMessage(msg);
                           Log  .d(TAG,   "  lost  time  
 " 
   
 + 
   
 timer 
 );
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
             }
         });

         clockThread.start();   /*   启动线程   */  

     } 
   有的时候为了将Android代码变得更加紧凑,把线程的创建和启动编写在一条语句中,如下面chapter8_5的代码片段。代码清单8-6所示,完整代码请参考chapter8_5工程中 chapter8_5代码部分。
   【代码清单8-6】
 
new   Thread() {
         @Override
           public   void run() {
               int     timer    
 = 
   
 0 
 ;
               while   (isRunning) {
                 ry {
                     Thread.currentThread().sleep(  1000  );
                       timer  ++  ;
                       /   labelTimer.setText(  "  逝去了  
 " 
   
 + 
   
 timer 
   
 + 
   
 " 
  秒 
 " 
 );
                     Message msg   =     new   Message();
                     msg.obj   =     timer  ;
                     msg.what   =     0  ;
                     handler.sendMessage(msg);
                       Log  .d(TAG,   "  lost  time  
 " 
   
 + 
   
 timer 
 );
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }

         }
     }.start();



  chapter8_5代码看起来有些糊涂吧?chapter8_4和chapter8_5创建线程的区别是:chapter8_4采用Thread(Runnable target)构造方法创建一个线程,需要提供一个Runnable接口对象,需要提供的参数是实现了Runnable接口的匿名内部类对象。chapter8_5采用Thread()构造方法创建一个线程,在这里采用了简便的编程方法,直接新建一个Thread类,同时重写run()方法。

  chapter8_5编程方法虽然晦涩难懂,而且违背了Java编程规范,程序结构也比较混乱,但却是Android习惯写法,这主要源于Android对于减少字节码的追求。究竟这两种方式在性能上有多少差别呢?诚实地讲我没有做过测试和求证,在我看来就上面的程序而言它们之间不会有太大差别,由于本书要尽可能遵守Java编程规范和Android的编程习惯,因此本书中两种编程方式都会采用,如果给大家带来不便敬请谅解。

  运行模拟器结果如图8-1所示,加载屏幕后马上开始计时,也可以单击“停止计时”按钮来停止计时。