前言:本文所寫的是博主的個人見解悯仙,如有錯誤或者不恰當(dāng)之處膘怕,歡迎私信博主想诅,加以改正!原文鏈接岛心,demo鏈接
當(dāng)某個應(yīng)用組件啟動且未運行其他組件時来破, Android 系統(tǒng)會使用單個執(zhí)行線程為應(yīng)用啟動新的 Linux 進程。默認情況下忘古,同一應(yīng)用的所有組件在相同的進程和線程(主線程)中運行徘禁。如果某個應(yīng)用組件啟動且應(yīng)用已存在進程(存在其他組件),則該組件會在此進程內(nèi)啟動并使用相同的執(zhí)行線程髓堪,但是你可以安排應(yīng)用的其他組件在單獨的進程中運行送朱,并為任何進程創(chuàng)建額外的線程娘荡。
進程
默認情況下,同一應(yīng)用的所有組件均在相同的進程中運行驶沼,且大多數(shù)的應(yīng)該都是如此炮沐。但是,如果需要控制某個組件所屬的進程回怜,則可以在清單文件中執(zhí)行此操作大年。
各類組件元素的清單文件條目 <activity>
、<service>
鹉戚、<receiver>
和 <provider>
均支持 android:process
屬性鲜戒,該屬性可以指定組件應(yīng)該運行在哪個進程∧ǖ剩可以設(shè)置此屬性遏餐,使每個組件在各自的進程中運行,或者使一些組件共享一個進程赢底,而其他組件則不共享失都。此外,還可以設(shè)置 android:process 幸冻,應(yīng)用共享具有相同的 Linux 用戶 ID 和相同的證書簽署粹庞,使不同應(yīng)用的組件在相同的進程中運行。
此外洽损,<application>
元素還支持 android:process
屬性庞溜,可以設(shè)置適用于所有組件的默認值。
如果內(nèi)存不足碑定,而其他為用戶提供更緊急服務(wù)的進程又需要內(nèi)存時流码,Android 可能會決定在某一時刻關(guān)閉某一進程。在被終止進程中運行的應(yīng)用組件也會隨之銷毀延刘。當(dāng)這些組件需要再次運行時漫试,系統(tǒng)將為它們重啟進程。
決定終止哪個進程時碘赖,Android 系統(tǒng)將權(quán)衡它們對用戶的相對重要程度驾荣。例如普泡,相對于托管可見 Activity 的進程而言,它更有可能關(guān)閉托管屏幕上不再可見的 Activity 進程撼班。因此,是否終止某個進程取決于該進程所運行組件的狀態(tài)权烧。
進程的生命周期
Android 系統(tǒng)將盡量長時間的保持應(yīng)用進程眯亦,但為了新建進程或者運行更重要的進程伤溉,最終需要移除舊的進程來回收內(nèi)存妻率。為了確定保留或終止哪些進程,系統(tǒng)會根據(jù)正在運行的組件及這些組件的狀態(tài)宫静,將每個進程按重要性層次結(jié)構(gòu)進行劃分走净,必要時,系統(tǒng)會首先清除重要性最低的進程孤里,然后是重要性略遜的進程,以此類推说搅,來回收系統(tǒng)資源。
重要性層次結(jié)構(gòu)一共有5級弄唧。下面的列表按照重要程度列出了各類進程(第一個進程最重要霍衫,會是最后被終止的進程):
-
前臺進程
用戶當(dāng)前操作所必需的進程静浴。如果一個進程滿足以下任意一個條件淘菩,即視為前臺進程:
- 托管于用戶正在交互的 Activity (已調(diào)用 Activity 的 onResume() 方法)
- 托管于某個 Service,后者綁定到用戶正在交互的 Activity
- 托管于正在前臺運行的 Service (服務(wù)已調(diào)用 startForeground())
- 托管于正在執(zhí)行一個生命周期回調(diào)的 Service (onCreate()掌腰、onStart() 或 onDestroy())
- 托管于正在執(zhí)行 onReceive() 方法的 BroadcastReceiver
通常在任意給定時間前臺進程都為數(shù)不多携兵。只有在內(nèi)存不足以支持它們同時繼續(xù)運行搂誉,不得已的情況下,系統(tǒng)才會終止它們炭懊。此時,嘲碧,設(shè)備往往已達到內(nèi)存分頁狀態(tài)父阻,需要終止一些前臺進程來確保用戶界面正常響應(yīng)望抽。
-
可見進程
沒有任何前臺組件、但會影響用戶在屏幕上所見內(nèi)容的進程履婉。如果一個進程滿足以下任一條件煤篙,即視為可見進程:
- 托管不在前臺、但仍對客戶可見的 Activity (已調(diào)用其 onPause()方法)辑奈。例如已烤,如果前臺 Activity 啟動一個對話框,允許在其后顯示一個 Activity ,則有可能會發(fā)生這種情況稍计。
- 托管綁定到可見(或前臺) Activity 的 Service
可見進程被視為極其重要的進程唐片,除非是為了維持所有前臺進程同時運行而必須終止,否則系統(tǒng)不會終止這些進程茧球。
-
服務(wù)進程
正在運行已使用 startService() 方法啟動的服務(wù)不屬于上面的兩個更高級別的進程星持。盡管服務(wù)進程與用戶縮減內(nèi)容沒有直接關(guān)聯(lián),但他們通常執(zhí)行一些用戶關(guān)心的操作(例如揪垄,在后臺播放音樂或從網(wǎng)絡(luò)下載數(shù)據(jù))逻翁。因此除非內(nèi)存不足以維持所有前臺進程和可見進程同時運行,否則系統(tǒng)會會讓服務(wù)進程保持運行狀態(tài)八回。
-
后臺進程
包含目前對用戶不可見的 Activity 的進程(已調(diào)用 Activity 的 onStop() 方法)缠诅。這些進程對用戶體驗沒有直接影響,系統(tǒng)隨時可以終止它們管引,以回收內(nèi)存供前臺進程、可見進程或服務(wù)進程使用谅将。通常會有很多后臺進程在運行,他們都會保存在LRU(最近最少使用)列表中邦投,確保包含用戶最近查看的 Activity 的進程會是被最后終止的一個擅笔。如果某個 Activity 正確的實現(xiàn)了生命周期方法,并且保存了其當(dāng)前狀態(tài)念脯,則終止進程不會對用戶體驗產(chǎn)生明顯影響弯淘,因為當(dāng)用戶導(dǎo)航回該 Activity 時,Activity 會恢復(fù)其所有可見的狀態(tài)庐橙。可以參考淺談Android Activity的生命周期转培。
空進程
不含任何活動應(yīng)用組件的進程浆竭。 保留這種進程的唯一目的是用作緩存,以縮短下次在其運行組件所需的啟動時間删窒。為使總體系統(tǒng)資源在進程緩存和底層內(nèi)了緩存之間保持平衡顺囊,系統(tǒng)往往會終止這些進程。
根據(jù)進程中當(dāng)前活動組件的重要程度诚亚, Android 會將進程評定為它可能達到的最高級別测萎。例如届巩,如果某些進程托管這服務(wù)和可見 Activity ,則會將進程評定為可見進程腕唧,而不是服務(wù)進程。
此外枣接,一個進程的級別可能會因其他進程對他的依賴而有所提高但惶,即服務(wù)于另一個進程的進程,它的級別永遠不會低于它所服務(wù)的進程膀曾。例如添谊,如果進程 A 中的內(nèi)容提供程序為 B 進程的客戶端提供服務(wù),或者進程 A 中的服務(wù)綁定到進程 B 中的組件,則進程 A 始終被視為至少與 B 同樣重要斩狱。
由于運行服務(wù)的進程其級別高于托管在后臺的 Activity 的進程,因此啟動長時間運行操作的 Activity 最好為該操作啟動服務(wù)泌枪,而不是簡單的創(chuàng)建工作線程污筷,當(dāng)操作有可能比 Activity 更加持久時尤要如此。例如陆蟆,正在將圖片上傳到網(wǎng)站的 Activity 應(yīng)該啟動服務(wù)來執(zhí)行上傳惋增,這樣一來,即使用戶退出 Activity 林束,仍可在后臺繼續(xù)執(zhí)行上傳操作稽亏。使用服務(wù)可以保證,無論 Activity 發(fā)生什么情況胖腾,該操作至少具備“服務(wù)進程”優(yōu)先級。同理咸作,廣播接收器也可以使用服務(wù)记罚,而不是簡單的將耗時操作放入工作線程中。
線程
應(yīng)用啟動時桐智,系統(tǒng)會為應(yīng)用創(chuàng)建一個名為“主線程”的執(zhí)行線程说庭。此線程非常重要,它負責(zé)將事件分派給相應(yīng)的用戶界面小部件口渔,其中包括繪圖事件。此外痪欲,他也是應(yīng)用于 Android UI 工具包組件(來自 android.widget 和 android.view 軟件包的組件)進行交互的線程攻礼。因此,主線程也有時被稱為 UI 線程知举。
系統(tǒng)不會為每個組件創(chuàng)建單獨的線程太伊。運行在同一進程的所有組件均在 UI 線程中實例化,并且對每個組件的系統(tǒng)調(diào)用均由該線程進行分派锰提。因此響應(yīng)系統(tǒng)回調(diào)的方法(例如芳悲,報告用戶操作的 onKeyDown() 或者生命周期回調(diào)方法)始終在進程的 UI 線程中運行。
例如谅年,當(dāng)用戶觸摸屏幕上的按鈕時肮韧,應(yīng)用的 UI 線程將會將觸摸時間分派給小部件文黎,而小部件反過來又設(shè)置其按下狀態(tài)殿较,并將失效請求發(fā)布到事件隊列中淋纲。UI 線程從隊列中取消該請求并通知小部件重繪自身院究。
在應(yīng)用執(zhí)行繁重的任務(wù)響應(yīng)用戶交互時,除非正確實現(xiàn)應(yīng)用伙窃,否則這種單線程模式可能會導(dǎo)致性能低下样漆。如果 UI 線程需要處理所有任務(wù),則執(zhí)行耗時操作(如網(wǎng)絡(luò)訪問或數(shù)據(jù)庫查詢)將會阻塞整個 UI 鳍怨。一旦線程被阻塞跪妥,將無法分派任何事件,包括繪圖事件侦香。從用戶角度來看纽疟,應(yīng)該顯示為掛起。更糟糕的情況是如果 UI 線程被阻塞超過幾秒鐘(目前大約是5秒鐘)污朽,用戶就會看到一個“應(yīng)用無響應(yīng)”( ANR )對話框膘壶。如果引起用戶不滿,他們可能會決定退出并卸載應(yīng)用顷锰。
此外亡问,Android UI 工具包并非線程安全工具包肛宋,所以不能通過工作線程操縱 UI束世,只能通過 UI 線程操縱用戶界面。因此 Android 的單線程模式必須遵守兩條規(guī)則:
- 不要阻塞 UI 線程
- 不要在 UI 線程之外訪問 Android UI 工具包
工作線程
在單線程模式下沉帮,要保證應(yīng)用 UI 的響應(yīng)能力贫堰,關(guān)鍵是不能阻塞 UI 線程。如果執(zhí)行的操作不能很快完成喇勋,則應(yīng)該確保他們在單獨的線程(后臺或者工作線程)中運行偎行。
例如,下面演示了一個點擊監(jiān)聽器從單獨的線程下載圖片并將其顯示在 ImageView 中:
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
final Bitmap bitmap = loadImageFromNetwork(url);
showImage.setImageBitmap(bitmap);
}
}).start();
}
這段代碼看起來似乎運行良好渗常,因為它創(chuàng)建了一個新的線程來處理網(wǎng)絡(luò)操作皱碘。但是它違背了單線程模式的第二條規(guī)則:不要在 UI 線程之外訪問 Android UI 工具包,此示例從工作線程(而不是 UI )線程修改了 ImageView 隐孽。這個可能導(dǎo)致出現(xiàn)不明確,不可預(yù)見的行為菱阵,但是要跟蹤這個行為既困難又費時晴及。
未解決此問題,Android 提供了幾種途徑從其他線程訪問 UI 線程琳钉。以下列出幾種有用的方法:
- Activity.runOnUiThread(Runnable)
- View.post(Runnable)
- View.postDelayed(Runnable,long)
例如你可以通過使用 Activity.runOnUiThread(Runnable) 方法修復(fù)上面的代碼:
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
final Bitmap bitmap = loadImageFromNetwork(url);
MainActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
showImage.setImageBitmap(bitmap);
}
});
}
}).start();
}
上面的實現(xiàn)屬于線程安全型:在單獨的線程中完成網(wǎng)絡(luò)操作蛛倦,而在 UI 線程中操作 ImageView溯壶。
但是隨著操作的復(fù)雜甫男,這類代碼變得難以維護验烧,要通過工作線程實現(xiàn)更復(fù)雜的交互,可以考慮在工作線程中使用 Handler 處理來自 UI 線程的消息若治。當(dāng)然更好的解決方案是擴展 AsyncTask 類倔监,此類簡化了與 UI 線程進行交互所需要執(zhí)行的工作線程任務(wù)菌仁。
使用 AsyncTask
AsyncTask允許用戶對用戶界面執(zhí)行異步操作。他會先阻塞工作線程中的操作谱秽,然后在 UI 線程中發(fā)布結(jié)果摹迷,而你無需親自處理線程和處理程序。
要使用它近哟,必須創(chuàng)建 AsyncTask 的子類鲫寄,并實現(xiàn) odInBrackground() 回調(diào)方法,該方法將在后臺線程池中運行,需要更新 UI 則要實現(xiàn) onPostExecute()戳玫,傳遞 odInBrackground() 返回的結(jié)果并在 UI 線程中運行未斑,使之更安全地更新 UI蜡秽,可以通過從 UI 線程中調(diào)用 execute() 來運行任務(wù)。
例如芽突,下面使用 AsyncTask 來實現(xiàn)上述的示例:
Activity 里調(diào)用 execute() 方法
@Override
public void onClick(View v) {
new DownloadImageTask(showImage).execute(url);
}
繼承 AsyncTask 實現(xiàn)異步加載
public class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
private ImageView mImageView;
public DownloadImageTask() {
}
public DownloadImageTask(ImageView imageView) {
mImageView = imageView;
}
@Override
protected Bitmap doInBackground(String... params) {
return DownImageUtil.getInstance().loadImageFromNetwork(params[0]);
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (mImageView != null) {
mImageView.setImageBitmap(bitmap);
}
}
}
現(xiàn)在 UI 是安全的诉瓦,代碼也得到簡化,任務(wù)分解成了兩部分:一部分在工作線程內(nèi)完成力细,另一部分在 UI 線程內(nèi)完成眠蚂。
下面簡單的介紹 AsyncTask 的工作方法:
- 可以使用泛型指定參數(shù)類型斗躏、進度值和任務(wù)最終值
- 方法 doInBackground() 會在工作線程上自動執(zhí)行
- onPreExecute() 、onPostExecute() 和 onProgressUpdate() 均在 UI 線程中調(diào)用
- doInBackground() 返回值將發(fā)送到 onPostExecute()
- 可以隨時在 doInBackground() 中調(diào)用 publishProgress() 笛臣,以在 UI 線程中執(zhí)行 onProgressUpdate()
- 可以隨時取消任何線程中的任務(wù)
注意:使用工作線程時有可能遇到另一個問題隧饼,即:運行時配置變更(例如,用戶更改了屏幕方向)诞丽,導(dǎo)致 Activity 意外重啟拐格,這可能會銷毀工作線程。
線程安全方法
在某些情況下懂衩,你實現(xiàn)的方法可能會從多個線程調(diào)用金踪,因此在編寫這些方法時必須確保其滿足線程安全的要求。
這一點主要適用于可以遠程調(diào)用的方法沛申,如綁定服務(wù)中的方法姐军。如果對 IBinder 中所實現(xiàn)方法的調(diào)用源自運行 IBinder 的同一進程,則該方法在調(diào)用方的線程中執(zhí)行著觉。但是惊暴,如果調(diào)用源自其他進程辽话,則該方法將從線程池中選擇某個線程中執(zhí)行(而不是在進程的 UI 線程中執(zhí)行)卫病,線程池由系統(tǒng)與 IBinder 相同的進程中維護典徘。例如,服務(wù)的 onBind() 方法從服務(wù)的 UI 線程中調(diào)用帜平,在 onBind() 返回的對象中實現(xiàn)的方法仍會從線程池中的線程調(diào)用梅鹦。由于一個服務(wù)可以有多個客戶端,因此可能會有多個線程池同一時間使用同一個 IBinder 方法嗤栓。所以 IBinder 方法實現(xiàn)必須為線程安全方法蝶念。
同樣芋绸,內(nèi)容提供程序也可以接受來自其他進程的數(shù)據(jù)請求摔敛。盡管 ContentResolver 和 ContentProvider 類隱藏了如何管理進程間通信的細節(jié),但響應(yīng)這些請求的 ContentProvider 方法(query()马昙、insert()行楞、delete()、update() 和 getType() 方法)將從內(nèi)容提供程序所在進程的線程池中調(diào)用形用,而不是從進程的 UI 線程調(diào)用证杭。由于這些方法同時從任意數(shù)量的線程調(diào)用,因此它們必須實現(xiàn)為線程安全方法镇饺。
進程間通信
Android 利用遠程過程調(diào)用(RPC)提供了一種進程間通信(IPC)機制送讲,通過這種機制惋啃,由 Activity 或其他應(yīng)用組件調(diào)用方法將(在其他進程中)遠程執(zhí)行肥橙,而所有結(jié)果將返回給調(diào)用方秸侣。這要求把方法調(diào)用及其數(shù)據(jù)分解至操作系統(tǒng)可以識別的程度,并將其從本地進程和地址空間傳輸至遠程進程和地址空間椭坚,然后在遠程進程中重新組裝并執(zhí)行該調(diào)用搏色。然后返回值將沿相反方向傳輸回來。Android 提供了執(zhí)行這些 IPC 事務(wù)所需的全部代碼垂涯,因此只需要集中精力定義和實現(xiàn) RPC 編程接口即可航邢。
需要執(zhí)行 IPC,必須使用 bindService() 將應(yīng)用綁定到服務(wù)上,想了解詳細的信息,可以參考 淺談 Android Service膳殷。