前言
上次面試被問到多進程的知識,回答不上來,真的感覺很羞恥。以下內(nèi)容很多來自參考文章,侵刪。
一、什么情況下要用到多進程
參考《Android多進程使用場景》,感謝。
常駐后臺任務應用:類似音樂類、跑步健身類、手機管家類等長時間需要在后臺運行的應用。這些應用的特點就是,當用戶切到別的應用,或者關掉手機屏幕的時候,應用本身的核心模塊還在正常運行,提供服務。如果因為手機內(nèi)存過低,或者是進程重要性降低,導致應用被殺掉,后臺服務停止,對于這些應用來說,就是滅頂之災。合理利用多進程,將核心后臺服務模塊和其他UI模塊進行分離,保證應用能更穩(wěn)定的提供服務,從而提升用戶體驗。
多模塊應用:
多進程還有一種非常有用的場景,就是多模塊應用。比如我做的應用大而全,里面肯定會有很多模塊,假如有地圖模塊、大圖瀏覽、自定義WebView等等(這些都是吃內(nèi)存大戶),還會有一些諸如下載服務,監(jiān)控服務等等,一個成熟的應用一定是多模塊化的。
首先多進程開發(fā)能為應用解決了OOM問題,Android對內(nèi)存的限制是針對于進程的,這個閾值可以是48M、24M、16M等,視機型而定,所以,當我們需要加載大圖之類的操作,可以在新的進程中去執(zhí)行,避免主進程OOM。
多進程不光解決OOM問題,還能更有效、合理的利用內(nèi)存。我們可以在適當?shù)臅r候生成新的進程,在不需要的時候及時殺掉,合理分配,提升用戶體驗。減少系統(tǒng)被殺掉的風險。
多進程還能帶來一個好處就是,單一進程崩潰并不影響整體應用的使用。例如我在圖片瀏覽進程打開了一個過大的圖片,java heap 申請內(nèi)存失敗,但是不影響我主進程的使用,而且,還能通過監(jiān)控進程,將這個錯誤上報給系統(tǒng),告知他在什么機型、環(huán)境下、產(chǎn)生了什么樣的Bug,提升用戶體驗。
再一個好處就是,當我們的應用開發(fā)越來越大,模塊越來越多,團隊規(guī)模也越來越大,協(xié)作開發(fā)也是個很麻煩的事情。項目解耦,模塊化,是這階段的目標。通過模塊解耦,開辟新的進程,獨立的JVM,來達到數(shù)據(jù)解耦目的。模塊之間互不干預,團隊并行開發(fā),責任分工也明確。至于模塊化開發(fā)與多進程的結合,后續(xù)會寫一篇專門的文章來研究這個問題。
二、為什么要用多進程,舉個例子
舉個例子:
現(xiàn)在要做一款音樂播放器,現(xiàn)在有以下幾種方案:
A. 在Activity中直接播放音樂。
B. 啟動后臺Service,播放音樂。
C. 啟動前臺Service,播放音樂。
D. 在新的進程中,啟動后臺Service,播放音樂。
E. 在新的進程中,啟動前臺Service,播放音樂。
在Android內(nèi)存回收機制中,進程的oom_adj值越高,表示越容易被回收掉。參考《Android內(nèi)存管理篇 - adj的概念與進程adj級別控制》,感謝。

我們依次根據(jù)【打開狀態(tài)】、【按了Home鍵被切換狀態(tài)】、【按了Back鍵被退出狀態(tài)】,來查看各個方案下的oom_adj、oom_score、oom_score_adj的值。通過cat /proc/進程id/oom_adj可以看到當前進程的oom_adj值。
A. 在Activity中直接播放音樂。 0 7 9
B. 啟動后臺Service,播放音樂。0 7 9
C. 啟動前臺Service,播放音樂。 0 2 2
D. 在新的進程中,啟動后臺Service,播放音樂。 0 7 9 - 5
E. 在新的進程中,啟動前臺Service,播放音樂。 0 7 9 - 2
我們可以看到,E這個進程的值是2,像C方案,非常小,非常穩(wěn)定,而且,我們還可以在系統(tǒng)進入后臺后,手動殺掉主進程,使整個應用的內(nèi)存消耗降到最低,內(nèi)存低,優(yōu)先級又高,E獲得了今天的最穩(wěn)定的方案獎。
多進程不光解決OOM問題,還能更有效、合理的利用內(nèi)存。我們可以在適當?shù)臅r候生成新的進程,在不需要的時候及時殺掉,合理分配,提升用戶體驗。減少系統(tǒng)被殺掉的風險。
三、多進程的android:process
默認情況下,同一應用的所有組件均在相同的進程中運行,且大多數(shù)應用都不會改變這一點。 但是,如果您發(fā)現(xiàn)需要控制某個組件所屬的進程,則可在清單文件中執(zhí)行此操作。
各類組件元素的清單文件條目activity、service、receiver 和 provider均支持 android:process 屬性,此屬性可以指定該組件應在哪個進程運行。您可以設置此屬性,使每個組件均在各自的進程中運行,或者使一些組件共享一個進程,而其他組件則不共享。 此外,您還可以設置 android:process,使不同應用的組件在相同的進程中運行,但前提是這些應用共享相同的 Linux 用戶 ID 并使用相同的證書進行簽署。
此外,application元素還支持 android:process 屬性,以設置適用于所有組件的默認值。
如果內(nèi)存不足,而其他為用戶提供更緊急服務的進程又需要內(nèi)存時,Android 可能會決定在某一時刻關閉某一進程。在被終止進程中運行的應用組件也會隨之銷毀。 當這些組件需要再次運行時,系統(tǒng)將為它們重啟進程。
決定終止哪個進程時,Android 系統(tǒng)將權衡它們對用戶的相對重要程度。例如,相對于托管可見 Activity 的進程而言,它更有可能關閉托管屏幕上不再可見的 Activity 的進程。 因此,是否終止某個進程的決定取決于該進程中所運行組件的狀態(tài)。(引自官方文檔)
四、開啟進程的方法
1.我們通常會使用修改清單文件的android:process來達到多進程的目的。如果android:process的value值以冒號開頭的話,那么該進程就是私有進程,如果是以其他字符開頭,那么就是公有進程,這樣擁有相同 ShareUID 的不同應用可以跑在同一進程里。
2.通過JNI利用C/C++,調(diào)用fork()方法來生成子進程,一般開發(fā)者會利用這種方法來做一些daemon(守護進程)進程,來實現(xiàn)防殺?;畹刃Ч?。
**ShareUID : **
ShareUserId,在Android里面每個app都有一個唯一的linux user ID,則這樣權限就被設置成該應用程序的文件只對該用戶可見,只對該應用程序自身可見,而我們可以使他們對其他的應用程序可見,這會使我們用到SharedUserId,也就是讓兩個apk使用相同的userID,這樣它們就可以看到對方的文件。為了節(jié)省資源,具有相同ID的apk也可以在相同的linux進程中進行(注意,并不是一定要在一個進程里面運行),共享一個虛擬機。 ShareUserId的作用,數(shù)據(jù)共享、調(diào)用其他程序資源。
五、由多進程引起的問題
1)Application實例化多次
在Application的onCreate中獲取進程Id來判斷不同進程,然后做不同的事情。
String processName = this.getProcessName();
if (!TextUtils.isEmpty(processName) && processName.equals(this.getPackageName())) {
//判斷進程名,保證只有主進程運行
//在這里進行主進程初始化邏輯操作
Log.i(">>>>>>","oncreate");
}
public static String getProcessName() {
try {
File file = new File("/proc/" + android.os.Process.myPid() + "/" + "cmdline");
BufferedReader mBufferedReader = new BufferedReader(new FileReader(file));
String processName = mBufferedReader.readLine().trim();
mBufferedReader.close();
return processName;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
2)針對靜態(tài)成員的失效:
使用Intent或者aidl等進程通訊方式傳遞內(nèi)容,不能用靜態(tài)或單例模式。
3)針對文件共享問題:
多進程情況下會出現(xiàn)兩個進程在同一時刻訪問同一個數(shù)據(jù)庫文件的情況。這就可能造成資源的競爭訪問,導致諸如數(shù)據(jù)庫損壞、數(shù)據(jù)丟失等。在多線程的情況下我們有鎖機制控制資源的共享,但是在多進程中比較難,雖然有文件鎖、排隊等機制,但是在Android里很難實現(xiàn)。解決辦法就是多進程的時候不并發(fā)訪問同一個文件,比如子進程涉及到操作數(shù)據(jù)庫,就可以考慮調(diào)用主進程進行數(shù)據(jù)庫的操作。
4)針對斷點調(diào)試問題:
調(diào)試就是跟蹤程序運行過程中的堆棧信息,由于每個進程都有自己獨立的內(nèi)存空間和各自的堆棧,無法實現(xiàn)在不同的進程間調(diào)試。因此要改為同一進程:調(diào)試時去掉AndroidManifest.xml中android:process標簽,這樣保證調(diào)試狀態(tài)下是在同一進程中,堆棧信息是連貫的。待調(diào)試完成后,再將標簽復原。
</article>