Android 進程和線程

這篇文章轉載自:http://www.oschina.net/question/195301_32205html

進程和線程java

若是某個應用程序組件是第一次被啓動,且這時應用程序也沒有其餘組件在運行,則Android系統會爲應用程序建立一個包含單個線程的linux進程。默認狀況下,同一個應用程序的全部組件都運行在同一個進程和線程裏(叫作「main」主線程)。若是組件啓動時,已經存在應用程序的進程了(由於應用程序的其它組件已經在運行了),則此組件會在已有的進程和線程中啓動運行。不過,能夠指定組件運行在其餘進程裏,也能夠爲任何進程建立額外的線程。linux

         本文討論進程和線程是如何在Android應用程序中發揮做用的。android

 

進程數據庫

默認狀況下,同一個應用程序內的全部組件都是運行在同一個進程中的,大部分應用程序也不會去改變它。不過,若是須要指定某個特定組件所屬的進程,則能夠利用manifest 文件來達到目的。編程

manifest文件中的每種組件元素——<activity>、 <service>、 <receiver><provider>——都支持定義android:process屬性,用於指定組件運行的進程。設置此屬性便可實現每一個組件在各自的進程中運行,或者某幾個組件共享一個進程而其它組件運行於獨立的進程。設置此屬性也可讓不一樣應用程序的組件運行在同一個進程中——實現多個應用程序共享同一個Linux用戶ID、賦予一樣的權限。緩存

<application>元素也支持android:process屬性,用於指定全部組件的默認進程。安全

若是內存不足,可又有其它爲用戶提供更緊急服務的進程須要更多內存,Android可能會決定關閉一個進程。在此進程中運行着的應用程序組件也會所以被銷燬。當須要再次工做時,會爲這些組件從新建立一個進程。網絡

在決定關閉哪一個進程的時候,Android系統會權衡它們相對用戶的重要程度。好比,相對於一個擁有可見activity的進程,更有可能去關閉一個activity已經在屏幕上看不見的進程。也就是說,是否終止一個進程,取決於運行在此進程中組件的狀態。終止進程的斷定規則將在後續內容中討論。多線程

 

進程的生命週期

Android系統試圖儘量長時間地保持應用程序進程,但爲了新建或者運行更加劇要的進程,老是須要清除過期進程來回收內存。爲了決定保留或終止哪一個進程,根據進程內運行的組件及這些組件的狀態,系統把每一個進程都劃入一個「重要性層次結構」中。重要性最低的進程首先會被清除,而後是下一個最低的,依此類推,這都是回收系統資源所必需的。

重要性層次結構共有5級,如下列表按照重要程度列出了各種進程(第一類進程是最重要的,將最後一個被終止):

1. 前臺進程

用戶當前操做所必須的進程。知足如下任一條件時,進程被視做處於前臺:

o     其中運行着正與用戶交互的Activity(Activity對象的 onResume() 方法已被調用)。

o     其中運行着被正與用戶交互的activity綁定的服務Service

o     其中運行着「前臺」服務Service——服務以startForeground()方式被調用。

o     其中運行着正在執行生命週期回調方法(onCreate()onStart()onDestroy())的服務Service

o     其中運行着正在執行onReceive()方法的BroadcastReceiver

通常而言,任什麼時候刻前臺進程都是爲數很少的,只有做爲最後的策略——當內存不足以維持它們同時運行時——纔會被終止。一般,設備這時候已經到了內存分頁狀態(memory paging state)的地步,終止一些前臺進程是爲了保證用戶界面的及時響應。

 

2. 可見進程

沒有前臺組件、但仍會影響用戶在屏幕上所見內容的進程。知足如下任一條件時,進程被認爲是可見的:

o     其中運行着不在前臺的Activity,但用戶仍然可見到此activity(onPause()方法被調用了)。好比如下場合就可能發生這種狀況:前臺activity打開了一個對話框,而以前的activity還容許顯示在後面。

o     其中運行着被可見(或前臺)activity綁定的服務Service

可見進程被認爲是很是重要的進程,除非沒法維持全部前臺進程同時運行了,它們是不會被終止的。

 

3. 服務進程

此進程運行着由startService()方法啓動的服務,它不會升級爲上述兩級別。儘管服務進程不直接和用戶所見內容關聯,但他們一般在執行一些用戶關心的操做(好比在後臺播放音樂或從網絡下載數據)。所以,除非內存不足以維持全部前臺、可見進程同時運行,系統會保持服務進程的運行。

 

4. 後臺進程

包含目前用戶不可見activity(Activity對象的onStop()方法已被調用)的進程。這些進程對用戶體驗沒有直接的影響,系統可能在任意時間終止它們,以回收內存供前臺進程、可見進程及服務進程使用。一般會有不少後臺進程在運行,因此它們被保存在一個LRU(最近最少使用)列表中,以確保最近被用戶使用的activity最後一個被終止。若是一個activity正確實現了生命週期方法,並保存了當前的狀態,則終止此類進程不會對用戶體驗產生可見的影響。由於在用戶返回時,activity會恢復全部可見的狀態。關於保存和恢復狀態的詳細信息,請參閱Activities文檔。

 

5. 空進程

不含任何活動應用程序組件的進程。保留這種進程的惟一目的就是用做緩存,以改善下次在此進程中運行組件的啓動時間。爲了在進程緩存和內核緩存間平衡系統總體資源,系統常常會終止這種進程。

 

依據進程中目前活躍組件的重要程度,Android會給進程評估一個儘量高的級別。例如:若是一個進程中運行着一個服務和一個用戶可見的activity,則此進程會被評定爲可見進程,而不是服務進程。

此外,一個進程的級別可能會因爲其它進程的依賴而被提升——爲其它進程提供服務的進程級別永遠不會低於使用此服務的進程。好比:若是A進程中的content provider爲進程B中的客戶端提供服務,或進程A中的服務被進程B中的組件所調用,則A進程至少被視爲與進程B一樣重要。

由於運行服務的進程級別是高於後臺activity進程的,因此,若是activity須要啓動一個長時間運行的操做,則爲其啓動一個服務service會比簡單地建立一個工做線程更好些——尤爲是在此操做時間比activity自己存在時間還要長久的狀況下。好比,一個activity要把圖片上傳至Web網站,就應該建立一個服務來執行之,即便用戶離開了此activity,上傳仍是會在後臺繼續運行。不論activity發生什麼狀況,使用服務能夠保證操做至少擁有「服務進程」的優先級。同理,上一篇中的廣播接收器broadcast receiver也是使用服務而非線程來處理耗時任務的。

 

 

線程

應用程序啓動時,系統會爲它建立一個名爲「main」的主線程。主線程很是重要,由於它負責把事件分發給相應的用戶界面widget——包括屏幕繪圖事件。它也是應用程序與Android UI組件包(來自android.widgetandroid.view包)進行交互的線程。所以,主線程有時也被叫作UI線程。

系統並不會爲每一個組件的實例都建立單獨的線程。運行於同一個進程中的全部組件都是在UI線程中實例化的,對每一個組件的系統調用也都是由UI線程分發的。所以,對系統回調進行響應的方法(好比報告用戶操做的onKeyDown()或生命週期回調方法)老是運行在UI線程中。

舉個例子,當用戶觸摸屏幕上的按鈕時,應用程序的UI線程把觸摸事件分發給widget,widget先把本身置爲按下狀態,再發送一個顯示區域已失效(invalidate)的請求到事件隊列中。UI線程從隊列中取出此請求,並通知widget重繪本身。

若是應用程序在與用戶交互的同時須要執行繁重的任務,單線程模式可能會致使運行性能很低下,除非應用程序的執行時機恰好很合適。若是UI線程須要處理每一件事情,那些耗時很長的操做——諸如訪問網絡或查詢數據庫等——將會阻塞整個UI(線程)。一旦線程被阻塞,全部事件都不能被分發,包括屏幕繪圖事件。從用戶的角度看來,應用程序看上去像是掛起了。更糟糕的是,若是UI線程被阻塞超過必定時間(目前大約是5秒鐘),用戶就會被提示那個可惡的「應用程序沒有響應(ANR)對話框。若是引發用戶不滿,他可能就會決定退出並刪除這個應用程序。

此外,Andoid的UI組件包並不是線程安全的。所以不容許從工做線程中操做UI——只能從UI線程中操做用戶界面。因而,Andoid的單線程模式必須遵照兩個規則:

1.         不要阻塞UI線程。

2.         不要在UI線程以外訪問Andoid的UI組件包。

 

工做線程

根據對以上單線程模式的描述,要想保證程序界面的響應能力,關鍵是不能阻塞UI線程。若是操做不能很快完成,應該讓它們在單獨的線程中運行(「後臺」或「工做」線程)。

例如:如下響應鼠標點擊的代碼實現了在單獨線程中下載圖片並在ImageView顯示:

public void onClick(View v) { 

    new Thread(new Runnable() { 

        public void run() { 

            Bitmap b = loadImageFromNetwork("http://example.com/image.png"); 

            mImageView.setImageBitmap(b); 

        } 

    }).start(); 

}

乍看起來,這段代碼彷佛能運行得很好,由於建立了一個新的線程來處理訪問網絡的操做。但是它違反了單線程模式的第二條規則:不要在UI線程以外訪問Andoid的UI組件包——以上例子在工做線程裏而不是UI線程裏修改了ImageView。這可能致使不明確、不可預見的後果,要跟蹤這種狀況也是很困難很耗時間的。

爲了解決以上問題,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線程裏操縱的。

不過,隨着操做變得愈來愈複雜,這類代碼也會變得很複雜很難維護。爲了用工做線程完成更加複雜的交互處理,能夠考慮在工做線程中用Handler來處理UI線程分發過來的消息。固然,最好的解決方案也許就是繼承使用異步任務類AsyncTask,此類簡化了一些工做線程和UI交互的操做。

 

使用異步任務

異步任務AsyncTask 容許以異步的方式對用戶界面進行操做。它先阻塞工做線程,再在UI線程中呈現結果,在此過程當中不須要對線程和handler進行人工干預。

要使用異步任務,必須繼承AsyncTask類並實現doInBackground()回調方法,該對象將運行於一個後臺線程池中。要更新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 system calls this to perform work in a worker thread and 

      * delivers it the parameters given to AsyncTask.execute() */ 

    protected Bitmap doInBackground(String... urls) { 

        return loadImageFromNetwork(urls[]); 

    } 

     

    /** The system calls this to perform work in the UI thread and delivers 

      * the result from doInBackground() */ 

    protected void onPostExecute(Bitmap result) { 

        mImageView.setImageBitmap(result); 

    } 

}

如今UI是安全的,代碼也獲得簡化,由於任務分解成了工做線程內完成的部分和UI線程內完成的部分。

要全面理解這個類的使用,須閱讀AsyncTask的參考文檔。如下是關於其工做方式的概述:

·       能夠用generics來指定參數、進度值和任務最終值的類型。

·       工做線程中的doInBackground()方法會自動執行。

·       onPreExecute()onPostExecute()onProgressUpdate()方法都在UI線程中調用。

·       doInBackground()的返回值會傳給onPostExecute()

·       在doInBackground()內的任什麼時候刻,均可以調用publishProgress()來執行UI線程中的onProgressUpdate()

·       能夠在任什麼時候刻、任何線程內取消任務。

注意:在使用工做線程時,可能遇到的另外一個問題是因爲運行配置的改變(好比用戶改變了屏幕方向)致使activity意外重啓,這可能會銷燬該工做線程。要了解如何在這種狀況下維持任務執行、以及如何在activity被銷燬時正確地取消任務,請參見Shelves例程的源代碼。

 

線程安全的方法

在某些場合,方法可能會從不止一個線程中被調用,所以這些方法必須是寫成線程安全的。

對於能被遠程調用的方法——好比綁定服務(bound service)中的方法,這是理所固然的。若是對IBinder所實現方法的調用發起於IBinder所在進程的內部,那麼這個方法是執行在調用者的線程中的。可是,若是調用發起於其餘進程,那麼這個方法將運行於線程池中選出的某個線程中(而不是運行於進程的UI線程中),該線程池由系統維護且位於IBinder所在的進程中。例如,即便一個服務的onBind()方法是從服務所在進程的UI線程中調用的,實現了onBind()的方法對象(好比,實現了RPC方法的一個子類)仍會從線程池中的線程被調用。由於一個服務能夠有不止一個客戶端,因此同時能夠有多個線程池與同一個IBinder方法相關聯。所以IBinder方法必須實現爲線程安全的。

相似地,內容提供者(content provider)也能接收來自其它進程的數據請求。儘管ContentResolver類、ContentProvider類隱藏了進程間通信管理的細節,ContentProvider中響應請求的方法——query()insert()delete()update()getType()方法——是從ContentProvider所在進程的線程池中調用的,而不是進程的UI線程。由於這些方法可能會從不少線程同時調用,它們也必須實現爲線程安全的。

 

 

進程間通信

Android利用遠程過程調用(remote procedure call,RPC)提供了一種進程間通訊(IPC)機制,經過這種機制,被activity或其餘應用程序組件調用的方法將(在其餘進程中)被遠程執行,而全部的結果將被返回給調用者。這就要求把方法調用及其數據分解到操做系統能夠理解的程度,並將其從本地的進程和地址空間傳輸至遠程的進程和地址空間,而後在遠程進程中從新組裝並執行這個調用。執行後的返回值將被反向傳輸回來。Android提供了執行IPC事務所需的所有代碼,所以只要把注意力放在定義和實現RPC編程接口上便可。

要執行IPC,應用程序必須用bindService()綁定到服務上。詳情請參閱服務Services開發指南。

相關文章
相關標籤/搜索