2007年11月20日 星期二

Android官方部落格翻譯:防微杜漸(A Stitch in Time)

背景:當我在開發第一個Android App的時候(雖然很小),那是一個串接Podcast播放的應用程式,我需要一個輕而易舉的方法,能夠讓我在使用者介面上,實作即時更新的時鐘。

  問題:在一開始,我使用了java.util這個函式庫來更新時鐘,但是這個類別在Android上不是個好選擇。在這邊使用時鐘的例子來介紹,在應用程式開一個新線程。考慮到移動裝置應用程式有別於視窗應用程式,我們必須要找到更有效率的方法,來更新時鐘。

  應用程式:關於應用程式移植這部份的故事,留待日後的部落格會再開文詳述。如果你對開發應用程式有相當的疑問和興趣,你可以在開發者網站閱讀之前的文章,裡面有關於Matisse(Java上用於開發GUI-圖形使用者介面的類別)的介紹。原本的應用程式是使用Java Swing的SE應用程式。在錄製Podcast的時候,扮演了迴圈計時碼表的角色。當你開始錄音時,碼表同時被啟動,當你按下FLUB按鈕,會紀錄下那時候的音軌。最後你可以儲存它,並且使用Audacity來編輯它。

參考文件:http://www.developer.com/java/ent/print.php/3589961

一開始的版本中,計時器的程式碼如下:
class UpdateTimeTask extends TimerTask {
   public void run() {
       long millis = System.currentTimeMillis() - startTime;
       int seconds = (int) (millis / 1000);
       int minutes = seconds / 60;
       seconds     = seconds % 60;

       timeLabel.setText(String.format("%d:%02d", minutes, seconds));
   }
}
並在場景上的監聽器來啟動Timer()的更新,詳見以下實例:
if(startTime == 0L) {
   startTime = evt.getWhen();
   timer = new Timer();
   timer.schedule(new UpdateTimeTask(), 100, 200);
}
要特別注意的是,第一個參數100,表示第一次時鐘執行的時候,是在100毫秒之後,第二個參數200,表示之後每隔200毫秒更新一次,直到程式終止。200毫秒感覺並不明顯,但是如果不這樣做的話,就會發生延遲了2秒,畫面卻還沒即時更新的奇怪現象。

  當我在使用Android SDK要移植應用程式的時候,發生了程式碼無法在Eclipse上編譯的窘況,原來Timer()這個類別無法被執行(幸好在錯誤訊息中找到原因),除此之外,String.format這個方法也無法使用,所以我必須盡快找到解決的辦法。

  最後,計時器的功能,可以透過android.os.Handler這個類別,來設置監聽器。
private Handler mHandler = new Handler();
...
OnClickListener mStartListener = new OnClickListener() {
   public void onClick(View v) {
       if (mStartTime == 0L) {
            mStartTime = System.currentTimeMillis();
            mHandler.removeCallbacks(mUpdateTimeTask);
            mHandler.postDelayed(mUpdateTimeTask, 100);
       }
   }
};
首先,我們無法調用getWhen()這個方法,來對計時器來做設定。於是我們使用了System.currentTimeMills()這個方法,同時設定為Handler.postDelayed()的參數,但是無法重覆取得當下的時間係數。在這種情況下,我們宣告Handler在100毫秒之後,呼叫mUpdateTimeTask()這個方法,雖然解決了宣告計時器的問題,但是卻沒有解決重覆取得時間係數的問題。

  我們希望程式能夠重複取得時間係數,直到我們告訴它停下來。為了做到這一點,必須把另一個宣告延遲的函式,放置到mUpdateTimeTask run()這個方法後面。要注意的是,Runnable是Handler實作的方法,所以我們改了一下mUpdateTimeTask去實作,新的時鐘更新方法如下:
private Runnable mUpdateTimeTask = new Runnable() {
   public void run() {
       final long start = mStartTime;
       long millis = SystemClock.uptimeMillis() - start;
       int seconds = (int) (millis / 1000);
       int minutes = seconds / 60;
       seconds     = seconds % 60;

       if (seconds < 10) {
           mTimeLabel.setText("" + minutes + ":0" + seconds);
       } else {
           mTimeLabel.setText("" + minutes + ":" + seconds);            
       }
     
       mHandler.postAtTime(this,
               start + (((minutes * 60) + seconds + 1) * 1000));
   }
};
並且可以被定義為類別的成員變數。

  if判斷式,是為了讓時間係數的分,在小於10的時候,會以10:06呈現,而不是10:6(希望外來String.format()這個方法能夠使用),在計時器取得時間係數的時候,會調用Handler本身,以每200毫秒來啟動下次的動作: (((minutes * 60) + seconds + 1) * 1000)。

  現在我們需要一個按鈕,來讓計時器能夠停止。並且宣告按鈕的監聽器如下:
OnClickListener mStopListener = new OnClickListener() {
   public void onClick(View v) {
       mHandler.removeCallbacks(mUpdateTimeTask);
   }
};
為了確保按鈕不會發生二次點擊,當按鈕按下的時候,將會移除監聽。

  Handler是一個比Timer更好用的類別。Handler能夠妥善處理程式碼在主線程執行,避免額外再新開一個線程。要記得維持線程的順暢,避免搞壞使用者體驗。

  以上就是一系列Android之旅的第一個開發提示。希望這能夠在你開發的路上,少走些冤枉路,節省一些時間,可以去開發你想要的程式(即使只是一個應用程式的小更新)。這一篇文章涵蓋了我許多移植應用程式的經驗,也許未來會增加更多的小提示,除此之外,還有更多的討論正在Android Developers Discussion Group進行著。

發文者 
原文出處 A Stitch in Time

沒有留言:

張貼留言