Android 多进程开发
前言
? ? ? ?正常情況下,一個(gè)apk啟動(dòng)后只會(huì)運(yùn)行在一個(gè)進(jìn)程中,其進(jìn)程名為AndroidManifest.xml文件中指定的應(yīng)用包名,所有的基本組件都會(huì)在這個(gè)進(jìn)程中運(yùn)行。但是如果需要將某些組件(如Service、Activity等)運(yùn)行在單獨(dú)的進(jìn)程中,就需要用到android:process屬性了。我們可以為android的基礎(chǔ)組件指定process屬性來指定它們運(yùn)行在指定進(jìn)程中。android使用多進(jìn)程重構(gòu)項(xiàng)目架構(gòu),可以分擔(dān)主進(jìn)程壓力以免因資源消耗過大被crash掉,另外多進(jìn)程相互監(jiān)聽喚醒,可以使應(yīng)用程序長(zhǎng)期駐守后臺(tái)接受即時(shí)消息和通知,防止應(yīng)用被系統(tǒng)回收。下面就總結(jié)一下多進(jìn)程開發(fā)的經(jīng)驗(yàn)及優(yōu)化。
一、多進(jìn)程概念
? ? ? ?一般情況下,一個(gè)應(yīng)用程序就是一個(gè)進(jìn)程,這個(gè)進(jìn)程名稱就是應(yīng)用程序包名。我們知道進(jìn)程是系統(tǒng)分配資源和調(diào)度的基本單位,所以每個(gè)進(jìn)程都有自己獨(dú)立的資源和內(nèi)存空間,別的進(jìn)程是不能任意訪問其他進(jìn)程的內(nèi)存和資源的。
二、多進(jìn)程機(jī)制
? ? ? ?四大組件在AndroidManifest文件中注冊(cè)的時(shí)候,有個(gè)屬性android:process這里可以指定組件的所處的進(jìn)程。
? ? ? ?對(duì)process屬性的設(shè)置有兩種形式:
? ? ? ?第一種形式:如android:process=":remote",以冒號(hào)開頭,冒號(hào)后面的字符串原則上是可以隨意指定的。如果我們的包名為“com.example.processtest”,則實(shí)際的進(jìn)程名為“com.example.processtest:remote”。這種設(shè)置形式表示該進(jìn)程為當(dāng)前應(yīng)用的私有進(jìn)程,其他應(yīng)用的組件不可以和它跑在同一個(gè)進(jìn)程中。
? ? ? ?第二種形式:如android:process="com.example.processtest.remote",以小寫字母開頭,表示運(yùn)行在一個(gè)以這個(gè)名字命名的全局進(jìn)程中,其他應(yīng)用通過設(shè)置相同的ShareUID可以和它跑在同一個(gè)進(jìn)程。
? ? ? ?下面通過一個(gè)例子來進(jìn)行一下驗(yàn)證。我們定義兩個(gè)類:ProcessTestActivity和ProcessTestService,然后在AndroidManifest.xml文件中增加這兩個(gè)類,并為我們的Service指定一個(gè)process屬性,代碼如下:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.processtest" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="19" /> <application android:name="com.example.processtest.MyApplication" android:icon="@drawable/ic_launcher" android:label="@string/app_name"> <activity android:name=".ProcessTestActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".ProcessTestService" android:process=":remote"> </service> </application> </manifest>運(yùn)行代碼,通過DDMS進(jìn)行觀察
? ? ? ?我們可以看到兩個(gè)進(jìn)程,名字分別是“com.example.processtest”和“com.example.processtest:remote”,進(jìn)程ID分別為2722和2739。
? ? ? ?ProcessTestService運(yùn)行在一個(gè)單獨(dú)的私有進(jìn)程中,如果想讓ProcessTestService運(yùn)行在一個(gè)全局進(jìn)程中,那么只需修改android:process="com.example.processtest.remote"即可。Android為每一個(gè)應(yīng)用程序分配了一個(gè)獨(dú)立的虛擬機(jī),或者說為每個(gè)進(jìn)程都分配了一個(gè)獨(dú)立的虛擬機(jī),不同的虛擬機(jī)在內(nèi)存分配上 有不同的地址空間,這就會(huì)導(dǎo)致在不同的虛擬機(jī)中訪問同一類的對(duì)象會(huì)產(chǎn)生多份副本。在一個(gè)進(jìn)程中修改某個(gè)值只會(huì)影響當(dāng)前進(jìn)程,對(duì)其他進(jìn)程不會(huì)造成任何影響。
三、多進(jìn)程優(yōu)點(diǎn)
一般來說,Android應(yīng)用多進(jìn)程有三個(gè)好處:
? ? ? ?1)我們知道Android系統(tǒng)對(duì)每個(gè)應(yīng)用進(jìn)程的內(nèi)存占用是有限制的,而且占用內(nèi)存越大的進(jìn)程,通常被系統(tǒng)殺死的可能性越大。讓一個(gè)組件運(yùn)行在單獨(dú)的進(jìn)程中,可以減少主進(jìn)程所占用的內(nèi)存,降低被系統(tǒng)殺死的概率.
? ? ? ?2)如果子進(jìn)程因?yàn)槟撤N原因崩潰了,不會(huì)直接導(dǎo)致主程序的崩潰,可以降低我們程序的崩潰率。
? ? ? ?3)即使主進(jìn)程退出了,我們的子進(jìn)程仍然可以繼續(xù)工作,假設(shè)子進(jìn)程是推送服務(wù),在主進(jìn)程退出的情況下,仍然能夠保證用戶可以收到推送消息。
四、多進(jìn)程缺點(diǎn)
? ? ? ?我們已經(jīng)開啟了應(yīng)用內(nèi)多進(jìn)程,那么,開啟多進(jìn)程是不是只是我們看到的這么簡(jiǎn)單呢?其實(shí)這里面會(huì)有一些陷阱,稍微不注意就會(huì)陷入其中。我們首先要明確的一點(diǎn)是進(jìn)程間的內(nèi)存空間時(shí)不可見的。
從而,開啟多進(jìn)程后,我們需要面臨這樣幾個(gè)問題:
? ? ? ?1)Application的多次重建。運(yùn)行在同一個(gè)進(jìn)程中的組件是屬于同一個(gè)虛擬機(jī)和同一個(gè)Application的,運(yùn)行在不同進(jìn)程中的組件是屬于兩個(gè)不同的虛擬機(jī)和Application的。
? ? ? ?2)靜態(tài)成員和單例模式完全失效。
? ? ? ?3)文件共享問題。比如SharedPreferences 的可靠性下降。
? ? ? ?4)斷點(diǎn)調(diào)試問題。
1、Application的多次重建
我們先通過一個(gè)簡(jiǎn)單的例子來看一下第一種情況。
? ? ? ?Manifest文件如上面提到的,定義了兩個(gè)類:ProcessTestActivity和ProcessTestService,我們只是在Activity的onCreate方法中直接啟動(dòng)了該Service,同時(shí),我們自定義了自己的Application類。代碼如下:
public class MyApplication extends Application { public static final String TAG = "viclee"; @Override public void onCreate() { super.onCreate(); int pid = android.os.Process.myPid(); Log.d(TAG, "MyApplication onCreate"); Log.d(TAG, "MyApplication pid is " + pid); } } public class ProcessTestActivity extends Activity { public final static String TAG = "viclee"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_process_test); Log.i(TAG, "ProcessTestActivity onCreate"); this.startService(new Intent(this, ProcessTestService.class)); } } public class ProcessTestService extends Service { public static final String TAG = "viclee"; @Override public void onCreate() { Log.i(TAG, "ProcessTestService onCreate"); } @Override public IBinder onBind(Intent arg0) { return null; } } 執(zhí)行上面這段代碼,查看打印信息:? ? ? ?我們發(fā)現(xiàn)MyApplication的onCreate方法調(diào)用了兩次,分別是在啟動(dòng)ProcessTestActivity和ProcessTestService的時(shí)候,而且我們發(fā)現(xiàn)打印出來的pid也不相同。由于通常會(huì)在Application的onCreate方法中做一些全局的初始化操作,它被初始化多次是完全沒有必要的。出現(xiàn)這種情況,是由于即使是通過指定process屬性啟動(dòng)新進(jìn)程的情況下,系統(tǒng)也會(huì)新建一個(gè)獨(dú)立的虛擬機(jī),自然需要重新初始化一遍Application。那么怎么來解決這個(gè)問題呢?
? ? ? ?我們可以通過在自定義的Application中通過進(jìn)程名來區(qū)分當(dāng)前是哪個(gè)進(jìn)程,然后單獨(dú)進(jìn)行相應(yīng)的邏輯處理。
public class MyApplication extends Application { public static final String TAG = "viclee"; @Override public void onCreate() { super.onCreate(); int pid = android.os.Process.myPid(); Log.d(TAG, "MyApplication onCreate"); Log.d(TAG, "MyApplication pid is " + pid); ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.RunningAppProcessInfo> runningApps = am.getRunningAppProcesses(); if (runningApps != null && !runningApps.isEmpty()) { for (ActivityManager.RunningAppProcessInfo procInfo : runningApps) { if (procInfo.pid == pid) { if (procInfo.processName.equals("com.example.processtest")) { Log.d(TAG, "process name is " + procInfo.processName); } else if (procInfo.processName.equals("com.example.processtest:remote")) { Log.d(TAG, "process name is " + procInfo.processName); } } } } } }運(yùn)行之后,查看Log信息:
? ? ? ?圖中可以看出,不同的進(jìn)程執(zhí)行了不同的代碼邏輯,可以通過這種方式來區(qū)分不同的進(jìn)程需要完成的初始化工作。
2、靜態(tài)變量和單例模式完全失效
? ? ? ?下面我們來看第二個(gè)問題,將之前定義的Activity和Service的代碼進(jìn)行簡(jiǎn)單的修改,代碼如下:
public class ProcessTestActivity extends Activity { public final static String TAG = "viclee"; public static boolean processFlag = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_process_test); processFlag = true; Log.i(TAG, "ProcessTestActivity onCreate"); this.startService(new Intent(this, ProcessTestService.class)); } } public class ProcessTestService extends Service { public static final String TAG = "viclee"; @Override public void onCreate() { Log.i(TAG, "ProcessTestService onCreate"); Log.i(TAG, "ProcessTestActivity.processFlag is " + ProcessTestActivity.processFlag); } @Override public IBinder onBind(Intent arg0) { return null; } }重新執(zhí)行代碼,打印Log:? ? ? ?針對(duì)靜態(tài)變量失效的問題,我們可以使用Intent或者aidl等進(jìn)程通訊方式傳遞內(nèi)容。
3、文件共享問題
? ? ? ?第三個(gè)問題是文件共享問題。多進(jìn)程情況下會(huì)出現(xiàn)兩個(gè)進(jìn)程在同一時(shí)刻訪問同一個(gè)數(shù)據(jù)庫(kù)文件的情況。這就可能造成資源的競(jìng)爭(zhēng)訪問,導(dǎo)致諸如數(shù)據(jù)庫(kù)損壞、數(shù)據(jù)丟失等。在多線程的情況下我們有鎖機(jī)制控制資源的共享,但是在多進(jìn)程中比較難,雖然有文件鎖、排隊(duì)等機(jī)制,但是在Android里很難實(shí)現(xiàn)。解決辦法就是多進(jìn)程的時(shí)候不并發(fā)訪問同一個(gè)文件,比如子進(jìn)程涉及到操作數(shù)據(jù)庫(kù),就可以考慮調(diào)用主進(jìn)程進(jìn)行數(shù)據(jù)庫(kù)的操作。
4、斷點(diǎn)調(diào)試問題
? ? ? ?最后是斷點(diǎn)調(diào)試的問題。調(diào)試就是跟蹤程序運(yùn)行過程中的堆棧信息,由于每個(gè)進(jìn)程都有自己獨(dú)立的內(nèi)存空間和各自的堆棧,無法實(shí)現(xiàn)在不同的進(jìn)程間調(diào)試。不過可以通過下面的方式實(shí)現(xiàn):調(diào)試時(shí)去掉AndroidManifest.xml中android:process標(biāo)簽,這樣保證調(diào)試狀態(tài)下是在同一進(jìn)程中,堆棧信息是連貫的。待調(diào)試完成后,再將標(biāo)簽復(fù)原。
五、多進(jìn)程使用場(chǎng)景
? ? ? ?類似音樂類、跑步健身類、手機(jī)管家類等長(zhǎng)時(shí)間需要在后臺(tái)運(yùn)行的應(yīng)用。這些應(yīng)用的特點(diǎn)就是,當(dāng)用戶切到別的應(yīng)用,或者關(guān)掉手機(jī)屏幕的時(shí)候,應(yīng)用本身的核心模塊還在正常運(yùn)行,提供服務(wù)。如果因?yàn)槭謾C(jī)內(nèi)存過低,或者是進(jìn)程重要性降低,導(dǎo)致應(yīng)用被殺掉,后臺(tái)服務(wù)停止,對(duì)于這些應(yīng)用來說,就是滅頂之災(zāi)。合理利用多進(jìn)程,將核心后臺(tái)服務(wù)模塊和其他UI模塊進(jìn)行分離,保證應(yīng)用能更穩(wěn)定的提供服務(wù),從而提升用戶體驗(yàn)。
舉個(gè)例子:
現(xiàn)在要做一款音樂播放器,現(xiàn)在有以下幾種方案:
A. 在Activity中直接播放音樂。
B. 啟動(dòng)后臺(tái)Service,播放音樂。
C. 啟動(dòng)前臺(tái)Service,播放音樂。
D. 在新的進(jìn)程中,啟動(dòng)后臺(tái)Service,播放音樂。
E. 在新的進(jìn)程中,啟動(dòng)前臺(tái)Service,播放音樂。
A. 在Activity中直接播放音樂。
? ? ? ?在A中,我們的播放器是直接在activity中啟動(dòng)的。首先這么做肯定是不對(duì)的,我們需要在后臺(tái)播放音樂,所以當(dāng)activity退出后就播不了了,之所以給出這個(gè)例子是為了控制變量作對(duì)比。
? ? ? ?然后我們來看下A的使用場(chǎng)景。
? ? ? ?音樂播放器無非是打開app,選歌,播放,退到桌面,切其他應(yīng)用。我們選取了三個(gè)場(chǎng)景,打開、按home切換其他應(yīng)用、按back退回桌面。讓我們看一下A的相對(duì)應(yīng)的oom_adj、oom_score、oom_score_adj的值。(下面三張圖依次對(duì)應(yīng)為【打開狀態(tài)】、【按了Home鍵被切換狀態(tài)】、【按了Back鍵被退出狀態(tài)】)
上圖為打開狀態(tài)下oom_adj、oom_score、oom_score_adj的值
上圖為按了Home鍵狀態(tài)下oom_adj、oom_score、oom_score_adj的值
上圖為按了Back鍵狀態(tài)下oom_adj、oom_score、oom_score_adj的值
? ? ? ?當(dāng)我們應(yīng)用在前臺(tái)的時(shí)候,無論adj還是score還是score_adj,他們的值都非常的小,基本不會(huì)被LMK所殺掉,但是當(dāng)我們按了Home之后,進(jìn)程的adj就會(huì)急劇增大,變?yōu)?,相應(yīng)的score和score_adj也會(huì)增大。在上篇文章中我們得知,adj=7即為被切換的進(jìn)程,兩個(gè)進(jìn)程來回切換,上一個(gè)進(jìn)程就會(huì)被設(shè)為7。當(dāng)我們按Back鍵的時(shí)候,adj就會(huì)被設(shè)為9,也就是緩存進(jìn)程,優(yōu)先級(jí)比較低,有很大的幾率被殺掉。
B. 啟動(dòng)后臺(tái)Service,播放音樂。
? ? ? ?B是直接啟動(dòng)一個(gè)后臺(tái)service并且播放音樂,這個(gè)處理看起來比A好了很多,那么實(shí)際上,B的各個(gè)場(chǎng)景的優(yōu)先級(jí)和A又有什么不同呢?讓我們來看下B的對(duì)應(yīng)的打開、切換、退出相應(yīng)的adj、score、score_adj的值。(下面三張圖依次對(duì)應(yīng)為【打開狀態(tài)】、【按了Home鍵被切換狀態(tài)】、【按了Back鍵被退出狀態(tài)】)
上圖為打開狀態(tài)下oom_adj、oom_score、oom_score_adj的值
上圖為按了Home鍵狀態(tài)下oom_adj、oom_score、oom_score_adj的值
上圖為按了Back鍵狀態(tài)下oom_adj、oom_score、oom_score_adj的值
? ? ? ?B的情況其實(shí)是與A類似的,三種狀態(tài)的adj、score_adj的值都是一樣的,只有score有一點(diǎn)出入,其實(shí)分析源碼得知,LMK殺進(jìn)程的時(shí)候,score的左右其實(shí)并不大,所以我們暫時(shí)忽略它。所以,與A相比,他們的adj和score_adj的值都相同,如果遇到內(nèi)存不足的情況下,這兩個(gè)應(yīng)用誰占得內(nèi)存更大,誰就會(huì)被殺掉。不過鑒于A實(shí)在activity中播放音樂,所以B還是比A略好的方案。
C. 啟動(dòng)前臺(tái)Service,播放音樂。
? ? ? ?C的話是啟動(dòng)一個(gè)前臺(tái)Service來播放音樂。讓我們來看一下對(duì)應(yīng)的值。(下面三張圖依次對(duì)應(yīng)為【打開狀態(tài)】、【按了Home鍵被切換狀態(tài)】、【按了Back鍵被退出狀態(tài)】)
上圖為打開狀態(tài)下oom_adj、oom_score、oom_score_adj的值
上圖為按了Home鍵狀態(tài)下oom_adj、oom_score、oom_score_adj的值
上圖為按了Back鍵狀態(tài)下oom_adj、oom_score、oom_score_adj的值
? ? ? ?在前臺(tái)的時(shí)候,和AB是一樣的,adj都是0,當(dāng)切到后臺(tái),或者back結(jié)束時(shí),C對(duì)應(yīng)的adj就是2,也就是可感知進(jìn)程。adj=2可以說是很高優(yōu)先級(jí)了,非root手機(jī),非系統(tǒng)應(yīng)用已經(jīng)沒有辦法將其殺掉了。adj<5的應(yīng)用不會(huì)被殺掉。
? ? ? ?總的來說,C方案比B優(yōu)秀,擁有前臺(tái)Service的C更不容易被系統(tǒng)或者其他應(yīng)用所殺掉了,進(jìn)程的優(yōu)先級(jí)一下子提高到了2,相對(duì)于B來說更穩(wěn)定,用戶體驗(yàn)更好。不過有一點(diǎn)不足是必須啟動(dòng)一個(gè)前臺(tái)service。不過現(xiàn)在大部分的音樂類軟件都會(huì)提供一個(gè)前臺(tái)service,也就不是什么缺點(diǎn)了。其實(shí)也是有灰色方法可以啟動(dòng)一個(gè)不顯示通知的前臺(tái)service,這里就不過多介紹了。
那么還有可改進(jìn)的余地嗎?
答案當(dāng)然是肯定的。
D. 在新的進(jìn)程中,啟動(dòng)后臺(tái)Service,播放音樂。
? ? ? ?終于我們的主角,多進(jìn)程登場(chǎng)了。
? ? ? ?D把應(yīng)用進(jìn)行了拆分,把用于播放音樂的service放到了新的進(jìn)程內(nèi),讓我們看一下對(duì)應(yīng)的值。(下面三張圖依次對(duì)應(yīng)為【打開狀態(tài)】、【按了Home鍵被切換狀態(tài)】、【按了Back鍵被退出狀態(tài)】)
上圖為打開狀態(tài)下oom_adj、oom_score、oom_score_adj的值
上圖為按了Home鍵狀態(tài)下oom_adj、oom_score、oom_score_adj的值
上圖為按了Back鍵狀態(tài)下oom_adj、oom_score、oom_score_adj的值
? ? ? ?上面三張圖對(duì)應(yīng)的是D應(yīng)用主進(jìn)程的ADJ相關(guān)值,我們可以看出來,跟A類似,adj都是0,7,9。由于少了service部分,內(nèi)存使用變少,最后計(jì)算出的oom_score_adj也更低了,意味著主進(jìn)程部分也更不容易被殺死。
下面我們看下拆分出的service的相關(guān)值
上圖為后臺(tái)Service的oom_adj、oom_score、oom_score_adj的值
? ? ? ?因?yàn)槭莝ervice進(jìn)程,所以不受打開,關(guān)閉,切換所影響,這里就放了一張圖。
? ? ? ?我們可以看到,service的adj值一直是5,也就是活躍的服務(wù)進(jìn)程,相比于B來說,優(yōu)先級(jí)高了不少。不過對(duì)于C來說,其實(shí)這個(gè)方案反倒不如C的adj=2的前臺(tái)進(jìn)程更穩(wěn)定。但是D可以自主釋放主進(jìn)程,使D實(shí)際所占用的內(nèi)存很小,從而不容易被殺掉。那么到底C和D誰是更優(yōu)秀的設(shè)計(jì)?我個(gè)人認(rèn)為,在ABCDE這5個(gè)設(shè)計(jì)中,D是最具智慧的設(shè)計(jì),具體是為什么?先賣個(gè)關(guān)子,等我們說完了E,再作總結(jié)。
E. 在新的進(jìn)程中,啟動(dòng)前臺(tái)Service,播放音樂。
? ? ? ?E也是使用了多進(jìn)程,并且在新進(jìn)程中,使用了前臺(tái)service,先來看下對(duì)應(yīng)的值。(下面三張圖依次對(duì)應(yīng)為【打開狀態(tài)】、【按了Home鍵被切換狀態(tài)】、【按了Back鍵被退出狀態(tài)】)
上圖為打開狀態(tài)下oom_adj、oom_score、oom_score_adj的值
上圖為按了Home鍵狀態(tài)下oom_adj、oom_score、oom_score_adj的值
上圖為按了Back鍵狀態(tài)下oom_adj、oom_score、oom_score_adj的值
? ? ? ?這個(gè)不多解釋,和ABD基本差不多,都是0,7,9。我們看下拆分出來的進(jìn)程的值。 上圖為后臺(tái)Service的oom_adj、oom_score、oom_score_adj的值 ? ? ? ?我們可以看到,這個(gè)進(jìn)程的值是2,像C方案,非常小,非常穩(wěn)定,而且,我們還可以在系統(tǒng)進(jìn)入后臺(tái)后,手動(dòng)殺掉主進(jìn)程,使整個(gè)應(yīng)用的內(nèi)存消耗降到最低,內(nèi)存低,優(yōu)先級(jí)又高,E獲得了今天的最穩(wěn)定的方案獎(jiǎng)。? ? ? ?其實(shí)我們可以做個(gè)比喻,把整個(gè)Android系統(tǒng)比喻成一個(gè)旅游景點(diǎn),Low Memory Killer就是景點(diǎn)的門衛(wèi)兼保安,然后我們每個(gè)進(jìn)程的ADJ相當(dāng)于手里的門票,有的人是VIP門票,有的人是普通門票。景點(diǎn)平常沒人的時(shí)候還好,誰拿票都能進(jìn),當(dāng)人逐漸擁擠的時(shí)候,保安就開始根據(jù)票的等級(jí),往外轟人。E方案就是一個(gè)拿著普通票的媽媽,帶著一個(gè)VIP的孩子去參觀,D方案就是一個(gè)拿著普通票的媽媽,帶著一個(gè)拿著中等票的孩子參觀。當(dāng)內(nèi)存不夠的時(shí)候,保安會(huì)先把兩個(gè)媽媽轟出去,孩子們?cè)诶锩婵?#xff0c;再不夠了,就會(huì)把D孩子給轟出去。這么看來,顯然E的效果更好一些,不過由于Android系統(tǒng)對(duì)于VIP票的發(fā)放沒有節(jié)制,大家都可以領(lǐng)VIP票,那也就是相當(dāng)于沒有VIP票了。所以如果E方案是一種精明,那么D才是真正的智慧。將調(diào)度權(quán)還給系統(tǒng),做好自己,維護(hù)好整個(gè)Android生態(tài)。
多模塊應(yīng)用
? ? ? ?多進(jìn)程還有一種非常有用的場(chǎng)景,就是多模塊應(yīng)用。比如我做的應(yīng)用大而全,里面肯定會(huì)有很多模塊,假如有地圖模塊、大圖瀏覽、自定義WebView等等(這些都是吃內(nèi)存大戶),還會(huì)有一些諸如下載服務(wù),監(jiān)控服務(wù)等等,一個(gè)成熟的應(yīng)用一定是多模塊化的。
? ? ? ?首先多進(jìn)程開發(fā)能為應(yīng)用解決了OOM問題,Android對(duì)內(nèi)存的限制是針對(duì)于進(jìn)程的,這個(gè)閾值可以是48M、24M、16M等,視機(jī)型而定,所以,當(dāng)我們需要加載大圖之類的操作,可以在新的進(jìn)程中去執(zhí)行,避免主進(jìn)程OOM。
? ? ? ?多進(jìn)程不光解決OOM問題,還能更有效、合理的利用內(nèi)存。我們可以在適當(dāng)?shù)臅r(shí)候生成新的進(jìn)程,在不需要的時(shí)候及時(shí)殺掉,合理分配,提升用戶體驗(yàn)。減少系統(tǒng)被殺掉的風(fēng)險(xiǎn)。
? ? ? ?多進(jìn)程還能帶來一個(gè)好處就是,單一進(jìn)程崩潰并不影響整體應(yīng)用的使用。例如我在圖片瀏覽進(jìn)程打開了一個(gè)過大的圖片,java heap 申請(qǐng)內(nèi)存失敗,但是不影響我主進(jìn)程的使用,而且,還能通過監(jiān)控進(jìn)程,將這個(gè)錯(cuò)誤上報(bào)給系統(tǒng),告知他在什么機(jī)型、環(huán)境下、產(chǎn)生了什么樣的Bug,提升用戶體驗(yàn)。
? ? ? ?再一個(gè)好處就是,當(dāng)我們的應(yīng)用開發(fā)越來越大,模塊越來越多,團(tuán)隊(duì)規(guī)模也越來越大,協(xié)作開發(fā)也是個(gè)很麻煩的事情。項(xiàng)目解耦,模塊化,是這階段的目標(biāo)。通過模塊解耦,開辟新的進(jìn)程,獨(dú)立的JVM,來達(dá)到數(shù)據(jù)解耦目的。模塊之間互不干預(yù),團(tuán)隊(duì)并行開發(fā),責(zé)任分工也明確。至于模塊化開發(fā)與多進(jìn)程的結(jié)合,后續(xù)會(huì)寫一篇專門的文章來研究這個(gè)問題。
總結(jié)
以上是生活随笔為你收集整理的Android 多进程开发的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何看待自己写的烂代码
- 下一篇: 世界gdp排名2021,中国排名第二