|
1. 本文的講述內容和範圍
本文講述的是Android中線程及創建線程的方法,涉及到的內容包括: Runable, Thread, Handler, Looper, HandlerThread, AsyncTask。將要詳細講述以下幾個方面的內容:
2) Runable和Thread的關係
3) Handler, Thread, Looper, HandlerThread之間的關係
4) Runable的一點說明
5) Android中的便利類(AsyncTask等)
2. Runable和Thread的關係
在java中可有兩種方法實現多線程,一種是繼承Thread類,一種是實現Runnable接口;
1)繼承Thread類的方式
Thread類是在java.lang包中定義的。一個類只要繼承了Thread類同時覆寫了本類中的run()
步驟就可以實現多線程操作了,然而一個類只能繼承一個父類,這是此種方法的的局限。下面看例子:
- class MyThread extends Thread{
- private String name;
- public MyThread(String name) {
- super();
- this.name = name;
- }
- public void run() {
- for(int i=0;i<10;i++) {
- System.out.println("線程開端:"+this.name+",i="+i);
- }
- }
- }
- public class ThreadDemo01 {
- public static void main(String[] args) {
- MyThread mt1=new MyThread("線程a");
- MyThread mt2=new MyThread("線程b");
- mt1.start();
- mt2.start();
- }
- }
複製代碼
2) Runnable接口
在實際開闢中一個多線程的操作很少使用Thread類,而是通過Runnable接口實現。- <div>public interface Runnable{</div><div> public void run(); </div><div>}</div>
複製代碼
例子:
- class MyThread implements Runnable{
- private String name;
- public MyThread(String name) {
- this.name = name;
- }
- public void run(){
- for(int i=0;i<100;i++){
- System.out.println("線程開端:"+this.name+",i="+i);
- }
- }
- };
複製代碼
然而在Runnable的子類中沒有start() 方法,只有Thread類中才有。此時視察Thread類,有一個構造函數:public Thread(Runnable targer) 此構造函數接受Runnable的子類實例,也就是說可以通過Thread類來啟動Runnable實現多線程。(start() 可以協調系統的資源):
- public class ThreadDemo01 {
- public static void main(String[] args) {
- MyThread mt1=new MyThread("線程a");
- MyThread mt2=new MyThread("線程b");
- new Thread(mt1).start();
- new Thread(mt2).start();
- }
- }
複製代碼
3)實際中如何應用這兩種實現模式
在程序實現多線程應優先以實現Runnable接口為主,由於實現Runnable接口相比繼承Thread類有如下好處:
• 避免點繼承的局限,一個類可以繼承多個接口。
• 利於資源的共享。
以賣票程序為例,通過Thread類實現:
- class MyThread extends Thread {
- private int ticket=10;
- public void run(){
- for(int i=0;i<20;i++) {
- if(this.ticket>0){
- System.out.println("賣票:ticket"+this.ticket--);
- }
- }
- }
- };
複製代碼
下面通過三個線程對象,同時賣票:
- public class ThreadTicket {
- public static void main(String[] args) {
- MyThread mt1=new MyThread();
- MyThread mt2=new MyThread();
- MyThread mt3=new MyThread();
- mt1.start();//每個線程都各賣了10張,共賣了30張票
- mt2.start(); //但實際只有10張票,每個線程都賣自己的票
- mt3.start();//沒有達到資源共享
- }
- }
複製代碼
假如用Runnable就可以實現資源共享,下面看例子:
- class MyThread implements Runnable{
- private int ticket=10;
- public void run(){
- for(int i=0;i<20;i++){
- if(this.ticket>0){
- System.out.println("賣票:ticket"+this.ticket--);
- }
- }
- }
- }
- public class RunnableTicket {
- public static void main(String[] args) {
- MyThread mt=new MyThread();
- new Thread(mt).start();//同一個mt
- new Thread(mt).start();
- new Thread(mt).start();
- }
- };
複製代碼
現在程序中有三個線程,然而一共賣了10張票,也就是說使用Runnable實現多線程可以達到資源共享的目的。
4)Runnable接口和Thread的關係總結
(1)說白了就是類和接口的區別。Thread是一個類,java中是不允許繼承多個父類的,這就是Thread的一個局限性。而使用Runnable就不同了,可以implements多個接口,同時繼承一個父類,這樣會更加靈活。
(2)當多個線程需要共享資源時,用Thread很難達到目的,但是用Runnable接口就容易許多了。
(3)二者的聯繫:看源碼可以發現,Thread其實就是繼承了Runnable接口的子類。
3. Handler, Thread, Looper之間的關係
1)概述
在Android中,線程分為有消息循環的線程和沒有消息循環的線程,有消息循環的線程一般都會有一個Looper,這個是android的新概念。我們的主線程(UI線程)就是一個有消息循環的線程。
針對這種消息循環的機制,我們引入一個新的機制--Handler,
我們有消息循環,就要往消息循環裡面發送相應的消息,自定義消息一般都會有自己對應的處理,消息的發送和清除、消息的的處理,把這些都封裝在Handler裡面,注意Handler只是針對那些有Looper的線程,不管是UI線程還是子線程,只要你有Looper,我就可以往你的消息隊列裡面添加東西,並做相應的處理。
一個線程對應一個或者零個Looper和MessageQueue。
2)各自職責
(1) Message:消息,其中包含了消息ID,消息處理對像以及處理的數據等,由MessageQueue統一列隊,終由Handler處理。
(2) MessageQueue:消息隊列,用來存放Handler發送過來的消息,並按照FIFO規則執行。當然,存放Message並非實際意義的保存,而是將Message以鍊錶的方式串聯起來的,等待Looper的抽取。
(3) Handler:處理者,負責Message的發送及處理。使用Handler時,需要實現handleMessage(Message msg)方法來對特定的Message進行處理,例如更新UI等。
(4) Looper:消息泵,不斷地從MessageQueue中抽取Message執行。因此,一個MessageQueue需要一個Looper。
(5) Thread:線程,負責調度整個消息循環,即消息循環的執行場所。
(6) Looper.myLooper()獲得新線程的Looper,Looper.getMainLooper()是獲得主線程的Looper
(7)通過new MyHandler(Looper)有參構造函數來讓Looper和Handler進行溝通
無參的構造函數,默認獲取的是當前線程的Looper
Message message = mHandler.obtainMessage(1, 1, 1, msg);
mHandler.sendMessage(message); //發送消息
定義一個類繼承自Handler,改寫方法handleMessage(Message msg)接收並處理消息
3) Handler
Handler在android裡負責發送和處理消息。它的主要用途:
1)按計劃發送消息或執行某個Runnanble(使用POST方法),類似定時器;
2)從其他線程中發送來的消息放入消息隊列中,避免線程衝突(常見於更新UI線程);
默認情況下,Handler接受的是當前線程下的消息循環實例(使用Handler(Looper looper)、Handler(Looper looper, Handler.Callback callback)可以指定線程),同時一個消息隊列(MessageQueue和Looper封裝)可以被當前線程中的多個對象進行分發、處理(在UI線程中,系統已經有一個Activity來處理了,你可以再起若干個Handler來處理)。
在實例化Handler的時候,Looper可以是任意線程的,只要有Handler的指針,任何線程也都可以sendMessage。
Handler對於Message的處理不是並發的。一個Looper 只有處理完一條Message才會讀取下一條,所以消息的處理是阻塞形式的(handleMessage()方法裡不應該有耗時操作,可以將耗時操作放在其他線程執行,操作完後發送Message(通過sendMessges方法),然後由handleMessage()更新UI)。
4)Looper
Looper類用來創建消息隊列. 每個線程最多只能有一個消息隊列, android中UI線程默認具有消息隊列, 但非UI線程在默認情況下是不具備消息隊列的. 如果需要在非UI線程中開啟消息隊列, 需要調用Looper.prepare()方法, 在該方法的執行過程中會創建一個Looper對象, 而Looper的構造函數中會創建一個MessageQueue instance(Looper的構造函數是私有的, 在Looper類之外無法創建其對象). 此後再為該線程綁定一個Handler instance, 然後調用Looper.loop()方法, 就可以不斷的從消息隊列中取出消息和處理消息了. Looper.myLoop()方法可以得到線程的Looper對象, 如果為null, 說明此時該線程尚未開啟消息隊列.
創建一個Looper對象時,會同時創建一個MessageQueue對象(一個looper對應一個MessageQueue)。除了 主線程有默認的Looper ,其他線程默認是沒有MessageQueue對象的,所以,不能接受Message 。如需要接受,自己定義一個Looper對象(通過prepare函數),這樣該線程就有了自己的Looper對象和MessageQueue數據結構了。
Looper從MessageQueue中取出Message然後,交由Handler的handleMessage進行處理。處理完成後,調用Message.recycle()將其放入Message Pool中。
5) Message
Message :消息對象, Message Queue 中的存放的對象。一個Message Queue 中包含多個Message 。
Message實例對象的取得,通常使用Message類裡的靜態方法obtain(),該方法有多個重載版本可供選擇;它的創建並不一定是直接創建一個新的實例,而是先從Message Pool (消息池)中看有沒有可用的Message實例,存在則直接取出返回這個實例。如果Message Pool中沒有可用的Message實例,則才用給定的參數創建一個Message對象。調用removeMessages()時,將Message從Message Queue中刪除,同時放入到Message Pool中。除了 上面這種方式,也可以通過Handler對象的obtainMessage()獲取一個Message實例。
Message類用於表示消息. Message對象可以通過arg1, arg2, obj字段和setData()攜帶數據,此外還具有很多字段.
when字段決定Message應該何時出對處理,;
target字段用來表示將由哪個Handler對象處理這個消息;
next字段表示在消息隊列中排在這個Message之後的下一個Message;
callback字段如果不為null表示這個Message包裝了一個runnable對象;
what字段表示code, 即這個消息具體是什麼類型的消息. 每個what都在其handler的namespace中, 我們只需要確保將由同一個handler處理的消息的what屬性不重複就可以.
6) MessageQueue
MessageQueue類用於表示消息隊列. 隊列中的每一個Message都有一個when字段, 這個字段用來決定Message應該何時出隊處理. 消息隊列中的每一個Message根據when字段的大小由小到大排列, 排在最前面的消息會首先得到處理, 因此可以說消息隊列並不是一個嚴格的先進先出的隊列。主線程創建時,會創建一個默認的Looper 對象,而Looper 對象的創建,將自動創建一個Message Queue 。其他非主線程,不會自動創建Looper ,要需要的時候,通過調用prepare 函數來實現。
將消息壓入消息隊列
Message對象的target字段關聯了哪個線程的消息隊列, 這個消息就會被壓入哪個線程的消息隊列中.
a 調用Handler類中以send開頭的方法可以將Message對象壓入消息隊列中;
b 調用Handler類中以post開頭的方法可以將一個runnable對象包裝在一個Message對像中, 然後再壓入消息隊列, 此時入隊的Message其callback字段不為null, 值就是這個runnable對象. 調用Handler對象的這些方法入隊的Message, 其target屬性會被賦值為這個handler對象.
c 調用Message對象的sendToTarget()方法可以將其本身壓入與其target字段(即handler對象)所關聯的消息隊列中.
從消息隊列中取出消息並處理消息
所有在消息隊列中的消息, 都具有target字段. 消息是在target所關聯的線程上被取出和處理的.
1. 如果取出的Message對象的callback字段不為null, 那麼就調用callback字段的run()方法(callback字段的類型是runnable). 注意此時並不開啟一個新的線程運行run()方法, 而是直接在handler對象(即Message的target字段)所關聯的線程上運行.
2. 如果取出的Message對象的callback字段為null, 且Handler對像中的callback字段也為null, 那麼這個消息將由Handler對象的handleMessage(msg)方法處理. 注意Message對象的callback字段是Runnable類型的而Handler對象的callback字段是Callback類型的, Handler對象的callback字段是在創建Handler instance的時候指定的, 如果沒有指定則這個字段為null, 詳見Handler類的四個構造方法.
3. 如果取出的Message對象的callback字段為null, 且Handler對像中的callback字段不為null, 那麼這個消息將由Handler對像中的callback字段的handleMessage方法處理.
以上的述說太麻煩,下面看一下源代碼中的實現吧, 加深一下印象:
- public void dispatchMessage(Message msg) {
- if (msg.callback != null) {
- handleCallback(msg);
- } else {
- if (mCallback != null) {
- if (mCallback.handleMessage(msg)) {
- return;
- }
- }
- handleMessage(msg);
- }
- }
複製代碼
7) Handler的用法例子
有了以上的敘述, 線程間的通信也就好理解了. 假如一個handler關聯了A線程上的消息隊列, 那麼我們可以在B線程上調用handler的相關方法向A線程上的消息隊列壓入一個Message, 這個Message將在A線程上得到處理. 下面列舉幾個常用的例子:
通過Runnable在子線程中更新界面的例子
a. 在onCreate中創建Handler
- public class HandlerTestApp extends Activity {
- Handler mHandler;
- TextView mText;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- mHandler = new Handler();//創建Handler
- mText = (TextView) findViewById(R.id.text0);//一個TextView
- }
複製代碼
b. 構建Runnable對象,在runnable中更新界面,此處,我們修改了TextView的文字.此處需要說明的是,Runnable對象可以再主線程中創建,也可以再子線程中創建。我們此處是在子線程中創建的。
- Runnable mRunnable0 = new Runnable()
- {
- @Override
- public void run() {
- // TODO Auto-generated method stub
- mText.setText("This is Update from ohter thread, Mouse DOWN");
- }
- };
複製代碼
c. 創建子線程,在線程的run函數中,我們向主線程的消息隊列發送了一個runnable來更新界面。
- private void updateUIByRunnable(){
- new Thread()
- {
- //Message msg = mHandler.obtainMessage();
- public void run()
- {
- //mText.setText("This is Update from ohter thread, Mouse DOWN");/這句將拋出異常
- mHandler.post(mRunnable0);
- }
- }.start();
- }
複製代碼
用Message在子線程中來更新界面
用Message更新界面與Runnable更新界麵類似,只是需要修改幾個地方。
a.實現自己的Handler,對消息進行處理
- private class MyHandler extends Handler
- {
- @Override
- public void handleMessage(Message msg) {
- // TODO Auto-generated method stub
- super.handleMessage(msg);
- switch(msg.what)
- {
- case UPDATE://在收到消息時,對界面進行更新
- mText.setText("This update by message");
- break;
- }
- }
- }
複製代碼
b. 在新的線程中發送消息
- private void updateByMessage()
- {
- //匿名對象
- new Thread()
- {
- public void run()
- {
- //mText.setText("This is Update from ohter thread, Mouse DOWN");
- //UPDATE是一個自己定義的整數,代表了消息ID
- Message msg = mHandler.obtainMessage(UPDATE);
- mHandler.sendMessage(msg);
- }
- }.start();
- }
複製代碼
8) HandlerThread
上一節講到,UI線程都是帶Looper的線程,可以進行消息循環,而自己新建的子線程或線程都是沒有Looper的,不能接收消息進行處理,如果想要往自己的線程中發送消息或post一個runable,那麼必須在自己的線程中去調用Looper.prepare()。
如果自己要實現Thread並手工建立Loop的話,則需要注意線程的同步等問題。具體參考《深入理解Android 卷一》
但是,哈哈,Android是一個優秀易用的系統,對於這樣的問題,它早就想到了,並提供了解決方案,那就是HandlerThread。大家直接用它就行。下面給一個例子:
- private class TestHandlerThread extends HandlerThread{ // 注5
- public TestHandlerThread(String name) {
- super(name);
- // TODO Auto-generated constructor stub
- }
- @Override
- public void run() {
- // TODO Auto-generated method stub
- super.run();
-
- Message msg = mTestHandler1.obtainMessage();
- Bundle b = new Bundle();
- b.putString("color", "red");
- //給handler發送的數據,放入message中
- msg.setData(b);
- msg.what = MESSAGE_ID;
-
- Looper looper = this.getLooper();
- new Handler(looper){
- @Override
- public void handleMessage(Message msg) {
- // TODO Auto-generated method stub
- super.handleMessage(msg);
- Log.d("HandlerDemo", "TestHandlerThread---------");
- }
-
- }.sendMessage(msg);
- }
- }
-
- // (3)new HandlerThread
- TestHandlerThread testHandlerThread = new TestHandlerThread("testHandlerThread");
- testHandlerThread.start();
複製代碼
上面的例子,是在自己的線程中new Handler並給自己發消息, 當然你也可以在主線程或其它線程中創建Handler, 給它發消息,讓它進行處理。
4. Runable的一點說明
大家如果能看到這裡,那恭喜你,來點輕鬆的,哈哈。
Runable在上面總共說到了兩種用途。還記得嗎,請看:
1) 實現一個Runable接口的子類,把它當實例傳給Thread的構造函數,來創建一個新線程。
2) 實現一個Runable接口的子類,用Handler把它post到相關聯的線程中。
不用疑惑,上面的兩個用法沒有一點點關係,把它當成兩類來看就是了。原因就是Runable是一個接口,只定義了一個run()方法,具體用法可以多樣化,呵呵~~
文章來源:http://zzqhost.com/?post=57
|
|