首頁技術(shù)文章正文

Android+物聯(lián)網(wǎng)培訓之進程和線程詳解

更新時間:2017-07-02 來源:黑馬程序員Android+物聯(lián)網(wǎng)培訓 瀏覽量:

 
Android 進程和線程詳解
 
   當啟動一個應用程序組件時,如果該應用沒有正在運行的其它程序組件,那么Android系統(tǒng)將為這個應用創(chuàng)建一個新進程(包含一個線程)用于運行應用。缺省情況下,一個應用的所有組件(Activity,Service等)運行在同一個進程和線程中(稱為“主”線程)。如果在啟動一個應用程序組件時,這個應用已經(jīng)有進程在運行(因為有應用的其它組件存在),那么這個應用程序組件將使用同一進程和線程運行。當然你可以使用不同進程來運行不同的組件,或者在進程中創(chuàng)建新的線程。
 
進程
缺省情況,應用的所有組件都運行在同一個進程,而且應用不應該改變這個傳統(tǒng)。然而,如果你發(fā)現(xiàn)你需要控制某個組件運行在那個進程中,你可以通過應用程序清單來配置。
 
在應用程序清單文件中,每個類型的應用程序組件-<activity>,<service>,<receiver>和<provider>都支持 android:process 屬性,這個屬性用來指明該程序組件運行的進程。你可以為應用程序組件設置這個屬性以使每個組件運行在不同的進程中或者某幾個組件使用同一進程。你也可以通過設置android:process 使得不同應用中的組件運行在同一個進程中-前提是這些應用使用同一個Linux用戶名并且使用同一個證書簽名。
 
<Application>元素也支持 android: process 屬性,用來為應用程序的所有組件設置缺省的進程。
 
Android系統(tǒng)中系統(tǒng)資源過低而且有需要為用戶立即提供服務的進程需要啟動時可能會終止某些進程的運行。運行在這些被終止的進程中的程序組件將逐個被銷毀。此后如果還有工作需要這些應用程序組件時將啟動新的進程。
 
系統(tǒng)中決定哪些進程可以殺死時,系統(tǒng)將權(quán)衡這些進程對用戶的重要性。比如,對于那些運行不可見的Activity的進程比運行屏幕上可見的Activity的進程更容易被殺死。
 
進程生命周期
 
Android系統(tǒng)會盡可能長的保持應用程序進程的運行,但總會有需要清除舊的進程來釋放資源以滿足新或是重要的進程的運行。為了決定哪些進程可以殺死,哪些進程需要保留,系統(tǒng)根據(jù)運行在其中的應用程序組件和這些組件的狀態(tài),將這些進程分配到“重要性層次表”中。具有最低重要性的進程首先被殺死,次重要性的進程為其次等等直到系統(tǒng)恢復所需的資源。
 
“重要性層次表”可以分為五個層次,下面列表給出了不同類型的進程的重要性等級(最重要的排在前面):
 
1.前臺進程
 
這種進程是當前用戶所需要的。一個進程被認為是前臺進程需滿足下面條件之一:
 
 
    ·        本進程中有Activity是當前和用戶有交互的Activity(該Activity的onResume()已調(diào)用)。
 
    ·        本進程中有Service和當前用戶有交互Activity的綁定。
 
    ·        本進程中有在前臺運行的Service—該Service調(diào)用過startForeground()。
 
    ·        本進程中有Service正在執(zhí)行某個生命周期回調(diào)函數(shù)(onCreate(),onStart()或onDestroy())。
 
    ·        本進程中的某個BroadcastReceiver正在執(zhí)行onReceive()方法。
 
2.可視進程
 
這種進程雖然不含有任何在前臺運行的組件,但會影響當前顯示給用戶屏幕上的內(nèi)容,一個進程中滿足下面兩個條件之一時被認為是個可見進程:
 
 
    ·        本進程含有一個雖然不在前臺但卻部分可見的Activity(該Activity的onPause()被調(diào)用)??赡馨l(fā)生的情形是前臺Activity顯示一對話框,此時之前的Activity變?yōu)椴糠挚梢姟?br />  
    ·        本進程含有綁定到可見Activity的Service。
 
3. 服務進程
 
該進程運行了某個使用startService()啟動的Service,但不屬于以上兩種情況。盡管此服務進程不直接和用戶可以看到的任何部分有關(guān)聯(lián),但它會運行一些用戶關(guān)心的事情(比如在后臺播放音樂或者通過網(wǎng)絡下載文件)。因此Android系統(tǒng)會盡量讓它們運行直到系統(tǒng)資源低到無法滿足前臺和可見進程的運行。
 
 4.后臺進程
 
    該進程運行一些目前用戶不可見的Activity(該Activity的onStop()已被調(diào)用),該進程對用戶體驗無直接的影響,系統(tǒng)中資源低時為保證前臺,可見或服務進程運行時可以隨時殺死該進程。通常系統(tǒng)中有很多進程在后臺運行,這些進程保存在LRU(最近使用過)列表中以保證用戶最后看到的進程最后被殺死。如果一個Activity正確實現(xiàn)了它的生命周期函數(shù),并保存了它的狀態(tài)。殺死運行該Activity的進程對用戶來說在視覺上不會有什么影響,這是因為之后用戶回到該Activity時,該Activity能夠正確恢復之前屏幕上的狀態(tài)。
 
 5.空進程
 
該進程不運行任何活動的應用程序組件。保持這種進程運行的唯一原因是由于緩存,以縮短下次運行某個程序組件時的啟動時間。系統(tǒng)會為了進程緩存和內(nèi)核緩存之間的平衡經(jīng)常會清除空進程。
 
Android系統(tǒng)會根據(jù)進程中當前活動的程序組件的重要性,近可能高的給該進程評級。比如,如果一個進程中同時有一個Service和一個可見的Activity在運行,該進程將被定級為可見進程而不是服務進程(可見進程的優(yōu)先級高于服務進程)。
 
此外,一個進程的級別可能有對其有依賴的其它進程提升—一個給其它進程提供服務的進程的級別不會低于它所服務的進程的級別。比如,進程A中的Content Provider 給進程B中某客戶端提供數(shù)據(jù)服務或者進程A中某個服務被進程B某組件所綁定。那么進程A重要性程度不會低于進程B。
 
由于運行Service的進程的級別高于運行后臺Activity的進程的級別,一個需要較長時間運行操作的Activity 啟動能夠完成該操作的Service可能也能很好的完成任務而無需簡單創(chuàng)建一個新工作線程—尤其是該操作運行時間比該Activity還要長。比如,如果一個Activity需要完成向服務器上傳圖片任務時應該使用一個服務來完成上載任務,這些即使用戶離開該Activity,Service依然可以在后臺完成上載任務。使用Service可以保證某個操作至少具有“服務進程”的優(yōu)先級而無需關(guān)心該Activity發(fā)生了什么變化。這也是一個Broadcast Receiver應該使用一個Service而非一線程來完成某個耗時的任務。
 
線程
 
Android系統(tǒng)啟動某個應用后,將會創(chuàng)建一個線程來運行該應用,這個線程成為“主”線程。主線程非常重要,這是因為它要負責消息的分發(fā),給界面上相應的UI組件分發(fā)事件,包括繪圖事件。這也是應用可以和UI組件(為android.widget和android.view中定義的組件)發(fā)生直接交互的線程。因此主線程也通常稱為用戶界面線程(UI線程)。
 
Android系統(tǒng)不會主動為應用程序的組件創(chuàng)建額外的線程。運用在同一進程中所有程序組件都在UI線程中初始化,并使用UI線程來分發(fā)對這些程序組件的系統(tǒng)調(diào)用。由此可見,響應系統(tǒng)回調(diào)函數(shù)(比如onKeyDown() 響應用戶按鍵或者某個生命周期回調(diào)函數(shù))的方法總是使用UI線程來運行。
 
比如,當用戶觸摸屏幕上某個按鈕時,你的應用中的UI線程將把這個觸摸事件發(fā)送到對應的UI小組件,然后該UI小組件設置其按下的狀態(tài)并給事件隊列發(fā)送一個刷新的請求,之后UI線程處理事件隊列并通知該UI小組件重新繪制自身。
 
當你的應用中響應用戶事件時需要完成一些費事的工作時,這種單線程工作模式可能會導致非常差的用戶響應性能。尤其是如果所有的工作都在UI線程中完成,比如訪問網(wǎng)絡,數(shù)據(jù)庫查詢等費時的工作將會阻塞UI線程。當UI線程被阻塞時,就無法分發(fā)事件,包括繪圖事件。此時從用戶的角度來看,該應用看起來不再有響應。更為糟糕的是,如果UI線程阻塞超過幾秒鐘(目前為五秒),系統(tǒng)將給用戶顯示著名的“應用程序無響應”(ANR)對話框。用戶可能會選擇退出你的應用,更為甚者如果他們感覺很不滿意也會選擇卸載你的應用。
 
此外,Android的UI組件包不是“線程安全”的,因此你不能走工作線程中調(diào)用UI組件的方法,所有有關(guān)UI的操作必須在UI線程中完成,因此下面為使用UI單線程工作線程的兩個規(guī)則:
 
1.       永遠不要阻塞UI線程。
 
2.       不要在非UI線程中操作UI組件。
 
工作線程
 
由于Android使用單線程工作模式,因此不阻塞UI線程對于應用程序的響應性能至關(guān)重要。如果在你的應用中包含一些不是一瞬間就能完成的操作的話,你應用使用額外的線程(工作線程或是后臺線程)來執(zhí)行這些操作。
 
 
比如下面示例,在用戶點擊某個按鈕后,就啟動一個新線程來下載某個圖像然后在ImageView中顯示:
 
[java] view plaincopyprint?
 
 
    public void onClick(View v) { 
 
        new Thread(new Runnable() { 
 
            public void run() { 
 
                Bitmap b = loadImageFromNetwork("http://example.com/image.png"); 
 
                mImageView.setImageBitmap(b); 
 
            } 
 
     
 
            private Bitmap loadImageFromNetwork(String string) { 
 
                // TODO Auto-generated method stub 
 
                return null; 
 
            } 
 
        }).start(); 
 
    } 
 
    乍一看,這段代碼應該很好的完成工作,因為它創(chuàng)建了一個新線程來完成網(wǎng)絡操作。然而它違法了上面說的第二個規(guī)則:不要在非UI線程中操作UI組件。在這段代碼中的工作線程中而不是在UI線程中,直接修改ImageView,這將導致一些不可以預見的后果,常常導致發(fā)現(xiàn)此類錯誤捕捉異常困難和費時。
 
 
為了更正此類錯誤,Android提供了多種方法使得在非UI線程中訪問UI組件,下面給出了其中的幾種方法:
 
 
    ·        Activity.runOnUiThread (Runnable) 方法
 
    ·        View.post (Runnable) 方法
 
    ·        View.postDelayed (Runnable) 方法
 
比如,使用View.post(Runnable)修改上面的代碼:
 
[java] view plaincopyprint?
 
    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(); 
 
    } 
 
   這樣的實現(xiàn)是符合“線程安全”原則的:在額外的線程中完成網(wǎng)絡操作并且在UI線程中完成對ImageView的操作。
 
   然而,隨著操作復雜性的增加,上述代碼可能會變得非常復雜導致維護困難。為了解決工作線程中處理此類復雜操作,你可能會考慮在工作線程中使用Handler類來處理由UI線程發(fā)送過來的消息。但可能使用AsyncTask是此類問題的最好解決方案,它很好的簡化了工作線程需要和UI組件發(fā)生交互的問題。
 
使用AsyncTask
 
AsyncTask允許你完成一些和用戶界面相關(guān)的異步工作。它在一個工作線程中完成一些阻塞工作任務然后在任務完成后通知UI線程,這些都不需要你自己來管理工作線程。
 
你必須從AsyncTask派生一個子類并實現(xiàn)doInBackground()回調(diào)函數(shù)來使用AsyncTask,AsyncTask將使用后臺進程池來執(zhí)行異步任務。為了能夠更新用戶界面,你必須實現(xiàn)onPostExecute()方法,該方法將傳遞doInBackground()的返回結(jié)果,并且運行在UI線程中。然后你可以在UI線程中調(diào)用execute() 方法來執(zhí)行該任務。
 
比如,使用AsyncTask來完成之前的例子:
 
[java] view plaincopyprint?
 
    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[0]); 
 
        } 
 
         
 
        /** 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); 
 
        } 
 
    } 
 
    現(xiàn)在UI是安全的而且代碼變的更簡單,因為它把在工作線程中的工作和在UI線程的工作很好的分隔開。
 
你應該參考AsyncTask的詳細文檔以便更好的理解它的工作原理,這里給出它的基本步驟:
 
 
    ·        你可以使用generics為Task指定參數(shù)類型,返回值類型等
 
    ·        方法doInBackground()將自動在一個工作線程中執(zhí)行。
 
    ·        方法onPreExecute(),onPostExecute()和onProgressUpdate都在UI線程中調(diào)用。
 
    ·        方法doInBackground()的返回值將傳遞給onPostExecute()方法。
 
    ·        你可以在doInBackground()中任意調(diào)用publishProgress()方法,該方法將會調(diào)用UI線程中的onProgressUpdate()方法,你可以用它來報告任務完成的進度。
 
·        你可以在任意線程中任意時刻終止任務的執(zhí)行。
 
要注意的是,由于系統(tǒng)配置的變化(比如屏幕的方向轉(zhuǎn)動)你的工作線程可能會碰到意外的重新啟動,這種情況下,你的工作線程可能被銷毀,你可以參考Android開發(fā)包中Shelves示例來處理線程重新的問題。
 
編寫“線程安全”方法
 
在某些情況下,你編寫的方法可能會被多個線程調(diào)用,此時你實現(xiàn)方法時要保證它是“線程安全”的。
 
“線程安全”是可以被遠程調(diào)用方法實現(xiàn)的基本規(guī)則—比如支持“綁定”的Service中的方法。當在實現(xiàn)IBinder接口同一進程中調(diào)用IBinder對象的方法時,該方法運行在調(diào)用者運行的同一線程中。然而,如果調(diào)用來自不同進程,系統(tǒng)將使用和實現(xiàn)IBinder接口的進程關(guān)聯(lián)的線程池中的某個線程(非該進程中的UI線程)來執(zhí)行IBinder的方法。比如,一個Service的onBind()方法會在某個Service進程的UI線程中調(diào)用,而由onBind()返回的對象(比如實現(xiàn)遠程調(diào)用RPC方法的子類)的方法會在線程池的某個線程中執(zhí)行。由于Service可能服務于多個客戶端,那么可能有線程池中的多個線程同時執(zhí)行IBinder對象的某個方法,因此IBinder對象的方法必須保證是線程安全的。
 
同樣的,一個Content Provider可以接受來自其它多個進程的數(shù)據(jù)請求。盡管ContentResolver和ContentProvider類隱藏了處理這些數(shù)據(jù)請求時進程間通信的詳細機制,這些請求方法有query(), insert (), delete (), update () 及getType() 等。這些方法會在Content Provider的進程的線程池的某個線程中執(zhí)行。由于這些方法同時有不定數(shù)量的線程同時調(diào)用,因此這些方法也必須是線程安全的。
 
進程間通信
 
    Android系統(tǒng)支持使用遠程調(diào)用(RPC)來實現(xiàn)進程間通信(IPC)的機制。此時在一個Activity或其它程序組件調(diào)用某個方法,而該方法的實現(xiàn)執(zhí)行是在另外的進程中(遠程進程)。遠程調(diào)用可能給調(diào)用者返回結(jié)果。這就要求將方法調(diào)用和相關(guān)數(shù)據(jù)分離到某個層次,以便能讓操作系統(tǒng)理解,能從本地進程傳送數(shù)據(jù)到遠程進程地址空間,在遠程能夠重新構(gòu)造數(shù)據(jù)以執(zhí)行方法,返回數(shù)據(jù)也能夠反向返回。Android支持能夠完成這些進程間通信事務的所有代碼,從而使你可以只關(guān)注于定義和實現(xiàn)遠程調(diào)用的接口。
 
    為了使用進程間通信(IPC),你的應用需要使用bindService()綁定到某個Service。


本文版權(quán)歸黑馬程序員Android+物聯(lián)網(wǎng)培訓學院所有,歡迎轉(zhuǎn)載,轉(zhuǎn)載請注明作者出處。謝謝!
作者:黑馬程序員Android+物聯(lián)網(wǎng)培訓學院
首發(fā):http://android.itheima.com
分享到:
在線咨詢 我要報名
和我們在線交談!