1. 程式人生 > >Android學習筆記(三一:執行緒:Message和Runnable

Android學習筆記(三一:執行緒:Message和Runnable

程式需要相應使用者的操作,最要能在200ms(0.2s)之內,如果超過5秒沒有反應,ActivityManager會沒有提示就kill了activity。然而,activity可能真的需要時間來進行處理,這往往會用到後臺執行緒-background thread。後臺執行緒可以安全地和UI執行緒進行互動,其中後臺執行緒是不能修改UI的。我不太確切知曉“不能修改UI”到何種程度,例如在下面的例子進度條狀態的修改是允許的,在複雜的例子中可能會出現問題,例如兩個後臺執行緒都要處理同一個widget,可能會有不可預知的情況出現,然而就程式設計而言,確實不應當如此處理,後臺程序應避免涉及UI的處理,UI的歸UI,處理的規處理。這樣才是良好的程式設計風格或者是一種程式設計原則。

通過建立一個Handler子類的物件,每個acvivity只需一個Handler物件。後臺程序可通過兩種方式Handler進行通訊:message和Runnable物件,其結果實質都是將在Handler的佇列中放入內容,message是放置資訊,可以傳遞一些引數,Handler獲取這些資訊並將判度如何處理,而Runnable則是直接給出處理的方法。佇列就是依次執行,Handler會處理完一個訊息或者執行完某個處理在進行下一步,這樣不會出現多個執行緒同時要求進行UI處理而引發的混亂現象。

這些佇列中的內容(無論Message還是Runnable)可以要求馬上執行,延遲一定時間執行或者指定某個時刻執行,如果將他們放置在佇列頭,則表示具有最高有限級別,立即執行。這些函式包括有:sendMessage(), sendMessageAtFrontOfQueue(), sendMessageAtTime(), sendMessageDelayed()以及用於在佇列中加入Runnable的post(), postAtFrontOfQueue(), postAtTime(),postDelay()。

一般而言,推薦是Messge方式,這樣程式設計得可以更為靈活,而Runnable在某些簡單明確的方式中使用。我們將通過三種方法編寫一個小例子來學習。這個例子是一個進度條,每隔1秒,進度條步進5,如果acvity停止時,進度條歸零。

Android XML

<?xml version="1.0" encoding="utf-8"?><LinearLayout ...... />  <ProgressBar android:id="@+id/c15_progress"     style="?android:attr/progressBarStyleHorizontal"  <!-- 這表明採用傳統水平進度條的方式-->

    android:layout_width="fill_parent"    android:layout_height="wrap_content" /></LinearLayout>

例子一:執行緒開啟,採用Message傳遞後臺執行緒和UI主執行緒之間的資訊

public class Chapter15Test1 extends Activity{    private ProgressBar bar = null;    private boolean isRunning = false;        /* 我們為這個Acivity建立一個用於和後臺程式通訊的handler,簡單地,只要一收到message,就將progressbar進度增加5。*/    /* 步驟1:建立Handler,並通過handleMessage()給出當收到訊息是UI需要進行如何處理,例子簡單不對msg的內容進行分析*/    Handler handler= new Handler(){        public void handleMessage(Message msg) {            bar.incrementProgressBy(5);        }    };        protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.chapter_15_test1);        bar=(ProgressBar)findViewById(R.id.c15_progress);    }    /*on Start是UI初始化並顯示時呼叫*/    protected void onStart() {        super.onStart();        bar.setProgress(0);        /*步驟2:建立後臺執行緒處理,採用Thread,其中run()的內容,就是執行緒並行處理的內容,Thread是Runnable的implements*/        Thread background = new Thread(new Runnable(){            public void run() {                try{                    for(int i = 0; i < 20 && isRunning; i ++){                        Thread.sleep(1000);                        /* 步驟2.1:傳送Message到佇列中,引數中的obtainMessage()是用於給出一個新Message,本例無引數,對應的在handler在佇列中收到這條訊息時,則通過handleMessage()進行處理*/                        handler.sendMessage(handler.obtainMessage());                    }                }catch(Throwable t){                    //jest end the thread                }            }                    });        isRunning = true;       /*步驟3:啟動執行緒*/        background.start();    }    /*onStop是UI停止顯示時呼叫,例如我們按了返回鍵*/    protected void onStop() {        super.onStop();        isRunning = false;    }  }

例子2:採用Runnable

我們在上面的例子的基礎上進行修改,如下

    /*步驟1:由於不需要處理Message,也即不需要處理handleMessage()*/    Handler handler= new Handler();   /*步驟1.1:定義處理動作,採用Runnable的例項,通過implements run()來定製處理,這裡是簡單將進度條步進5。由於我們將在Thread中使用這個例項,所以考慮採用final的方式*/    final Runnable r = new Runnable(){         public void run(){            bar.incrementProgressBy(5);         }      };   /* ... ...在onStart()中的步驟2:執行緒的處理,和提供message不同,對於runnable方式,採用post */        Thread background = new Thread(new Runnable(){            public void run() {                try{                    for(int i = 0; i < 20 && isRunning; i ++){                        Thread.sleep(1000);                        handler.post(r);                    }                }catch(Throwable t){                    //jest end the thread                }            }                  });        background.start();

例子3:可以用延遲處理實現定時觸發,讓程式更為簡單

在這裡例子,事實我們是進行定時的處理,利用Handler佇列可以設定延期處理的方式,我們並不需要建立一個後臺執行的執行緒,也可以實現

    Handler handler= new Handler();      ... ... 在onStart() ... ...    //利用handler.postDelayed(r,1000),在佇列中要求延遲1秒後進行r的處理,而在r的處理中, 最後在handler的佇列中加 入一個要求延遲1秒的處理,如是,就可以實現每隔1秒的定期處理。        handler.postDelayed(new Runnable(){            public void run() {                if(isRunning && Chapter15Test2.step < 20){                    step ++;                    bar.incrementProgressBy(5);                    handler.postDelayed(this, 1000);                }            }                },1000);

在這個例子中,我們基礎某種判度,自動停止向佇列加入處理。如果有某種情況,我們需要清除佇列中的訊息或者理,可以使用removMessages()或者removeCallbacks()的處理,這種對於延遲處理方式是非常有用的,可以中斷定期的處理。當然,一般來講我們希望能夠得到某種判度,以使得定期處理能夠優雅地結束,而不是簡單地從佇列中將訊息或者處理刪除。

例子4:不知道在UI主執行緒還是在後臺執行緒

有時候,我們並不清楚程式碼將在UI執行緒還是後臺執行緒執行,例如這些程式碼封裝為一個JAR,提供給其他人呼叫,我們並不清楚其他人如何使用這些程式碼。為了解決這個問題Android在activity中提供了runOnUiThread(),如果在UI執行緒,則馬上執行,如果在後臺執行緒,則將Runnable的執行內容加入到主執行緒的佇列中,這樣無論程式碼在UI執行緒還是後臺執行緒都能安全地執行。

我們在例子1的基礎上進行試驗:

1、建立一個Runnable,以便我們將在UI和後臺Thread中進行試驗

    Runnable runAction = new Runnable(){        public void run(){            //注意,我們不能使用Toast.makeText(this,....),因為我們無法確定Runnable具體執行的context            Toast.makeText(getApplicationContext(),"Hello!",Toast.LENGTH_SHORT).show();            //Log.d("WEI","runAction .... is called");        }    };

由於Toast的顯示和隱藏需要一定的時間,而間隔1秒顯然不夠,我們將例子1的間隔時間1000ms,改為5000ms這樣會比較清晰,當然可以採用Log.d的方式來替代。

2、在UI執行緒中執行該操作,在後臺執行緒中增加該操作,這個操作無論是在UI還是在後臺執行緒都是可以正確執行的。

protected void onStart() {    ... ...     Thread background = new Thread(new Runnable(){        public void run() {            try{                for(int i = 0; i < 20 && isRunning; i ++){                    Thread.sleep(5000);                    handler.sendMessage(handler.obtainMessage());                    runOnUiThread(runAction);                }            }catch(Throwable t){                //jest end the thread            }        }               });            isRunning = true;    background.start();    runOnUiThread(runAction);}

例子5:HandlerThread

在上面的例子中,無論是否使用了後臺執行緒(例子1-2),Handler的處理實際就是UI主執行緒的處理,一般的使用方式為我們通過後臺執行緒執行某些操作,如果需要進行UI的互動,將訊息或者處理方式到Handler的的佇列中,然手在UI主執行緒中進行處理。這是我們通用的情況。

之前我們討論過為何UI的歸UI,處理的處理,然而,可能有這樣的需求,舉個例子,在某些情況下,Handler收到訊息觸發的處理中可能會有Sleep(),這會導致main執行緒進入sleep狀態,不是我們期待的。因此我們希望通過一個執行緒專門處理Hanlder的訊息,這個執行緒也是依次從Handler的佇列中獲取資訊,逐個進行處理,保證安全,不會出現混亂引發的異常。

針對此Android提供的HandlerThread。方式使用方法如下:

//步驟1:創新HandlerThread的一個物件,並開啟這個執行緒,HandlerThread將通過Looper來處理Handler對來中的訊息,也就是如果發現Handler中有訊息,將在HandlerThread這個執行緒中進行處理。HandlerThread ht = new HandlerThread("hander_thread");//步驟2:啟動handerhandler這個執行緒;ht.start();//步驟3:建立handler中,帶上Looper的引數,即handlerThread.getLooper()。注意,此處理必須在HandlerThread啟動後才能呼叫,否則會報 ,getLooper()會返回null,則程式異常出錯Handler handler = new Handler(ht.getLooper()){    ....    public void handleMessage(Message msg){    ... ...    /*這裡的處理,將不在主執行緒中執行,而在HandlerThread執行緒中執行,可以通過Thread.currentThread().getId()或者Thread.currentThread().getName()來確定*/    }};