Android開發中的多線程詳解

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

Android線程應用中的問題與分析
▲圖8-8 運行結果異常圖

  我們打開LogCat窗口,出錯日誌信息如圖8-9所示。

Android線程應用中的問題與分析
▲圖8-9 出錯日誌

  系統拋出的異常信息是“Only the original thread that created a view hierarchy can touch its views”,在Android中更新UI處理必須由創建它的線程更新,而不能在其他線程中更新。上面的錯誤原因就在於此。

  現在分析一下上面的案例,在上面的程序中有兩個線程:一個主線程和一個子線程,它們的職責如圖8-10所示。

  由於labelTimer是一個UI控件,它是在主線程中創建的,但是它卻在子線程中被更新了,更新操作在clockThread線程的run()方法中實現,代碼如下:

Android線程應用中的問題與分析
▲圖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所示。

Android線程應用中的問題與分析
▲圖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所示,加載屏幕後馬上開始計時,也可以單擊“停止計時”按鈕來停止計時。

原文:http://tech.it168.com/a2011/0922/1250/000001250289_all.shtml

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章