Android的進程與線程(3)安全執行緒問題

來源:互聯網
上載者:User

當一個程式啟動的時候,系統會為程式建立一個名為main的線程。這個線程重要性在於它負責把事件分發給適合的使用者組件,這些事件包括繪製事件。並且這個線程也是你的程式與Android UI工具包中的組件(比如android.widget和android.view包中的組件)進行互動的線程。正因為如此,這個main線程有時也被稱為UI線程。

系統並不會為組件的每個執行個體都建立一個單獨的線程。運行在同一個進程中的所有組件都是在UI線程中執行個體化的,並且系統對這些組件的調用都是由UI分發的。所以,對系統的回調做出回應的方法都是運行在進程中的UI線程中的(比如,用於報告使用者操作的onKeyDown()方法,或者生命週期回調方法)。

比如,當使用者按下了螢幕上的一個按鈕,你程式的UI線程就會將這個觸屏事件分發給這個button組件,然後這個組件會依次設定它的按下狀態,並向事件隊列發送一個無效請求(post an invalidate request to the event queue)。然後UI線程將這個請求彈出隊列,並通知這個組件使其重繪。

當你的程式在為了響應使用者事件,而頻繁的執行操作的時候,除非你能很恰當的實現你的程式,否則的話,可能會給使用者帶來不好的體驗。特別是,如果讓程式中所有的工作都在主線程中完成的時候,像訪問互連網或者資料庫這些需要長時間的操作將會堵塞整個UI。當UI線程被阻塞的時候,所有的事件都不能被分發,包括繪製事件。從使用者的角度來講,這時這個程式就像終止了一樣。更糟糕的是,如果UI線程被堵塞超過5秒鐘的話,系統將會彈出一個“application not responing”(ANR)對話方塊。然後使用者可能就會決定放棄你的這個程式甚至選擇卸載。

另外,Android的UI toolkit不是安全執行緒的。所以,你不能在你的其他線程中處理UI,你的所有的UI方面的工作都應該在UI線程來完成。這裡,有兩條關於Android單執行緒模式的規則:

1.不要堵塞UI線程

2.不要在UI線程之外訪問Android UI toolkit

worker thread

正是因為上面討論的這種單執行緒模式,為了程式的響應速度,你是絕不能堵塞UI線程的。如果你要做的工作不是瞬時就能完成的,那麼你就應該在新的線程("background"或者“worker”線程)中處理它們。

比如,下面是關於單擊一個監聽器來在一個單獨的線程中下載一張圖片,並使用ImageView顯示該圖片的代碼:

public void onClick(View v){new Thread(new Runnable(){Bitma b = loadImageFromNetwork("http://examble.com/image.png");mImageView.setImageBitmap(b);}).start();}

乍看起來的話,這段代碼好像是沒問題的,它建立一個新的線程來處理連網操作。然而它違反了上面的第二條規則:不能在UI線程之外訪問Android Ui tookit,因為這個例子中在新的worker線程中修改了ImageView,而不是在UI線程中。這樣做將會導致未定義或者意料不到的行為發生,這對於發現這個錯誤來說,將是很難很耗時的。

為瞭解決這個問題,Android提供如下的方式,來實現從其他線程中訪問UI線程。下面是具體方法:

Activity.runOnUiThread(Runnable)View.post(Runnable)View.postDelayed(Runnable, long)

比如,你可以使用View.post(Runnable)來修複上面的代碼:

public void onClick(View v){new Thread(new Runnable(){public void run(){final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");mImageView.post(new Runnable(){public void run(){mImageView.setImageBitmap(bitmap);}});}}).start();}

現在這種實現方式,就是安全執行緒的了:連網的操作在一個單獨線程中完成,而ImageView的處理是在UI線程中完成的。

然而,當隨著操作的複雜性提高的時候,上面的代碼可能會變得複雜,並且不容易維護。為了使用worker線程處理更複雜的操作,你可以考慮在你的worker線程使用Handler來處理從UI線程中分發過來的訊息。或許最好的方式,是通過繼承AsyncTask這個類,這簡化了worker線程需要同UI進行互動的過程。下面介紹怎樣使用AsyncTask。

AsyncTask允許你對於UI執行非同步的操作。它在worker線程中執行耗時的操作,然後在UI線程中更新結果,在這個過程中不需要你去處理線程或者handlers。

為了使用AsyncTask,你必須繼承AsyncTask並實現doInBackground回調方法,這些回調方法運行在後台線程的池中(run in a pool of background threads)。為了更新你的UI,你應該實現onPostExecute()方法,這個方法將doInBackground()方法中的處理結果傳遞到UI線程中,這樣,你就可以安全的更新你的UI了。然後你就可以在UI線程中調用execute()方法來實現這整個過程了。

比如,你可以通過使用AsyncTask來修改上面的代碼:

public void onClick(View v){new DownloadImageTask().execute("http://example.com/image.png");}private class DownloadImageTask extends AsyncTask<String, Void, Bitmap>{/**The sytem calls this to perform work in worker thread and delivers it the parameters given to AsyncTask.execute()*/procted Bitmap doInBackground(String... urls){return loadImageFromNetwork(urls[0]);}/**系統調用該方法來在更新UI線程,並將doInbackground()的結果返回出來*/protected void onPostExecute(Bitmap result){mImageView.setImageBitmap(result);}}

現在這個Ui是安全的,並且代碼更簡單。下面是對AsyncTask的一個簡單介紹:

-你可以通過使用泛型,來指定參數的類型,進度值(the progress values)和the final value of the task。

-方法doInBackground()將自動在worker線程中執行

-onPreExecute(),onPostExecute()和onProgressUpdate()方法都是在UI線程中觸發的

-doInBackground()方法的結果將返回到onPostExecute()方法中

-在doInbackground()方法中,你可以在任意的時間通過調用publishprogress()來在主線程中執行onProgressUpdate()

-你可以從任何線程中取消當前的task

這裡有一點需要注意的是當使用worker線程時,可能會因為裝置運行時配置發生了改變(比如螢幕翻轉),而導致worker線程重啟。


參考文檔:http://developer.android.com/guide/topics/fundamentals/processes-and-threads.html

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.