2014年11月17日 星期一

[Android Sample Code] 將 Non-UI Thread 的資料傳遞給 UI Thread - 使用runOnUiThread

在Android系統裡,Activity Manager 和 Window Manager system services會負責監控程式的響應(response)時間,如果由使用者觸發的事件 (例如按下按鈕)超過太久沒有響應的話 (Android限制使用者觸發的事件得在5秒內完成;BroadcastReceiver則是限制在10秒內得完成工作),Android會觸發Application Not Responding (ANR)對話框詢問是否要終止程式:

Fig. 1 ANR Dialog

為了避免上述的狀況,過於花時的運算和事件等待應避免在UI 執行緒(Activity裡的Main Thread)執行,換句話說可以透過建立額外的Non UI 執行緒來處理,不過Android不允許Non UI 執行緒 (即UI 執行緒下的子執行緒)直接更新UI 執行緒的畫面,所以Non UI 執行緒必須得將自己的資料回傳給UI 執行緒由它負責處理,在這篇文章介紹一個簡單的方法,使用 - runOnUiThread。 runOnUiThread(Runnable action)是Activity Class裡的一個方法 (Method),當Non UI 執行緒呼叫此方法時,會將引數action回傳到UI 執行緒的Event Queue裡等待被執行,所以可以根據實際的設計需求實作特定的action,將Non UI 執行緒的資料傳遞給UI 執行緒也得以實踐。下面的範例程式碼實作了一個忽略時間誤差的簡單時鐘程式,此範例程式透過Non UI 執行緒取得系統時間後傳給UI 執行緒更新畫面。此範例的UI介面有兩個TextView,第一個TextView顯示"A Message form the Non-UI Threads:",用來提示此APP的功能,第二個msgTextView用來顯示Non UI執行緒傳給UI執行緒的資料。關於程式碼的部份,MainActivity class (APP執行時的UI執行緒)裡宣告了一個繼承Thread class的inner class - NonUIThread (APP執行時UI執行緒下的Non UI執行緒),第64和65行是它的成員物件,nonUIThreadName是Non UI執行緒的名字,用來識別是哪一個執行緒,第67到71行的default constructor設定"Unknown Non-UI Thread"為預設的名字,或是透過呼叫第73到76行的constructor賦與不同的名字,另一個成員calendar則是用來獲取裝置所設定的系統年分、日期和時間的日曆物件,第79到90行覆寫了Thread class的run方法,run方法會不斷的去呼叫sayHelloToUIThread方法,第83行的sleep方法設定了讓執行緒下次再繼續執行的時間(即1秒),第91到103行是sayHelloToUIThread方法的實做部分,此方法每隔一秒會被呼叫,並透過Calendar.getInstance()擷取出目前的時間,第93到96行將這些資訊轉換成字串(所有的JAVA calss均會繼承Object class,Object class裡有一個toString的方法,所以calendar.get()回傳的時間會靠此方法轉成字串),第97到102行就是本範例要講的重點,inner class - NonUIThread呼叫了MainActivity的runOnUiThread,傳入引數的Runnable物件一樣覆寫了run方法,run方法會呼叫MainActivity裡的receiveMsgFromNonUIThread方法並,傳入的引數toUIStr即為Non UI執行緒想要傳給UI執行緒的資料 ,第59到61行的receiveMsgFromNonUIThread方法實做則是再呼叫msgTextView的setText方法將目前的時間更新到UI執行緒上。MainActivity的onCreate方法建立了UI畫面和一個Non UI執行緒的物件,在onStart方法裡呼叫nonUIThread1.start()讓Non UI執行緒開始執行獲取系統時間並傳回給UI,Fig.2顯示了此範例APP的執行結果。


package com.example.threadcommunicationex1;

import java.util.Calendar;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;


public class MainActivity extends ActionBarActivity {
 private TextView msgTextView;
 private NonUIThread nonUIThread1;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getViewComponent();
        CreateThread(); 
    }
    
    @Override
    protected void onStart(){
     super.onStart();
     nonUIThread1.start();
    }
    
    private void getViewComponent()
    {
     msgTextView = (TextView) findViewById(R.id.msg);
     msgTextView.setText("");
    }
    
    private void CreateThread(){
     nonUIThread1 = new NonUIThread("Non-UI Thread 1");
    }
    
    private void receiveMsgFromNonUIThread(String msg){
     msgTextView.setText(msg);
    }
    
    class NonUIThread extends Thread{
     private String nonUIThreadName;
     private Calendar calendar;
     
     NonUIThread(){
      nonUIThreadName = "Unknown Non-UI Thread";
      //get a default instance of this class for general use
      calendar = Calendar.getInstance(); 
     }
     
     NonUIThread(String name){
      this();
      nonUIThreadName = name;  
     }
     
     @Override
     public void run(){
       while(true){
        sayHelloToUIThread();
        try {
          sleep(1000);
        } 
        catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }
     
     private void sayHelloToUIThread(){
       calendar = Calendar.getInstance();
       final String  toUIStr = "This is " + nonUIThreadName + 
        ", " + "current time is " + calendar.get(Calendar.HOUR_OF_DAY) 
        + ":" + calendar.get(Calendar.MINUTE) + ":" + 
        calendar.get(Calendar.SECOND) + "\n\n";
       runOnUiThread(new Runnable(){
         @Override
         public void run(){
          receiveMsgFromNonUIThread(toUIStr);
         }
       });
      }
    }   
}


程式執行的畫面如下:
Fig. 2. 程式執行結果

對JAVA執行緒不熟的話可以參考此網站的講解:


沒有留言:

張貼留言