Android中的線程
在Android平臺中多線程應用很廣泛,在UI更新、遊戲開發和耗時處理(網絡通信等)等方面都需要多線程。Android線程涉及的技術有:Handler;Message;MessageQueue;Looper;HandlerThread。
Android線程應用中的問題與分析
爲了介紹這些概念,我們把計時器的案例移植到Android系統上,按照在Frame方式修改之後的代碼清單8-4,完整代碼請參考chapter8_3工程中 chapter8_3代碼部分。
【代碼清單8-4】
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 線程職責
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】
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】
@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