2010年12月23日 星期四

【Android】使用Handler搭配Thread

當你希望使用Thread來改變UI View內容的時候就必須要搭配Handler來進行存取。

上圖是程式用的layout的解析圖,一開始是以一個ImageView的黑色圓圈提醒使用者那裡有ProgressBar,而真正的ProgressBar在初始化時則是隱藏的,再開始執行之後才經由Handler將它設定為可見,而TextView則會隨著程式的運行不斷的更新內容,提醒使用者進度。

layout/ ui.xml
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. android:orientation="vertical"
  5. android:layout_width="fill_parent"
  6. android:layout_height="fill_parent"
  7. >
  8. <ImageView
  9. android:id="@+id/ProgressDialogSample_ImageView_pImg"
  10. android:layout_width="wrap_content"
  11. android:layout_height="wrap_content"
  12. android:layout_centerInParent="true"
  13. android:background="@drawable/progress_circular_background"
  14. />
  15. <ProgressBar
  16. android:id="@+id/ProgressDialogSample_ProgressBar_pBar"
  17. android:layout_width="wrap_content"
  18. android:layout_height="wrap_content"
  19. android:layout_centerInParent="true"
  20. android:visibility="invisible"
  21. />
  22. <TextView
  23. android:id="@+id/ProgressDialogSample_TextView_desc"
  24. android:layout_width="wrap_content"
  25. android:layout_height="wrap_content"
  26. android:layout_centerHorizontal="true"
  27. android:layout_below="@id/ProgressDialogSample_ProgressBar_pBar"
  28. android:text="未執行任何步驟。"
  29. />
  30. </RelativeLayout>

主程式
  1. package iamshiao.sample;
  2.  
  3. import android.app.Activity;
  4. import android.os.Bundle;
  5. import android.os.Handler;
  6. import android.os.Message;
  7. import android.view.View;
  8. import android.widget.ImageView;
  9. import android.widget.ProgressBar;
  10. import android.widget.TextView;
  11.  
  12. public class ProgressDialogSample extends Activity {
  13. private final int step1 = 1, step2 = 2, step3 = 3, finish = 4;
  14. private Thread thread = null;
  15. @Override
  16. public void onCreate(Bundle savedInstanceState) {
  17. super.onCreate(savedInstanceState);
  18. setContentView(R.layout.ui);
  19. //建構執行緒
  20. thread = new Thread(){
  21. @Override
  22. public void run(){
  23. try{
  24. doStep1();
  25. doStep2();
  26. doStep3();
  27. Thread.sleep(3000);
  28. Message msg = new Message();
  29. msg.what = finish;
  30. uiMessageHandler.sendMessage(msg);
  31. }catch (Exception e){
  32. e.printStackTrace();
  33. }finally{
  34. }
  35. }
  36. };
  37. //開始執行執行緒
  38. thread.start();
  39. }
  40. private void doStep1() throws InterruptedException{
  41. Thread.sleep(3000);
  42. Message msg = new Message();
  43. msg.what = step1;
  44. uiMessageHandler.sendMessage(msg);
  45. }
  46. private void doStep2() throws InterruptedException{
  47. Thread.sleep(3000);
  48. Message msg = new Message();
  49. msg.what = step2;
  50. uiMessageHandler.sendMessage(msg);
  51. }
  52. private void doStep3() throws InterruptedException{
  53. Thread.sleep(3000);
  54. Message msg = new Message();
  55. msg.what = step3;
  56. uiMessageHandler.sendMessage(msg);
  57. }
  58. //宣告Handler並同時建構隱含類別實體
  59. Handler uiMessageHandler = new Handler(){
  60. @Override
  61. public void handleMessage(Message msg){
  62. //讀出ui.xml中的進度光棒
  63. final ProgressBar pBar =
  64. (ProgressBar)findViewById(
  65. R.id.ProgressDialogSample_ProgressBar_pBar);
  66. pBar.setVisibility(View.VISIBLE); //開始後設為可見
  67. //讀出ui.xml中用以表示未開始的圖案
  68. final ImageView pImg =
  69. (ImageView)findViewById(R.id.ProgressDialogSample_ImageView_pImg);
  70. pImg.setVisibility(View.INVISIBLE); //開始後設為不可見
  71. //讀出ui.xml中的描述用TextView
  72. TextView tv =
  73. (TextView)findViewById(R.id.ProgressDialogSample_TextView_desc);
  74. switch (msg.what){
  75. case step1:
  76. tv.setText(R.string.processing_step1);
  77. break;
  78. case step2:
  79. tv.setText(R.string.processing_step2);
  80. break;
  81. case step3:
  82. tv.setText(R.string.processing_step3);
  83. break;
  84. case finish:
  85. tv.setText(R.string.finish);
  86. pBar.setVisibility(View.INVISIBLE);
  87. pImg.setVisibility(View.VISIBLE);
  88. tv.setText("完成。");
  89. thread.interrupt();
  90. break;
  91. }
  92. super.handleMessage(msg);
  93. }
  94. };
  95. }

執行緒的部分與前篇大致相同,但由於其中不能直接執行
  1. tv.setText(R.string.processing_step1);
這類UI View存取的動作,所以加入了Handler來控制UI View,Handler的運作是利用handleMessage這個事件來監聽執行緒是否有送出Message,如果有的話利用Message的what屬性等機制來判別要進行什麼動作。 例如程式碼中我宣告了4個常數
  1. private final int step1 = 1, step2 = 2, step3 = 3, finish = 4;
  1. private void doStep1() throws InterruptedException{
  2. Thread.sleep(3000);
  3. Message msg = new Message();
  4. msg.what = step1;
  5. uiMessageHandler.sendMessage(msg);
  6. }
表示執行緒進行的步驟進度,而執行緒執行副程式doStep1~3的這個過程中,副程式在sleep完成後就會利用Handler變數的sendMessage來送出對應其步驟的Message變數。
  1. //宣告Handler並同時建構隱含類別實體
  2. Handler uiMessageHandler = new Handler(){
  3. @Override
  4. public void handleMessage(Message msg){
  5. ...
  6. switch (msg.what){
  7. case step1:
  8. tv.setText(R.string.processing_step1);
  9. break;
  10. ...
  11. }
  12. super.handleMessage(msg);
  13. }
  14. };
最後handleMessage事件再接聽到sendMessage之後就利用Message變數的what屬性之類的機制來判讀、執行對應動作。

重點提醒,使用Thread&Handler要特別注意...
  • handleMessage必須有@Override標註表示覆寫。
  • 每個Message變數必須重新new,只改動what內容仍會被視為同樣的Message,而造成非預期的錯誤。
    1. Thread.sleep(3000);
    2. Message msg = new Message();
    3. msg.what = step1;
    4. uiMessageHandler.sendMessage(msg);
    5. Thread.sleep(3000);
    6. msg.what = step2; //X 同Message實體只變更what就重複使用會造成非預期錯誤。
    7. uiMessageHandler.sendMessage(msg);
    8. Thread.sleep(3000);
    9. msg = new Message(); //O Message有重新建構實體才正確。
    10. msg.what = step3;
    11. uiMessageHandler.sendMessage(msg);
  • 任何UI View的控制應該在handleMessage中進行,而不是直接在Thread中進行。

補充
Message變數也可以透過setData方法來攜帶Bundle實體傳遞資料。
  1. Message msg = new Message();
  2. Bundle data = new Bundle();
  3. data.putString("data", "data");
  4. msg.setData(data);
  5. msg.what = step2;
  6. uiMessageHandler.sendMessage(msg);
  1. case step2:
  2. tv.setText(msg.getData().getString("data"));


完整專案下載

8 則留言:

  1. 您的範例非常受用,非常感謝您的講解:)

    回覆刪除
  2. 從中獲益匪淺,謝謝您

    回覆刪除
  3. 找為什麼使用progress dialog的時候程式會crash的原因時,看到這篇好文,一再修改後,終於可以在程式中使用progress dialog了!謝謝iam$hiao的詳細解說

    回覆刪除
  4. 讚~ 很清楚 終於有點小小的瞭解

    回覆刪除
  5. http://stackoverflow.com/questions/17372814/conversion-between-message-and-string-in-android
    這也很清楚

    回覆刪除
  6. 網誌管理員已經移除這則留言。

    回覆刪除
    回覆
    1. 網誌管理員已經移除這則留言。

      刪除