Android开发中的多线程编程技术
Android開發中的多線程編程技術【IT168技術】多線程這個令人生畏的“洪水猛獸”,很多人談起多線程都心存畏懼。在Android開發過程中,多線程真的很難嗎?多線程程序的“麻煩”源于它很抽象、與單線程程序運行模式不同,但只要掌握了它們的區別,編寫多線程程序就會很容易了。下面讓我們集中精力開始學習吧! 多線程案例——計時器 我在給我的學生講多線程的時候都會舉一個計時器的案例,因為計時器案例是多線程的經典應用。 這個案例中,屏幕啟動之后,進入如圖8-1所示的界面。 屏幕上有一個文本框用于顯示逝去的時間,此外還有一個“停止計時”按鈕。案例的用例圖如圖8-2所示。
▲圖8-1 計時器界面
▲圖8-2 計時器用例圖 能夠在屏幕上“實時地顯示”時間的流逝,單線程程序是無法實現的,必須要多線程程序才可以實現,即便有些計算機語言可以通過封裝好的類實現這一功能,但從本質上講這些封裝好的類就是封裝了一個線程。 綜上所述,完成本案例用到的知識及技術如下: 1)進程和線程的概念; 2)Java中的線程,在Java中創建線程的方式; 3)Android中的線程,包括:Message、Handler、Looper和HandlerThread等概念。 線程究竟是什么?在Windows操作系統出現之前,個人計算機上的操作系統都是單任務系統,只有在大型計算機上才具有多任務和分時設計。Windows、Linux操作系統的出現,把原本只在大型計算機才具有的優點,帶到了個人計算機系統中。 進程概念 一般可以在同一時間內執行多個程序的操作系統都有進程的概念。一個進程就是一個執行中的程序,而每一個進程都有自己獨立的一塊內存空間、一組系統資源。在進程的概念中,每一個進程的內部數據和狀態都是完全獨立的。在Windows操作系統下我們可以通過〈Ctrl+Alt+Del〉組合鍵查看進程,在UNIX和Linux操作系統下是通過PS命令查看進程的。打開Windows當前運行的進程,如圖8-3所示。
▲圖8-3 Windows操作系統進程 在Windows操作系統中一個進程就是一個exe或dll程序,它們相互獨立,互相也可以通信,在Android操作系統中進程間的通信應用也是很多的。 線程概念 多線程指的是在單個程序中可以同時運行多個不同的線程,執行不同的任務。多線程意味著一個程序的多行語句可以看上去幾乎在同一時間內同時運行。 線程與進程相似,是一段完成某個特定功能的代碼,是程序中單個順序的流控制。但與進程不同的是,同類的多個線程共享一塊內存空間和一組系統資源,所以系統在各個線程之間切換時,資源占用要比進程小得多,正因如此,線程也被稱為輕量級進程。一個進程中可以包含多個線程。圖8-4所示是計時器程序進程和線程之間的關系,主線程負責管理子線程,即子線程的啟動、掛起、停止等操作。
▲圖8-4 進程和線程關系 Java中的線程 Java的線程類是java.lang.Thread類。當生成一個Thread類的對象之后,一個新的線程就產生了。Java中每個線程都是通過某個特定Thread對象的方法run()來完成其操作的,方法run( )稱為線程體。 下面是構建線程類幾種常用的方法: public Thread() public Thread(Runnable target) public Thread(Runnable target, String name) public Thread(String name) 參數target是一個實現Runnable接口的實例,它的作用是實現線程體的run()方法。目標target可為null,表示由本身實例來執行線程。name參數指定線程名字,但沒有指定的構造方法,線程的名字是JVM分配的,例如JVM指定為thread-1、thread-2等名字。 1、Java中的實現線程體方式1 在Java中有兩種方法實現線程體:一是繼承線程類Thread,二是實現接口Runnable。下面我們先看看繼承線程類Thread方式。 如果采用第1種方式,它繼承線程類Thread并重寫其中的方法 run(),在初始化這個類實例的時候,目標target可為null,表示由本實例來執行線程體。由于Java只支持單重繼承,用這種方法定義的類不能再繼承其他父類,例如代碼清單8-1,完整代碼請參考chapter8_1工程中chapter8_1代碼部分。 【代碼清單8-1】 public?class chapter8_1 extends Thread {????boolean?isRunning?=?true; ????int?timer?=?0; ????/** ?????*?線程體代碼 ?????*/ ????@Override ????public?void run() { ????????while?(isRunning) { ????????????try { ????????????????Thread.currentThread().sleep(1000); ????????????????timer++; ????????????????System.out.println("逝去了?"+timer+"?秒"); ????????????} catch (InterruptedException e) { ????????????????e.printStackTrace(); ????????????} ????????} ????} ???? ????public?static void main(String[] args) { ????????chapter8_1 t1?=?new?chapter8_1(); ????????t1.start(); ????????System.out.println("計時器啟動..."); ????????BufferedReader br?=?new?BufferedReader(new?InputStreamReader(System.in)); ????????try { ????????????String?line?=?br.readLine(); ????????????if?(line.equalsIgnoreCase("1")) { ????????????????t1.isRunning?=?false; ????????????????/*t1.stop();*/ ????????????} ????????} catch (IOException e) { ????????????e.printStackTrace(); ????????} ????} ????} 在main主方法中通過new chapter8_1()創建子線程,并通過t1.start()方法啟動子線程,main主方法所在線程為主線程,主線程負責管理其他的子線程。本例進程、主線程和子線程之間的關系如圖8-5所示。 子線程啟動之后就開始調用run()方法,run()是一個線程體,我們在子線程中處理事情就是在這里編寫代碼實現的。本案例中子線程要做的事情就是:休眠1s,計時器加1,再反復執行。Thread.currentThread().sleep(1000)就是休眠1s。 為了能夠停止線程,我們在主線程中增加了一個標識,通過在控制臺輸入一個字符 “1”來改變該標識t1.isRunning = false,從而結束這個線程。 注意: 事實上線程中有一個stop()方法也可以停止線程,但是由于這種方法會產生線程死鎖問題,所以在新版JDK中已經廢止了,它的替代解決方法就是增加標識,就是我們在本例中采用的方案。 很多人覺得線程難理解,主要有兩個問題: 線程休眠,既然線程已經休眠了,程序的運行速度還能提高嗎? 線程體一般都進行死循環,既然線程死循環,程序就應該死掉了,就會沒有反應。 1.關于線程休眠問題 對線程休眠問題頭痛的讀者,其實還是在用單線程的思維模式考慮問題,多數情況下我們的PC都是單CPU的,某個時間點只能有一個線程運行。所謂多線程就是多個線程交替執行就好像同時運行似的。因此,休眠當前線程可以交出CPU控制權,讓其他的線程有機會運行,多個線程之間只有交替運行效率才是最高的,這就像我們開車過十字路口,只有我等等,讓你先過,你再等等讓他先過,才能保證最高效率,否則就會造成交通系統崩潰,對線程情況也是一樣的。因此,多線程中線程的休眠是程序運行的最有效方式。 2.關于線程體死循環問題 在單線程中如果是死循環,程序應就會死掉,沒有反應,但是多線程中線程體(run方法)中的死循環,可以保證線程一直運行,如果不循環線程,則運行一次就停止了。在上面的例子中線程體運行死循環,可以保證線程一直運行,每次運行都休眠1s,然后喚醒,再然后把時間信息輸出到控制臺。所以,線程體死循環是保證子線程一直運行的前提。由于是子線程它不會堵塞主線程,就不會感覺到程序死掉了。但是需要注意的是有時我們確實執行一次線程體,就不需要循環了。 程序運行后開始啟動線程,線程啟動后就計算逝去的時間,每過1s將結果輸出到控制臺。當輸入1字符后線程停止,程序終止。如圖8-6所示。 Java中的實現線程體方式2 上面介紹繼承Thread方式實現線程體,下面介紹另一種方式,這種方式是提供一個實現接口Runnable的類作為一個線程的目標對象,構造線程時有兩個帶有Runnable target參數的構造方法: Thread(Runnable target); Thread(Runnable target, String name)。 其中的target就是線程目標對象了,它是一個實現Runnable的類,在構造Thread類時候把目標對象(實現Runnable的類)傳遞給這個線程實例,由該目標對象(實現Runnable的類)提供線程體run()方法。這時候實現接口Runnable的類仍然可以繼承其他父類。 請參看代碼清單8-2,這是一個Java AWT的窗體應用程序,完整代碼請參考chapter8_2工程中chapter8_2_1代碼部分。 【代碼清單8-2】 public?class chapter8_2_1 extends Frame implements ActionListener, Runnable {????private?Label label; ????private?Button button1; ????private?Thread clockThread; ????private?boolean?isRunning?=?false; ????private?int?timer?=?0; ????public?chapter8_2_1() { ????????button1?=?new?Button("結束計時"); ????????label?=?new?Label("計時器啟動..."); ????????button1.addActionListener(this); ????????setLayout(new?BorderLayout()); ????????add(button1,?"North"); ????????add(label,?"Center"); ????????setSize(320,?480); ????????setVisible(true); ????????clockThread?=?new?Thread(this); ????????/*?線程體是Clock對象本身,線程名字為"Clock"?*/ ????????clockThread.start();?/*?啟動線程?*/ ????????isRunning?=?true; ????} ????@Override ????public?void actionPerformed(ActionEvent event) { ????????isRunning?=?false; ????} ????@Override ????public?void run() { ????????while?(isRunning) { ????????????try { ????????????????Thread.currentThread().sleep(1000); ????????????????timer++; ????????????????label.setText("逝去了?"?+?timer?+?"?秒"); ????????????} catch (InterruptedException e) { ????????????????e.printStackTrace(); ????????????} ????????} ????} ????public?static void main(String?args[]) { ????????chapter8_2_1 a?=?new?chapter8_2_1(); ????} } 其中關于Java AWT知識本書就不在這里介紹了,有興趣的讀者可以自己看看相關書籍。在本例中構建AWT窗體的應用程序方式是繼承Frame類。采用第1種方式——繼承方式實現線程體是不可以的,因為Java是單繼承的,這個類不能既繼承Frame又繼承Thread。應該采用第2種方式——實現Runnable接口方式。Runnable接口也有一個run()方法,它是實現線程體方法,其代碼處理與上一節是一樣。需要注意的是,在第2種方法中,創建了一個Thread成員變量clockThread,才用構造方法new Thread(this)創建一個線程對象,其中創建線程使用的構造方法是Thread(Runnable target),其中的this就是代表本實例,它是一個實現了Runnable接口的實現類。 程序運行結果如圖8-7所示,屏幕開始加載的時候線程啟動開始計算時間,1s更新一次UI,當單擊“結束計時”按鈕時,停止計時。 Java中的實現線程體方式3 實現線程體方式3是實現線程體方式2的變種,本質上還是實現線程體方式2,但是在Android應用開發中經常采用第3種方式。下面我們看第3種方式的計時器代碼清單8-3,完整代碼請參考chapter8_2工程中 chapter8_2_2代碼部分。 【代碼清單8-3】 public?class chapter8_2_2 extends Frame implements ActionListener {????private?Label label; ????private?Button button1; ????private?Thread clockThread; ????private?boolean?isRunning?=?false; ????private?int?timer?=?0; ????public?chapter8_2_2() { ????????button1?=?new?Button("結束計時"); ????????label?=?new?Label("計時器啟動..."); ????????button1.addActionListener(this); ????????setLayout(new?BorderLayout()); ????????add(button1,?"North"); ????????add(label,?"Center"); ????????setSize(320,?480); ????????setVisible(true); ????????/*?線程體是Clock對象本身,線程名字為"Clock"?*/ ????????clockThread?=?new?Thread(new?Runnable() { ????????????@Override ????????????public?void run() { ????????????????while?(isRunning) { ????????????????????try { ????????????????????????Thread.currentThread().sleep(1000); ????????????????????????timer++; ????????????????????????label.setText("逝去了?"?+?timer?+?"?秒"); ????????????????????} catch (InterruptedException e) { ????????????????????????e.printStackTrace(); ????????????????????} ????????????????} ????????????} ????????}); ????????clockThread.start();?/*?啟動線程?*/ ????????isRunning?=?true; ????} ????@Override ????public?void actionPerformed(ActionEvent event) { ????????isRunning?=?false; ????} ????public?static void main(String?args[]) { ????????chapter8_2_2 a?=?new?chapter8_2_2(); ????} ????} 與第2種方式比較,我們發現Frame類不再實現Runnable接口了,而是在實例化Thread類的時候,定義了一個實現Runnable接口的匿名內部類: clockThread?=?new?Thread(new?Runnable() {????????????@Override ????????????public?void run() { ????????????????while?(isRunning) { ????????????????????try { ????????????????????????Thread.currentThread().sleep(1000); ????????????????????????timer++; ????????????????????????label.setText("逝去了?"?+?timer?+?"?秒"); ????????????????????} catch (InterruptedException e) { ????????????????????????e.printStackTrace(); ????????????????????} ????????????????} ????????????} ????}); 有關Java多線程的內容還有很多,例如線程優先級、線程同步等,由于這些內容與本書關系不是很緊密,所以不再介紹了,有關其他的線程知識可以參考Java方面的書籍。接下來介紹一下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所示。 我們打開LogCat窗口,出錯日志信息如圖8-9所示。 系統拋出的異常信息是“Only the original thread that created a view hierarchy can touch its views”,在Android中更新UI處理必須由創建它的線程更新,而不能在其他線程中更新。上面的錯誤原因就在于此。 現在分析一下上面的案例,在上面的程序中有兩個線程:一個主線程和一個子線程,它們的職責如圖8-10所示。 由于labelTimer是一個UI控件,它是在主線程中創建的,但是它卻在子線程中被更新了,更新操作在clockThread線程的run()方法中實現,代碼如下: 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所示。 主線程的職責是顯示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所示,加載屏幕后馬上開始計時,也可以單擊“停止計時”按鈕來停止計時。 |
Android開發中的多線程編程技術2011年10月03日00:05?it168網站原創 作者:關東升 趙志榮 編輯:景保玉?我要評論(5) 標簽:?Android?, 移動開發【IT168技術】多線程這個令人生畏的“洪水猛獸”,很多人談起多線程都心存畏懼。在Android開發過程中,多線程真的很難嗎?多線程程序的“麻煩”源于它很抽象、與單線程程序運行模式不同,但只要掌握了它們的區別,編寫多線程程序就會很容易了。下面讓我們集中精力開始學習吧! 多線程案例——計時器 我在給我的學生講多線程的時候都會舉一個計時器的案例,因為計時器案例是多線程的經典應用。 這個案例中,屏幕啟動之后,進入如圖8-1所示的界面。 屏幕上有一個文本框用于顯示逝去的時間,此外還有一個“停止計時”按鈕。案例的用例圖如圖8-2所示。
▲圖8-1 計時器界面
▲圖8-2 計時器用例圖 能夠在屏幕上“實時地顯示”時間的流逝,單線程程序是無法實現的,必須要多線程程序才可以實現,即便有些計算機語言可以通過封裝好的類實現這一功能,但從本質上講這些封裝好的類就是封裝了一個線程。 綜上所述,完成本案例用到的知識及技術如下: 1)進程和線程的概念; 2)Java中的線程,在Java中創建線程的方式; 3)Android中的線程,包括:Message、Handler、Looper和HandlerThread等概念。 線程究竟是什么?在Windows操作系統出現之前,個人計算機上的操作系統都是單任務系統,只有在大型計算機上才具有多任務和分時設計。Windows、Linux操作系統的出現,把原本只在大型計算機才具有的優點,帶到了個人計算機系統中。 進程概念 一般可以在同一時間內執行多個程序的操作系統都有進程的概念。一個進程就是一個執行中的程序,而每一個進程都有自己獨立的一塊內存空間、一組系統資源。在進程的概念中,每一個進程的內部數據和狀態都是完全獨立的。在Windows操作系統下我們可以通過〈Ctrl+Alt+Del〉組合鍵查看進程,在UNIX和Linux操作系統下是通過PS命令查看進程的。打開Windows當前運行的進程,如圖8-3所示。
▲圖8-3 Windows操作系統進程 在Windows操作系統中一個進程就是一個exe或dll程序,它們相互獨立,互相也可以通信,在Android操作系統中進程間的通信應用也是很多的。 線程概念 多線程指的是在單個程序中可以同時運行多個不同的線程,執行不同的任務。多線程意味著一個程序的多行語句可以看上去幾乎在同一時間內同時運行。 線程與進程相似,是一段完成某個特定功能的代碼,是程序中單個順序的流控制。但與進程不同的是,同類的多個線程共享一塊內存空間和一組系統資源,所以系統在各個線程之間切換時,資源占用要比進程小得多,正因如此,線程也被稱為輕量級進程。一個進程中可以包含多個線程。圖8-4所示是計時器程序進程和線程之間的關系,主線程負責管理子線程,即子線程的啟動、掛起、停止等操作。
▲圖8-4 進程和線程關系 Java中的線程 Java的線程類是java.lang.Thread類。當生成一個Thread類的對象之后,一個新的線程就產生了。Java中每個線程都是通過某個特定Thread對象的方法run()來完成其操作的,方法run( )稱為線程體。 下面是構建線程類幾種常用的方法: public Thread() public Thread(Runnable target) public Thread(Runnable target, String name) public Thread(String name) 參數target是一個實現Runnable接口的實例,它的作用是實現線程體的run()方法。目標target可為null,表示由本身實例來執行線程。name參數指定線程名字,但沒有指定的構造方法,線程的名字是JVM分配的,例如JVM指定為thread-1、thread-2等名字。 1、Java中的實現線程體方式1 在Java中有兩種方法實現線程體:一是繼承線程類Thread,二是實現接口Runnable。下面我們先看看繼承線程類Thread方式。 如果采用第1種方式,它繼承線程類Thread并重寫其中的方法 run(),在初始化這個類實例的時候,目標target可為null,表示由本實例來執行線程體。由于Java只支持單重繼承,用這種方法定義的類不能再繼承其他父類,例如代碼清單8-1,完整代碼請參考chapter8_1工程中chapter8_1代碼部分。 【代碼清單8-1】 public?class chapter8_1 extends Thread {????boolean?isRunning?=?true; ????int?timer?=?0; ????/** ?????*?線程體代碼 ?????*/ ????@Override ????public?void run() { ????????while?(isRunning) { ????????????try { ????????????????Thread.currentThread().sleep(1000); ????????????????timer++; ????????????????System.out.println("逝去了?"+timer+"?秒"); ????????????} catch (InterruptedException e) { ????????????????e.printStackTrace(); ????????????} ????????} ????} ???? ????public?static void main(String[] args) { ????????chapter8_1 t1?=?new?chapter8_1(); ????????t1.start(); ????????System.out.println("計時器啟動..."); ????????BufferedReader br?=?new?BufferedReader(new?InputStreamReader(System.in)); ????????try { ????????????String?line?=?br.readLine(); ????????????if?(line.equalsIgnoreCase("1")) { ????????????????t1.isRunning?=?false; ????????????????/*t1.stop();*/ ????????????} ????????} catch (IOException e) { ????????????e.printStackTrace(); ????????} ????} ????} 在main主方法中通過new chapter8_1()創建子線程,并通過t1.start()方法啟動子線程,main主方法所在線程為主線程,主線程負責管理其他的子線程。本例進程、主線程和子線程之間的關系如圖8-5所示。 子線程啟動之后就開始調用run()方法,run()是一個線程體,我們在子線程中處理事情就是在這里編寫代碼實現的。本案例中子線程要做的事情就是:休眠1s,計時器加1,再反復執行。Thread.currentThread().sleep(1000)就是休眠1s。 為了能夠停止線程,我們在主線程中增加了一個標識,通過在控制臺輸入一個字符 “1”來改變該標識t1.isRunning = false,從而結束這個線程。 注意: 事實上線程中有一個stop()方法也可以停止線程,但是由于這種方法會產生線程死鎖問題,所以在新版JDK中已經廢止了,它的替代解決方法就是增加標識,就是我們在本例中采用的方案。 很多人覺得線程難理解,主要有兩個問題: 線程休眠,既然線程已經休眠了,程序的運行速度還能提高嗎? 線程體一般都進行死循環,既然線程死循環,程序就應該死掉了,就會沒有反應。 1.關于線程休眠問題 對線程休眠問題頭痛的讀者,其實還是在用單線程的思維模式考慮問題,多數情況下我們的PC都是單CPU的,某個時間點只能有一個線程運行。所謂多線程就是多個線程交替執行就好像同時運行似的。因此,休眠當前線程可以交出CPU控制權,讓其他的線程有機會運行,多個線程之間只有交替運行效率才是最高的,這就像我們開車過十字路口,只有我等等,讓你先過,你再等等讓他先過,才能保證最高效率,否則就會造成交通系統崩潰,對線程情況也是一樣的。因此,多線程中線程的休眠是程序運行的最有效方式。 2.關于線程體死循環問題 在單線程中如果是死循環,程序應就會死掉,沒有反應,但是多線程中線程體(run方法)中的死循環,可以保證線程一直運行,如果不循環線程,則運行一次就停止了。在上面的例子中線程體運行死循環,可以保證線程一直運行,每次運行都休眠1s,然后喚醒,再然后把時間信息輸出到控制臺。所以,線程體死循環是保證子線程一直運行的前提。由于是子線程它不會堵塞主線程,就不會感覺到程序死掉了。但是需要注意的是有時我們確實執行一次線程體,就不需要循環了。 程序運行后開始啟動線程,線程啟動后就計算逝去的時間,每過1s將結果輸出到控制臺。當輸入1字符后線程停止,程序終止。如圖8-6所示。 Java中的實現線程體方式2 上面介紹繼承Thread方式實現線程體,下面介紹另一種方式,這種方式是提供一個實現接口Runnable的類作為一個線程的目標對象,構造線程時有兩個帶有Runnable target參數的構造方法: Thread(Runnable target); Thread(Runnable target, String name)。 其中的target就是線程目標對象了,它是一個實現Runnable的類,在構造Thread類時候把目標對象(實現Runnable的類)傳遞給這個線程實例,由該目標對象(實現Runnable的類)提供線程體run()方法。這時候實現接口Runnable的類仍然可以繼承其他父類。 請參看代碼清單8-2,這是一個Java AWT的窗體應用程序,完整代碼請參考chapter8_2工程中chapter8_2_1代碼部分。 【代碼清單8-2】 public?class chapter8_2_1 extends Frame implements ActionListener, Runnable {????private?Label label; ????private?Button button1; ????private?Thread clockThread; ????private?boolean?isRunning?=?false; ????private?int?timer?=?0; ????public?chapter8_2_1() { ????????button1?=?new?Button("結束計時"); ????????label?=?new?Label("計時器啟動..."); ????????button1.addActionListener(this); ????????setLayout(new?BorderLayout()); ????????add(button1,?"North"); ????????add(label,?"Center"); ????????setSize(320,?480); ????????setVisible(true); ????????clockThread?=?new?Thread(this); ????????/*?線程體是Clock對象本身,線程名字為"Clock"?*/ ????????clockThread.start();?/*?啟動線程?*/ ????????isRunning?=?true; ????} ????@Override ????public?void actionPerformed(ActionEvent event) { ????????isRunning?=?false; ????} ????@Override ????public?void run() { ????????while?(isRunning) { ????????????try { ????????????????Thread.currentThread().sleep(1000); ????????????????timer++; ????????????????label.setText("逝去了?"?+?timer?+?"?秒"); ????????????} catch (InterruptedException e) { ????????????????e.printStackTrace(); ????????????} ????????} ????} ????public?static void main(String?args[]) { ????????chapter8_2_1 a?=?new?chapter8_2_1(); ????} } 其中關于Java AWT知識本書就不在這里介紹了,有興趣的讀者可以自己看看相關書籍。在本例中構建AWT窗體的應用程序方式是繼承Frame類。采用第1種方式——繼承方式實現線程體是不可以的,因為Java是單繼承的,這個類不能既繼承Frame又繼承Thread。應該采用第2種方式——實現Runnable接口方式。Runnable接口也有一個run()方法,它是實現線程體方法,其代碼處理與上一節是一樣。需要注意的是,在第2種方法中,創建了一個Thread成員變量clockThread,才用構造方法new Thread(this)創建一個線程對象,其中創建線程使用的構造方法是Thread(Runnable target),其中的this就是代表本實例,它是一個實現了Runnable接口的實現類。 程序運行結果如圖8-7所示,屏幕開始加載的時候線程啟動開始計算時間,1s更新一次UI,當單擊“結束計時”按鈕時,停止計時。 Java中的實現線程體方式3 實現線程體方式3是實現線程體方式2的變種,本質上還是實現線程體方式2,但是在Android應用開發中經常采用第3種方式。下面我們看第3種方式的計時器代碼清單8-3,完整代碼請參考chapter8_2工程中 chapter8_2_2代碼部分。 【代碼清單8-3】 public?class chapter8_2_2 extends Frame implements ActionListener {????private?Label label; ????private?Button button1; ????private?Thread clockThread; ????private?boolean?isRunning?=?false; ????private?int?timer?=?0; ????public?chapter8_2_2() { ????????button1?=?new?Button("結束計時"); ????????label?=?new?Label("計時器啟動..."); ????????button1.addActionListener(this); ????????setLayout(new?BorderLayout()); ????????add(button1,?"North"); ????????add(label,?"Center"); ????????setSize(320,?480); ????????setVisible(true); ????????/*?線程體是Clock對象本身,線程名字為"Clock"?*/ ????????clockThread?=?new?Thread(new?Runnable() { ????????????@Override ????????????public?void run() { ????????????????while?(isRunning) { ????????????????????try { ????????????????????????Thread.currentThread().sleep(1000); ????????????????????????timer++; ????????????????????????label.setText("逝去了?"?+?timer?+?"?秒"); ????????????????????} catch (InterruptedException e) { ????????????????????????e.printStackTrace(); ????????????????????} ????????????????} ????????????} ????????}); ????????clockThread.start();?/*?啟動線程?*/ ????????isRunning?=?true; ????} ????@Override ????public?void actionPerformed(ActionEvent event) { ????????isRunning?=?false; ????} ????public?static void main(String?args[]) { ????????chapter8_2_2 a?=?new?chapter8_2_2(); ????} ????} 與第2種方式比較,我們發現Frame類不再實現Runnable接口了,而是在實例化Thread類的時候,定義了一個實現Runnable接口的匿名內部類: clockThread?=?new?Thread(new?Runnable() {????????????@Override ????????????public?void run() { ????????????????while?(isRunning) { ????????????????????try { ????????????????????????Thread.currentThread().sleep(1000); ????????????????????????timer++; ????????????????????????label.setText("逝去了?"?+?timer?+?"?秒"); ????????????????????} catch (InterruptedException e) { ????????????????????????e.printStackTrace(); ????????????????????} ????????????????} ????????????} ????}); 有關Java多線程的內容還有很多,例如線程優先級、線程同步等,由于這些內容與本書關系不是很緊密,所以不再介紹了,有關其他的線程知識可以參考Java方面的書籍。接下來介紹一下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所示。 我們打開LogCat窗口,出錯日志信息如圖8-9所示。 系統拋出的異常信息是“Only the original thread that created a view hierarchy can touch its views”,在Android中更新UI處理必須由創建它的線程更新,而不能在其他線程中更新。上面的錯誤原因就在于此。 現在分析一下上面的案例,在上面的程序中有兩個線程:一個主線程和一個子線程,它們的職責如圖8-10所示。 由于labelTimer是一個UI控件,它是在主線程中創建的,但是它卻在子線程中被更新了,更新操作在clockThread線程的run()方法中實現,代碼如下: 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所示。 主線程的職責是顯示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所示,加載屏幕后馬上開始計時,也可以單擊“停止計時”按鈕來停止計時。 |
總結
以上是生活随笔為你收集整理的Android开发中的多线程编程技术的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java基础笔记 – 增强的for循环F
- 下一篇: 【Android】关于Android控件