android基礎知識02——安全執行緒2:handler、message、runnable

來源:互聯網
上載者:User

       

        android的UI操作不是安全執行緒的,同時也只有主線程才能夠操作UI,同時主線程對於UI操作有一定的時間限制(最長5秒)。為了能夠做一些比較耗時的操作(比如下載、開啟大檔案等),android提供了一些列機制。《android基礎知識02——安全執行緒》系列文章就是參考了網上許多網友的文章後,整理出來的一個系列,介紹了主要的方法。分別如下:

         android基礎知識02——安全執行緒1:定義及例子

        android基礎知識02——安全執行緒2:handler、message、runnable

        android基礎知識02——安全執行緒3:Message,MessageQueue,Handler,Looper

        android基礎知識02——安全執行緒4:HandlerThread

        android基礎知識02——安全執行緒5: AsyncTask

四、使用訊息佇列的實現        

         在上文中,我們提到在android中只有主線程才可以進行UI操作,我們例子中讓子線程操作UI結果出現了錯誤。

         為了使該程式得以運行,我們使用如下解決方案:

private EditText editText;    private Handler messageHandler;    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.main);        editText = (EditText) findViewById(R.id.weather_city_edit);        Button button = (Button) findViewById(R.id.goQuery);        button.setOnClickListener(this);        //得到當前線程 的Looper執行個體,由於 當前線程是UI線程也可以 通過Looper.getMainLooper()得到        Looper looper = Looper.myLooper();        //此處甚至可以 不需要設定Looper,因為 Handler預設就使用當 前線程的Looper        messageHandler = new MessageHandler(looper);    }

@Override    public void onClick(View v) {        //建立一個子線 程去做耗時的網路連接工作        new Thread() {            @Override            public void run() {                //活動使用者輸入 的城市名稱                String city = editText.getText().toString();                //調用Google 天氣API查詢指定城 市的當日天氣情況                String weather = getWetherByCity(city);                //建立一個Message對象,並把得 到的天氣資訊賦值給Message對象                Message message = Message.obtain();                message.obj = weather;                //通過Handler發布攜帶有天 氣情況的訊息                messageHandler.sendMessage(message);            }        }.start();    }

 //子類化一個Handler    class MessageHandler extends Handler {        public MessageHandler(Looper looper) {            super(looper);        }        @Override        public void handleMessage(Message msg) {            //處理收到的消 息,把天氣資訊顯示在title上            setTitle((String) msg.obj);        }    }

        以上程式使用訊息佇列來實現,通過訊息佇列改寫過後的天氣預報程式已經可以成功運行,因為Handler的handleMessage方法實 際是由關聯有該訊息佇列的UI thread調用,而在UI thread中更新title並沒有違背Android的單執行緒模式的原則。

五、訊息佇列

        在上文中使用到了android的訊息佇列。說到訊息佇列,我們可以想到一個流程:產生訊息——接收訊息(存入訊息佇列)——訊息佇列——處理訊息。這裡android的實現,涉及handler、messagequeue、looper等定義,下面來一一介紹:

1、handler

        主要接受子線程發送的資料, 並用此資料配合主線程更新UI.
        解釋: 當應用程式啟動時,Android首先會開啟一個主線程 (也就是UI線程) , 主線程為管理介面中的UI控制項,進行事件分發, 比如說, 你要是點擊一個 Button, Android會分發事件到Button上,來響應你的操作。  如果此時需要一個耗時的操作,例如: 連網讀取資料,或者讀取本地較大的一個檔案的時候,你不能把這些操作放在主線程中,如果你放在主線程中的話,介面會出現假死現象, 如果5秒鐘還沒有完成的話,會收到Android系統的一個錯誤提示  "強制關閉".  這個時候我們需要把這些耗時的操作,放在一個子線程中,因為子線程涉及到UI更新,Android主線程是線程不安全的,也就是說,更新UI只能在主線程中更新,子線程中操作是危險的.
這個時候,Handler就出現了來解決這個複雜的問題,由於Handler運行在主線程中(UI線程中),它與子線程可以通過Message對象來傳遞資料,這個時候,Handler就承擔著接受子線程傳過來的(子線程用sedMessage()方法傳弟)Message對象,(裡麵包含資料)  , 把這些訊息放入主線程隊列中,配合主線程進行更新UI。

       handler可以分發Message對象和Runnable對象到主線程中, 每個Handler執行個體,都會綁定到建立他的線程中(一般是位於主線程),
        它有兩個作用: (1):  安排訊息或Runnable 在某個主線程中某個地方執行, (2)安排一個動作在不同的線程中執行
      
        Handler中分發訊息的一些方法
        post(Runnable)
        postAtTime(Runnable,long)
        postDelayed(Runnable long)
        sendEmptyMessage(int)
        sendMessage(Message)
        sendMessageAtTime(Message,long)
        sendMessageDelayed(Message,long)
        以上post類方法允許你排列一個Runnable對象到主線程隊列中,
        sendMessage類方法, 允許你安排一個帶資料的Message對象到隊列中,等待更新.

1.1 message和runnable

        通過建立一個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()來確定*/     }};

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.