Android多線程通信機制



掌握Android的多線程通信機制,我們首先應該掌握Android中進程與線程是什么。 ###1. 進程 在Android中,一個應用程序就是一個獨立的進程(應用運行在一個獨立的環境中,可以避免其他應用程序/進程的干擾)。一般來說,當我們啟動一個應用程序時,系統會創建一個進程(從Zygote中fork出來的,這個進程會有獨立的ID),並為這個進程創建一個主線程(UI線程),然后就可以運行MainActivity了,應用程序的組件默認都是運行在它的進程中,但我們可以通過指定應用的組件(四大組件)的運行進程:`android:process`來讓組件運行在不同的進程中;讓組件運行在不同的進程中,會帶來好處,也會帶來壞處:
  • 好處是:因為每一個應用程序(也就是每一個進程)都會有一個內存預算,所有運行在這個這個進程中的程序使用的總內存不能超過這個值,讓組件運行不同的進程中,可以讓主進程可以擁有更多的空間資源。

  • 壞處是:每個進程都會有自己的虛擬機實例,因此讓在進程間共享一些數據變得困難;(當然,我們可以采用多進程間的通信來實現數據的共享)

    當我們的應用程序比較大,需要的內存資源比較多時(也就是用戶會抱怨應用經常出現OutOfMemory時),可以考慮使用多進程

2. 線程

在Java中,線程會有那么幾種狀態:創建,就緒,運行,阻塞,死亡。當應用程序有組件在運行時,UI線程是處於運行狀態的。默認情況下,應用的所有組件的操作都是在UI線程里完成的,包括響應用戶的操作(觸摸,點擊等),組件生命周期方法的調用,UI的更新等。因此如果UI線程處理阻塞狀態時(在線程里做一些耗時的操作,如網絡連接等),就會不能響應各種操作,如果阻塞時間達到5秒,就會讓程序處於ANR(application not response)狀態,這時用戶就可能退出你的應用甚至卸載你的應用,這是我們不能接受的。 這時,有人就會說,我們在其他線程中更新UI不就可以了嗎,就不會導致UI線程處於阻塞狀態了。但答案是否定的。

因為Android的UI線程是非線程安全的,應用更新UI,是調用invalidate()方法來實現界面的重繪,而invalidate()方法是非線程安全的,也就是說當我們在非UI線程來更新UI時,可能會有其他的線程或UI線程也在更新UI,這就會導致界面更新的不同步。因此我們不能在非UI主線程中做更新UI的操作。也就是說我們在使用Android中的線程時,要保證:
  • 不能阻塞UI主線程,也就是不能在UI主線程中做耗時的操作,如網絡連接,文件的IO;

  • 只能在UI主線程中做更新UI的操作;

    在Android中,我們把除UI線程外的,其他所有的線程都叫做工作線程,也就是說Android只會存在兩種線程:UI主線程(UI thread)和工作線程(work thread).我們不能在UI主線程中做耗時的操作,因此我們可以把耗時的操作放在另一個工作線程中去做。操作完成后,再通知UI主線程做出相應的響應。這就需要掌握線程間通信的方式了。在Android中提供了兩種線程間的通信方式:一種是AsyncTask機制,另一種是Handler機制;

2.1 Android線程間通信方式之AsyncTask機制

AsyncTask,異步任務,也就是說在UI線程運行的時候,可以在后台的執行一些異步的操作;AsyncTask可以很容易且正確地使用UI線程,AsyncTask允許進行后台操作,並在不顯示使用工作線程或Handler機制的情況下,將結果反饋給UI線程。但是AsyncTask只能用於短時間的操作(最多幾秒就應該結束的操作),如果需要長時間運行在后台,就不適合使用AsyncTask了,只能去使用Java提供的其他API來實現。
2.1.1 AsyncTask的使用

AsyncTask只能通過繼承來使用,如:

 private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {   //處理異步任務的方法,是在工作線程中執行的,必須實現這個方法
     protected Long doInBackground(URL... urls) {
         int count = urls.length;
         long totalSize = 0;
         for (int i = 0; i < count; i++) {
             totalSize += Downloader.downloadFile(urls[i]);
             publishProgress((int) ((i / (float) count) * 100));
             // Escape early if cancel() is called
             if (isCancelled()) break;
         }
         return totalSize;
     }
    //更新后台任務的完成進度,可隨時向UI線程反饋執行進度,方法是在UI線程中執行的
     protected void onProgressUpdate(Integer... progress) {
         setProgressPercent(progress[0]);
     }
    //任務的最終結果,這個方法是在UI線程中執行的,
     protected void onPostExecute(Long result) {
         showDialog("Downloaded " + result + " bytes");
     }
 }

AsyncTask的幾個參數,AsyncTask <Params, Progress, Result>

  • Params..要執行的任務的參數類型;
  • Progress,在后台執行的任務的進度;
  • Results,后台執行的任務的最后結果;

當一個AsyncTask任務執行時,它會經歷四個步驟

1. onPreExecute(),在任務執行前調用,用來做一些UI的初始化工作,在UI線程中執行,如執行一個AsyncTask的下載任務時,初始化類似“正在下載中”的窗口;
2. doInBackground(Params...),在后台執行任務,是在工作線程中進行的;
3. onProgressUpdate(Progress...),更新正在后台執行任務的進度,在UI線程中工作,可用來向通知用戶任務現在的完成進度(可更新UI),在調用這個方法前,需要在第2個步驟里調用 publishProgress(Progress...)將進度傳遞給這個方法;
4. onPostExecute(Result),在后台任務完成后,會將結果返回給這個方法,在UI線程中調用,可以更新UI;

在使用AsyncTask的時候,需要注意的地方

  • AsyncTask類必須在UI線程中加載,這在在Android Jelly_Bean版本后這些都是自動完成的;
  • AsyncTask類必須在UI線程中實例化;
  • 不要手動去調用 onPreExecute(),doInBackground(Params...),publishProgress(Progress...),onPostExecute(Result)方法,這些方法都會由系統自動去調用。
  • execute(Params...)方法必須在UIt線程中調用,如在UI線程中執行這個語句:new DownloadFilesTask().execute(url1, url2, url3);我們不需要做其他的工作,這個下載任務就會自動在后台執行了,並且AsynacTask任務只能執行一次;

2.2 Android線程間通信方式之Handler機制;

 使用Handler機制,也就是通過使用Handler,Looper,MessageQueue,和Message這幾個類協調來完成。我們先來看使用Handler機制完成線程間通信的原理,然后再詳細介紹這幾個類;先看一下這幾個類的作用:
  • Handler,發送和處理Message對象和Runnable對象;

  • Looper,用於從MessageQueue中取出消息(Message)對象,並發送給Handler處理;

  • MessageQueue,消息隊列,用於存放通過Handler發布的消息;

  • Message,消息的類型,里面包含幾個實例對象:

  • what,用戶定義的int型消息代碼,用來描述消息;

  • obj,隨消息發送的用戶用戶指定對象;

  • target,處理消息的Handler;

    一個Handler對象僅與一個Looper相關聯,一個Message也僅與一個目標Handler對象相關聯,一個Looper對象擁有一個MessageQueue。但多個不同的Handler對象可以與同一個對象相關聯,也就是說多個Handler可以共享一個MessageQueue,從而達到消息共享的目的,這也是Android通過Handler機制實現多線程間通信的核心原理;

    上面說到Handler對象僅與一個Looper相關聯,那么這個關聯是什么時候實現的呢?答案是:Handler對象創建的時候。UI主線程在創建的時候就會擁有一個handler對象和一個Looper對象(工作線程需要自己調用Looper.prepare()來創建一個Looper對象),然后任何在UI主線程中創建的Handler對象默認都會與UI主線程的Looper對象相關聯(Handler對象創建的時候,會與這個線程的Looper對象相關聯)。進而我們就可以把在UI主線程中創建的Handler對象傳遞給(依賴注入或引用形式)工作線程,那么在工作線程中用這個Handler對象處理的消息就是在UI主線程的MessageQueue中處理的,從而達到線程間通信的目的。

了解了使用Handler機制來實現Android線程間異步通信的原理,下面我們再來詳細了解下這四個核心類;

2.2.1 Handler
Handler,繼承自Object類,用來發送和處理Message對象或Runnable對象;Handler在創建時會與當前所在的線程的Looper對象相關聯(如果當前線程的Looper為空或不存在,則會拋出異常,此時需要在線程中主動調用Looper.prepare()來創建一個Looper對象)。使用Handler的主要作用就是在后面的過程中發送和處理Message對象和讓其他的線程完成某一個動作(如在工作線程中通過Handler對象發送一個Message對象,讓UI線程進行UI的更新,然后UI線程就會在MessageQueue中得到這個Message對象(取出Message對象是由其相關聯的Looper對象完成的),並作出相應的響應)。

Handler用post體系來完成發送Runnable對象的工作,用sendMessage體系 來完成發送Message對象的工作;

  • post體系,允許把一個Runnable對象發送到消息隊列中,它的方法有:post(Runnable),postDelayed(Runnable,long),postAtTime(Runnable,long)

  • sendMessage體系,把Message對象發送給消息隊列,它的方法有:sendEmptyMessage(int),sendMessage(Message),sendMessageDelayed(Message,long),sendMessageAtTime(Message,long);

    如果Handler是通過post體系將Runnable對象發送到MessageQueue隊列中,則這個Runnable對象的run()方法是運行在Handler對象創建時所在線程;
    如果Handler是通過sendMessage體系將Message發送到MessageQueue中,則需要重寫handleMessage()方法來獲取工作線程傳遞過來的Message對象,handleMessage()方法是工作在Handler對象建立時所在的線程的(一般我們會在UI線程中建立Handler對象,然后傳遞給子線程,此時這個Handler對象的handleMessage()方法就是運行在UI主線程中的);

2.2.2 Message
Message用來定義一個包含任意數據的消息對象,這個消息對象是可以被發送給Handler處理的。我們最好通過Message.obtain()和Handler.obtatinMessage()來得到一個Message對象(通過這兩個方法得到的對象是從對象回收池中得到,也就是說是復用已經處理完的Message對象,而不是重新生成一個新對象),如果通過Message的構造方法得到一個Message對象,則這個Message對象是重新生成的(不建議使用這種方法)。

Message對象用來封裝需要傳遞的消息,Message的數據結構為:

Message{
int arg1;//如果我們只需要存儲一些簡單的Integer數據,則可通過設置這個屬性來傳遞
int agr2;//使用同arg1
Object obj; //設置需要發送給接收方的對象,這個對象需要實現序列化接口
int what; //描述這個消息的標識;
//設置與這個消息對應的任意數據,這個數據是用Bundle封裝的;
void setData(Bundle data);
Bundle getData(); 得到與這個消息對應的數據信息;
//省略了方法和可選的屬性
......

如果需要通過Message對象傳遞一些比較復雜的數據,則需要使用將數據封裝成Bundle對象,然后通過setData(Bundle)方法來傳遞,用getData()來得到與這個消息對應的數據(這方法與設置Message的Object 屬性作用相同);

2.2.3 MessageQueue

MessageQueue保存由Looper調度的消息列表,消息通過與Looper相關聯的Handler對象添加進MessageQueue。

2.2.4 Looper
Looper為線程運行一個消息的循環隊列,主要就是為了完成MessageQueue與Handler交互的功能;需要注意的是線程默認並不會給我們提供一個一個Looper實例來管理消息隊列,我們需要在線程中主動調用Looper.prepare()方法來實例化一個Looper對象,用於管理消息隊列;Looper對象會不斷去判斷MessageQueue是否為空,如果不空,則將Message取出給相應的Handler進行處理;如果MessageQueue為空,則Looper對象會進行阻塞狀態,直到有新的消息進入MessageQueue;

其實,說白了,Android中通過Handler機制來異步處理多線程間的通信就是多個線程間共享一個MessageQueue,工作線程將消息發送到MessageQueue,然后UI線程或其他工作線程在MessageQueue在取出消息,進行相應的處理;

參考:
http://www.cnblogs.com/plokmju/p/android_handler.html
Google官方文檔


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM