進(jìn)程和線程 如果某個(gè)應(yīng)用程序組件是第一次被啟動(dòng),且這時(shí)應(yīng)用程序也沒(méi)有其他組件在運(yùn)行,,則Android系統(tǒng)會(huì)為應(yīng)用程序創(chuàng)建一個(gè)包含單個(gè)線程的linux進(jìn)程,。默認(rèn)情況下,同一個(gè)應(yīng)用程序的所有組件都運(yùn)行在同一個(gè)進(jìn)程和線程里(叫做“main”主線程),。如果組件啟動(dòng)時(shí),,已經(jīng)存在應(yīng)用程序的進(jìn)程了(因?yàn)閼?yīng)用程序的其它組件已經(jīng)在運(yùn)行了),則此組件會(huì)在已有的進(jìn)程和線程中啟動(dòng)運(yùn)行,。不過(guò),,可以指定組件運(yùn)行在其他進(jìn)程里,,也可以為任何進(jìn)程創(chuàng)建額外的線程。 本文討論進(jìn)程和線程是如何在Android應(yīng)用程序中發(fā)揮作用的,。
默認(rèn)情況下,,同一個(gè)應(yīng)用程序內(nèi)的所有組件都是運(yùn)行在同一個(gè)進(jìn)程中的,大部分應(yīng)用程序也不會(huì)去改變它,。不過(guò),,如果需要指定某個(gè)特定組件所屬的進(jìn)程,則可以利用manifest 文件來(lái)達(dá)到目的,。 manifest文件中的每種組件元素——<activity>,、 <service>、 <receiver>和<provider>——都支持定義android:process屬性,,用于指定組件運(yùn)行的進(jìn)程,。設(shè)置此屬性即可實(shí)現(xiàn)每個(gè)組件在各自的進(jìn)程中運(yùn)行,或者某幾個(gè)組件共享一個(gè)進(jìn)程而其它組件運(yùn)行于獨(dú)立的進(jìn)程,。設(shè)置此屬性也可以讓不同應(yīng)用程序的組件運(yùn)行在同一個(gè)進(jìn)程中——實(shí)現(xiàn)多個(gè)應(yīng)用程序共享同一個(gè)Linux用戶ID,、賦予同樣的權(quán)限。 <application>元素也支持android:process屬性,,用于指定所有組件的默認(rèn)進(jìn)程,。 如果內(nèi)存不足,可又有其它為用戶提供更緊急服務(wù)的進(jìn)程需要更多內(nèi)存,,Android可能會(huì)決定關(guān)閉一個(gè)進(jìn)程,。在此進(jìn)程中運(yùn)行著的應(yīng)用程序組件也會(huì)因此被銷毀。當(dāng)需要再次工作時(shí),,會(huì)為這些組件重新創(chuàng)建一個(gè)進(jìn)程,。 在決定關(guān)閉哪個(gè)進(jìn)程的時(shí)候,Android系統(tǒng)會(huì)權(quán)衡它們相對(duì)用戶的重要程度,。比如,,相對(duì)于一個(gè)擁有可見(jiàn)activity的進(jìn)程,更有可能去關(guān)閉一個(gè)activity已經(jīng)在屏幕上看不見(jiàn)的進(jìn)程,。也就是說(shuō),,是否終止一個(gè)進(jìn)程,取決于運(yùn)行在此進(jìn)程中組件的狀態(tài),。終止進(jìn)程的判定規(guī)則將在后續(xù)內(nèi)容中討論,。
Android系統(tǒng)試圖盡可能長(zhǎng)時(shí)間地保持應(yīng)用程序進(jìn)程,但為了新建或者運(yùn)行更加重要的進(jìn)程,,總是需要清除過(guò)時(shí)進(jìn)程來(lái)回收內(nèi)存,。為了決定保留或終止哪個(gè)進(jìn)程,根據(jù)進(jìn)程內(nèi)運(yùn)行的組件及這些組件的狀態(tài),系統(tǒng)把每個(gè)進(jìn)程都劃入一個(gè)“重要性層次結(jié)構(gòu)”中,。重要性最低的進(jìn)程首先會(huì)被清除,,然后是下一個(gè)最低的,依此類推,,這都是回收系統(tǒng)資源所必需的,。 重要性層次結(jié)構(gòu)共有5級(jí),以下列表按照重要程度列出了各類進(jìn)程(第一類進(jìn)程是最重要的,,將最后一個(gè)被終止): 1. 前臺(tái)進(jìn)程 用戶當(dāng)前操作所必須的進(jìn)程,。滿足以下任一條件時(shí),進(jìn)程被視作處于前臺(tái): o 其中運(yùn)行著正與用戶交互的Activity(Activity對(duì)象的 onResume() 方法已被調(diào)用),。 o 其中運(yùn)行著被正與用戶交互的activity綁定的服務(wù)Service,。 o 其中運(yùn)行著“前臺(tái)”服務(wù)Service——服務(wù)以startForeground()方式被調(diào)用。 o 其中運(yùn)行著正在執(zhí)行生命周期回調(diào)方法(onCreate(),、onStart()或onDestroy())的服務(wù)Service,。 o 其中運(yùn)行著正在執(zhí)行onReceive()方法的BroadcastReceiver。 一般而言,,任何時(shí)刻前臺(tái)進(jìn)程都是為數(shù)不多的,,只有作為最后的策略——當(dāng)內(nèi)存不足以維持它們同時(shí)運(yùn)行時(shí)——才會(huì)被終止。通常,,設(shè)備這時(shí)候已經(jīng)到了內(nèi)存分頁(yè)狀態(tài)(memory paging state)的地步,,終止一些前臺(tái)進(jìn)程是為了保證用戶界面的及時(shí)響應(yīng)。
2. 可見(jiàn)進(jìn)程 沒(méi)有前臺(tái)組件,、但仍會(huì)影響用戶在屏幕上所見(jiàn)內(nèi)容的進(jìn)程,。滿足以下任一條件時(shí),進(jìn)程被認(rèn)為是可見(jiàn)的: o 其中運(yùn)行著不在前臺(tái)的Activity,,但用戶仍然可見(jiàn)到此activity(onPause()方法被調(diào)用了),。比如以下場(chǎng)合就可能發(fā)生這種情況:前臺(tái)activity打開(kāi)了一個(gè)對(duì)話框,,而之前的activity還允許顯示在后面,。 o 其中運(yùn)行著被可見(jiàn)(或前臺(tái))activity綁定的服務(wù)Service。 可見(jiàn)進(jìn)程被認(rèn)為是非常重要的進(jìn)程,,除非無(wú)法維持所有前臺(tái)進(jìn)程同時(shí)運(yùn)行了,,它們是不會(huì)被終止的。
3. 服務(wù)進(jìn)程 此進(jìn)程運(yùn)行著由startService()方法啟動(dòng)的服務(wù),,它不會(huì)升級(jí)為上述兩級(jí)別,。盡管服務(wù)進(jìn)程不直接和用戶所見(jiàn)內(nèi)容關(guān)聯(lián),但他們通常在執(zhí)行一些用戶關(guān)心的操作(比如在后臺(tái)播放音樂(lè)或從網(wǎng)絡(luò)下載數(shù)據(jù)),。因此,,除非內(nèi)存不足以維持所有前臺(tái)、可見(jiàn)進(jìn)程同時(shí)運(yùn)行,,系統(tǒng)會(huì)保持服務(wù)進(jìn)程的運(yùn)行,。
4. 后臺(tái)進(jìn)程 包含目前用戶不可見(jiàn)activity(Activity對(duì)象的onStop()方法已被調(diào)用)的進(jìn)程,。這些進(jìn)程對(duì)用戶體驗(yàn)沒(méi)有直接的影響,系統(tǒng)可能在任意時(shí)間終止它們,,以回收內(nèi)存供前臺(tái)進(jìn)程,、可見(jiàn)進(jìn)程及服務(wù)進(jìn)程使用。通常會(huì)有很多后臺(tái)進(jìn)程在運(yùn)行,,所以它們被保存在一個(gè)LRU(最近最少使用)列表中,,以確保最近被用戶使用的activity最后一個(gè)被終止。如果一個(gè)activity正確實(shí)現(xiàn)了生命周期方法,,并保存了當(dāng)前的狀態(tài),,則終止此類進(jìn)程不會(huì)對(duì)用戶體驗(yàn)產(chǎn)生可見(jiàn)的影響。因?yàn)樵谟脩舴祷?/SPAN>時(shí),,activity會(huì)恢復(fù)所有可見(jiàn)的狀態(tài),。關(guān)于保存和恢復(fù)狀態(tài)的詳細(xì)信息,請(qǐng)參閱Activities文檔,。
5. 空進(jìn)程 不含任何活動(dòng)應(yīng)用程序組件的進(jìn)程,。保留這種進(jìn)程的唯一目的就是用作緩存,以改善下次在此進(jìn)程中運(yùn)行組件的啟動(dòng)時(shí)間,。為了在進(jìn)程緩存和內(nèi)核緩存間平衡系統(tǒng)整體資源,,系統(tǒng)經(jīng)常會(huì)終止這種進(jìn)程。
依據(jù)進(jìn)程中目前活躍組件的重要程度,,Android會(huì)給進(jìn)程評(píng)估一個(gè)盡可能高的級(jí)別,。例如:如果一個(gè)進(jìn)程中運(yùn)行著一個(gè)服務(wù)和一個(gè)用戶可見(jiàn)的activity,則此進(jìn)程會(huì)被評(píng)定為可見(jiàn)進(jìn)程,,而不是服務(wù)進(jìn)程,。 此外,一個(gè)進(jìn)程的級(jí)別可能會(huì)由于其它進(jìn)程的依賴而被提高——為其它進(jìn)程提供服務(wù)的進(jìn)程級(jí)別永遠(yuǎn)不會(huì)低于使用此服務(wù)的進(jìn)程,。比如:如果A進(jìn)程中的content provider為進(jìn)程B中的客戶端提供服務(wù),,或進(jìn)程A中的服務(wù)被進(jìn)程B中的組件所調(diào)用,則A進(jìn)程至少被視為與進(jìn)程B同樣重要,。 因?yàn)檫\(yùn)行服務(wù)的進(jìn)程級(jí)別是高于后臺(tái)activity進(jìn)程的,,所以,如果activity需要啟動(dòng)一個(gè)長(zhǎng)時(shí)間運(yùn)行的操作,,則為其啟動(dòng)一個(gè)服務(wù)service會(huì)比簡(jiǎn)單地創(chuàng)建一個(gè)工作線程更好些——尤其是在此操作時(shí)間比activity本身存在時(shí)間還要長(zhǎng)久的情況下,。比如,一個(gè)activity要把圖片上傳至Web網(wǎng)站,,就應(yīng)該創(chuàng)建一個(gè)服務(wù)來(lái)執(zhí)行之,,即使用戶離開(kāi)了此activity,上傳還是會(huì)在后臺(tái)繼續(xù)運(yùn)行。不論activity發(fā)生什么情況,,使用服務(wù)可以保證操作至少擁有“服務(wù)進(jìn)程”的優(yōu)先級(jí),。同理,上一篇中的廣播接收器broadcast receiver也是使用服務(wù)而非線程來(lái)處理耗時(shí)任務(wù)的,。
應(yīng)用程序啟動(dòng)時(shí),,系統(tǒng)會(huì)為它創(chuàng)建一個(gè)名為“main”的主線程。主線程非常重要,,因?yàn)樗?/SPAN>負(fù)責(zé)把事件分發(fā)給相應(yīng)的用戶界面widget——包括屏幕繪圖事件,。它也是應(yīng)用程序與Android UI組件包(來(lái)自android.widget和android.view包)進(jìn)行交互的線程。因此,,主線程有時(shí)也被叫做UI線程,。 系統(tǒng)并不會(huì)為每個(gè)組件的實(shí)例都創(chuàng)建單獨(dú)的線程。運(yùn)行于同一個(gè)進(jìn)程中的所有組件都是在UI線程中實(shí)例化的,,對(duì)每個(gè)組件的系統(tǒng)調(diào)用也都是由UI線程分發(fā)的,。因此,對(duì)系統(tǒng)回調(diào)進(jìn)行響應(yīng)的方法(比如報(bào)告用戶操作的onKeyDown()或生命周期回調(diào)方法)總是運(yùn)行在UI線程中,。 舉個(gè)例子,,當(dāng)用戶觸摸屏幕上的按鈕時(shí),應(yīng)用程序的UI線程把觸摸事件分發(fā)給widget,,widget先把自己置為按下?tīng)顟B(tài),,再發(fā)送一個(gè)顯示區(qū)域已失效(invalidate)的請(qǐng)求到事件隊(duì)列中。UI線程從隊(duì)列中取出此請(qǐng)求,,并通知widget重繪自己,。 如果應(yīng)用程序在與用戶交互的同時(shí)需要執(zhí)行繁重的任務(wù),單線程模式可能會(huì)導(dǎo)致運(yùn)行性能很低下,,除非應(yīng)用程序的執(zhí)行時(shí)機(jī)剛好很合適,。如果UI線程需要處理每一件事情,那些耗時(shí)很長(zhǎng)的操作——諸如訪問(wèn)網(wǎng)絡(luò)或查詢數(shù)據(jù)庫(kù)等——將會(huì)阻塞整個(gè)UI(線程),。一旦線程被阻塞,,所有事件都不能被分發(fā),包括屏幕繪圖事件,。從用戶的角度看來(lái),,應(yīng)用程序看上去像是掛起了,。更糟糕的是,如果UI線程被阻塞超過(guò)一定時(shí)間(目前大約是5秒鐘),,用戶就會(huì)被提示那個(gè)可惡的“應(yīng)用程序沒(méi)有響應(yīng)”(ANR)對(duì)話框,。如果引起用戶不滿,他可能就會(huì)決定退出并刪除這個(gè)應(yīng)用程序。 此外,,Andoid的UI組件包并不是線程安全的,。因此不允許從工作線程中操作UI——只能從UI線程中操作用戶界面。于是,,Andoid的單線程模式必須遵守兩個(gè)規(guī)則: 1. 不要阻塞UI線程,。 2. 不要在UI線程之外訪問(wèn)Andoid的UI組件包。
根據(jù)對(duì)以上單線程模式的描述,,要想保證程序界面的響應(yīng)能力,,關(guān)鍵是不能阻塞UI線程。如果操作不能很快完成,,應(yīng)該讓它們?cè)趩为?dú)的線程中運(yùn)行(“后臺(tái)”或“工作”線程)。 例如:以下響應(yīng)鼠標(biāo)點(diǎn)擊的代碼實(shí)現(xiàn)了在單獨(dú)線程中下載圖片并在ImageView顯示: public void onClick(View v) { new Thread(new Runnable() { public void run() { Bitmap b = loadImageFromNetwork("http:///image.png"); mImageView.setImageBitmap(b); } }).start(); } 乍看起來(lái),這段代碼似乎能運(yùn)行得很好,,因?yàn)閯?chuàng)建了一個(gè)新的線程來(lái)處理訪問(wèn)網(wǎng)絡(luò)的操作,。可是它違反了單線程模式的第二條規(guī)則:不要在UI線程之外訪問(wèn)Andoid的UI組件包——以上例子在工作線程里而不是UI線程里修改了ImageView,。這可能導(dǎo)致不明確,、不可預(yù)見(jiàn)的后果,要跟蹤這種情況也是很困難很耗時(shí)間的,。 為了解決以上問(wèn)題,,Android提供了幾種途徑來(lái)從其它線程中訪問(wèn)UI線程。下面列出了有助于解決問(wèn)題的幾種方法: · Activity.runOnUiThread(Runnable) · View.postDelayed(Runnable, long)
比如說(shuō),,可以使用View.post(Runnable)方法來(lái)修正上面的代碼: public void onClick(View v) { new Thread(new Runnable() { public void run() { final Bitmap bitmap = loadImageFromNetwork("http:///image.png"); mImageView.post(new Runnable() { public void run() { mImageView.setImageBitmap(bitmap); } }); } }).start(); } 以上代碼的執(zhí)行現(xiàn)在是線程安全的了:網(wǎng)絡(luò)相關(guān)的操作在單獨(dú)的線程里完成,,而ImageView是在UI線程里操縱的。 不過(guò),,隨著操作變得越來(lái)越復(fù)雜,,這類代碼也會(huì)變得很復(fù)雜很難維護(hù)。為了用工作線程完成更加復(fù)雜的交互處理,,可以考慮在工作線程中用Handler來(lái)處理UI線程分發(fā)過(guò)來(lái)的消息,。當(dāng)然,最好的解決方案也許就是繼承使用異步任務(wù)類AsyncTask,,此類簡(jiǎn)化了一些工作線程和UI交互的操作,。
使用異步任務(wù) 異步任務(wù)AsyncTask 允許以異步的方式對(duì)用戶界面進(jìn)行操作。它先阻塞工作線程,,再在UI線程中呈現(xiàn)結(jié)果,,在此過(guò)程中不需要對(duì)線程和handler進(jìn)行人工干預(yù)。 要使用異步任務(wù),,必須繼承AsyncTask類并實(shí)現(xiàn)doInBackground()回調(diào)方法,,該對(duì)象將運(yùn)行于一個(gè)后臺(tái)線程池中,。要更新UI時(shí),須實(shí)現(xiàn)onPostExecute()方法來(lái)分發(fā)doInBackground()返回的結(jié)果,,由于此方法運(yùn)行在UI線程中,,所以就能安全地更新UI了。然后就可以在UI線程中調(diào)用execute()來(lái)執(zhí)行任務(wù)了,。 例如,,可以利用AsyncTask來(lái)實(shí)現(xiàn)上面的那個(gè)例子: public void onClick(View v) { new DownloadImageTask().execute("http:///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); } } 現(xiàn)在UI是安全的,代碼也得到簡(jiǎn)化,,因?yàn)槿蝿?wù)分解成了工作線程內(nèi)完成的部分和UI線程內(nèi)完成的部分,。 要全面理解這個(gè)類的使用,須閱讀AsyncTask的參考文檔,。以下是關(guān)于其工作方式的概述: · 可以用generics來(lái)指定參數(shù),、進(jìn)度值和任務(wù)最終值的類型。 · 工作線程中的doInBackground()方法會(huì)自動(dòng)執(zhí)行,。 · onPreExecute(),、onPostExecute()和onProgressUpdate()方法都在UI線程中調(diào)用。 · doInBackground()的返回值會(huì)傳給onPostExecute(),。 · 在doInBackground()內(nèi)的任何時(shí)刻,,都可以調(diào)用publishProgress()來(lái)執(zhí)行UI線程中的onProgressUpdate()。 · 可以在任何時(shí)刻,、任何線程內(nèi)取消任務(wù),。 注意:在使用工作線程時(shí),可能遇到的另一個(gè)問(wèn)題是由于運(yùn)行配置的改變(比如用戶改變了屏幕方向)導(dǎo)致activity意外重啟,,這可能會(huì)銷毀該工作線程,。要了解如何在這種情況下維持任務(wù)執(zhí)行、以及如何在activity被銷毀時(shí)正確地取消任務(wù),,請(qǐng)參見(jiàn)Shelves例程的源代碼,。 線程安全的方法 在某些場(chǎng)合,方法可能會(huì)從不止一個(gè)線程中被調(diào)用,,因此這些方法必須是寫(xiě)成線程安全的,。 對(duì)于能被遠(yuǎn)程調(diào)用的方法——比如綁定服務(wù)(bound service)中的方法,這是理所當(dāng)然的,。如果對(duì)IBinder所實(shí)現(xiàn)方法的調(diào)用發(fā)起于IBinder所在進(jìn)程的內(nèi)部,,那么這個(gè)方法是執(zhí)行在調(diào)用者的線程中的。但是,,如果調(diào)用發(fā)起于其他進(jìn)程,,那么這個(gè)方法將運(yùn)行于線程池中選出的某個(gè)線程中(而不是運(yùn)行于進(jìn)程的UI線程中),該線程池由系統(tǒng)維護(hù)且位于IBinder所在的進(jìn)程中,。例如,,即使一個(gè)服務(wù)的onBind()方法是從服務(wù)所在進(jìn)程的UI線程中調(diào)用的,實(shí)現(xiàn)了onBind()的方法對(duì)象(比如,,實(shí)現(xiàn)了RPC方法的一個(gè)子類)仍會(huì)從線程池中的線程被調(diào)用,。因?yàn)橐粋€(gè)服務(wù)可以有不止一個(gè)客戶端,所以同時(shí)可以有多個(gè)線程池與同一個(gè)IBinder方法相關(guān)聯(lián),。因此IBinder方法必須實(shí)現(xiàn)為線程安全的,。 類似地,內(nèi)容提供者(content provider)也能接收來(lái)自其它進(jìn)程的數(shù)據(jù)請(qǐng)求,。盡管ContentResolver類,、ContentProvider類隱藏了進(jìn)程間通訊管理的細(xì)節(jié),ContentProvider中響應(yīng)請(qǐng)求的方法——query(),、insert(),、delete()、update()和getType()方法——是從ContentProvider所在進(jìn)程的線程池中調(diào)用的,,而不是進(jìn)程的UI線程,。因?yàn)檫@些方法可能會(huì)從很多線程同時(shí)調(diào)用,它們也必須實(shí)現(xiàn)為線程安全的,。
Android利用遠(yuǎn)程過(guò)程調(diào)用(remote procedure call,,RPC)提供了一種進(jìn)程間通信(IPC)機(jī)制,通過(guò)這種機(jī)制,,被activity或其他應(yīng)用程序組件調(diào)用的方法將(在其他進(jìn)程中)被遠(yuǎn)程執(zhí)行,,而所有的結(jié)果將被返回給調(diào)用者。這就要求把方法調(diào)用及其數(shù)據(jù)分解到操作系統(tǒng)可以理解的程度,,并將其從本地的進(jìn)程和地址空間傳輸至遠(yuǎn)程的進(jìn)程和地址空間,,然后在遠(yuǎn)程進(jìn)程中重新組裝并執(zhí)行這個(gè)調(diào)用。執(zhí)行后的返回值將被反向傳輸回來(lái),。Android提供了執(zhí)行IPC事務(wù)所需的全部代碼,,因此只要把注意力放在定義和實(shí)現(xiàn)RPC編程接口上即可。 要執(zhí)行IPC,,應(yīng)用程序必須用bindService()綁定到服務(wù)上,。詳情請(qǐng)參閱服務(wù)Services開(kāi)發(fā)指南。 |
|
來(lái)自: BlazerOfIT > 《android》