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

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

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

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


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