一. 什么是多進(jìn)程?
多進(jìn)程就是多個(gè)進(jìn)程的意思,那么什么是進(jìn)程呢?
當(dāng)一個(gè)應(yīng)用在開(kāi)始運(yùn)行時(shí),系統(tǒng)會(huì)為它創(chuàng)建一個(gè)進(jìn)程,一個(gè)應(yīng)用默認(rèn)只有一個(gè)進(jìn)程,這個(gè)進(jìn)程(主進(jìn)程)的名稱就是應(yīng)用的包名。
進(jìn)程的特點(diǎn):
進(jìn)程是系統(tǒng)資源和分配的基本單位,而線程是調(diào)度的基本單位。
每個(gè)進(jìn)程都有自己獨(dú)立的資源和內(nèi)存空間
其它進(jìn)程不能任意訪問(wèn)當(dāng)前進(jìn)程的內(nèi)存和資源
系統(tǒng)給每個(gè)進(jìn)程分配的內(nèi)存會(huì)有限制
根據(jù)上邊的引言和進(jìn)程的特點(diǎn)可以看出,使用多進(jìn)程的場(chǎng)景為:需要使apk所使用的內(nèi)存限制擴(kuò)大。
二. 進(jìn)程的等級(jí)
按優(yōu)先級(jí)可以分為五類,優(yōu)先級(jí)從高到低排列:

前臺(tái)進(jìn)程:該進(jìn)程包含正在與用戶進(jìn)行交互的界面組件,比如一個(gè)Activity。在接收關(guān)鍵生命周期方法時(shí)會(huì)讓一個(gè)進(jìn)程臨時(shí)提升為前臺(tái)進(jìn)程,包括任何服務(wù)的生命周期方法onCreate()和onDestroy()和任何廣播接收器onReceive()方法。這樣做確保了這些組件的操作是有效的原子操作,每個(gè)組件都能執(zhí)行完成而不被殺掉。
可見(jiàn)進(jìn)程:該進(jìn)程中的組件雖然沒(méi)有和用戶交互,但是仍然可以被看到。activity可見(jiàn)的時(shí)候不一定在前臺(tái)。一個(gè)簡(jiǎn)單的例子是前臺(tái)的 activity 使用對(duì)話框啟動(dòng)了一個(gè)新的 activity 或者一個(gè)透明 activity 。另一個(gè)例子是當(dāng)調(diào)用運(yùn)行時(shí)權(quán)限對(duì)話框時(shí)(事實(shí)上它就是一個(gè) activity?。?。
服務(wù)進(jìn)程:該進(jìn)程包含在執(zhí)行后臺(tái)操作的服務(wù)組件,比如播放音樂(lè)的Service。對(duì)于許多在后臺(tái)做處理(如加載數(shù)據(jù))而沒(méi)有立即成為前臺(tái)服務(wù)的應(yīng)用都屬于這種情況。
請(qǐng)?zhí)貏e注意從onStartCommand()返回的常量,如果服務(wù)由于內(nèi)存壓力被殺掉,它表示控制什么發(fā)生什么:
START_STICKY表示希望系統(tǒng)可用的時(shí)候自動(dòng)重啟服務(wù),但不關(guān)心是否能獲得最后一次的 Intent (例如,可以重建自己的狀態(tài)或者控制自己的 start/stop 生命周期)。
START_REDELIVER_INTENT是為那些在被殺死之后重啟時(shí)重新獲得 Intent 的服務(wù)的,直到用傳遞給 onStartCommand() 方法的 startId 參數(shù)調(diào)用stopSelf()為止。這里會(huì)使用 Intent 和 startId 作為隊(duì)列完成工作。
START_NOT_STICKY用于那些殺掉也沒(méi)關(guān)系的服務(wù)。這適合那些管理周期性任務(wù)的服務(wù),它們只是等待下一個(gè)時(shí)間窗口工作。后臺(tái)進(jìn)程:該進(jìn)程包含的組件沒(méi)有與用戶交互,用戶也看不到 Service。在一般操作場(chǎng)景下,設(shè)備上的許多內(nèi)存就是用在這上面的,使可以重新回到之前打開(kāi)過(guò)的某個(gè) activity 。
空進(jìn)程:沒(méi)有任何界面組件、服務(wù)組件,或觸發(fā)器組件,只是出于緩存的目的而被保留(為了更加有效地使用內(nèi)存而不是完全釋放掉),只要 Android 需要可以隨時(shí)殺掉它們。
三. 多進(jìn)程的創(chuàng)建
Android多進(jìn)程創(chuàng)建很簡(jiǎn)單,只需要在AndroidManifest.xml的聲明四大組件的標(biāo)簽中增加”android:process”屬性即可。命名之后,就成了一個(gè)單獨(dú)的進(jìn)程。
process分私有進(jìn)程和全局進(jìn)程:
-
私有進(jìn)程的名稱前面有冒號(hào),例如:
<service android:name=".MusicService" android:process=":musicservice"/> -
全局進(jìn)程的名稱前面沒(méi)有冒號(hào),例如:
<service android:name=".MusicService" android:process="com.trampcr.musicdemo.service"/>
為了節(jié)省系統(tǒng)內(nèi)存,在退出該Activity的時(shí)候可以將其殺掉(如果沒(méi)有人為殺掉該進(jìn)程,在程序完全退出時(shí)該進(jìn)程會(huì)被系統(tǒng)殺掉)。
多進(jìn)程被創(chuàng)建好了,應(yīng)用運(yùn)行時(shí)就會(huì)對(duì)進(jìn)程進(jìn)行初始化,如果一個(gè)application中有多個(gè)進(jìn)程,在進(jìn)行全局初始化時(shí),多進(jìn)程會(huì)被初始化多次。
解決辦法:判斷當(dāng)前進(jìn)程,然后做相應(yīng)的初始化操作。
四. 多進(jìn)程間的通信IPC
IPC:InterProcess Communication,即進(jìn)程間通信。
我們知道,同一個(gè)進(jìn)程的多個(gè)線程是共享該進(jìn)程的所有資源,但多個(gè)進(jìn)程間內(nèi)存是不可見(jiàn)的,也就是說(shuō)多個(gè)進(jìn)程間內(nèi)存是不共享的。那么進(jìn)程間是如何進(jìn)行通信的呢?
Android中提供了三種方法:
系統(tǒng)實(shí)現(xiàn)。
AIDL(Android Interface Definition Language,Android接口定義語(yǔ)言):大部分應(yīng)用程序不應(yīng)該使用AIDL去創(chuàng)建一個(gè)綁定服務(wù),因?yàn)樗枰嗑€程能力,并可能導(dǎo)致一個(gè)更復(fù)雜的實(shí)現(xiàn)。
Messenger:利用Handler實(shí)現(xiàn)。(適用于多進(jìn)程、單線程,不需要考慮線程安全),其底層基于AIDL。
使用Messenger
如需讓服務(wù)與遠(yuǎn)程進(jìn)程通信,則可使用Messenger為服務(wù)提供接口。
定義一個(gè)MessengerService繼承自Service,并在AndroidManifest.xml中聲明并給一個(gè)進(jìn)程名,使該服務(wù)成為一個(gè)單獨(dú)的進(jìn)程。代碼如下:
MessengerService.java
public class MessengerService extends Service{
class IncomingHandler extends Handler{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 0:
Toast.makeText(getApplicationContext(), "hello, trampcr", Toast.LENGTH_SHORT).show();
break;
}
}
}
Messenger mMessenger = new Messenger(new IncomingHandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
return mMessenger.getBinder();
}
}
AndroidManifest.xml文件的配置如下:
<service android:name=".MessengerService"
android:process="com.trampcr.messenger.service"/>
MessengerActivity.java
public class MessengerActivity extends Activity{
private boolean mBound;
private Messenger mMessenger;
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mMessenger = new Messenger(service);
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
mMessenger = null;
mBound = false;
}
};
public void sayHello(View v){
if(!mBound){
return;
}
Message msg = Message.obtain(null, 0 , 0, 0);
try {
mMessenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger);
}
@Override
protected void onStart() {
super.onStart();
Intent intent = new Intent(MessengerActivity.this, MessengerService.class);
bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
if(mBound){
unbindService(mServiceConnection);
mBound = false;
}
}
}
通過(guò)以上代碼,可以看到Messenger的使用方法:
服務(wù)實(shí)現(xiàn)一個(gè)Handler,由其接收來(lái)自客戶端的每個(gè)調(diào)用的回調(diào)。
Handler用于創(chuàng)建Messenger對(duì)象(對(duì)Handler的引用)。
Messenger創(chuàng)建一個(gè)IBinder,服務(wù)通過(guò)onBind()使其返回客戶端。
客戶端使用IBinder將Messenger(引用服務(wù)的Handler)實(shí)例化,然后使用后者將Message對(duì)象發(fā)送給服務(wù)。
服務(wù)在其Handler中(具體地講,是在handleMessage()方法中)接收每個(gè)Message。
這樣,客戶端并沒(méi)有調(diào)用服務(wù)的“方法”。而客戶端傳遞的“消息”(Message對(duì)象)是服務(wù)在其Handler中接收的。
以上代碼實(shí)現(xiàn)的應(yīng)用,剛打開(kāi)會(huì)彈出一個(gè)binding,binding表示打開(kāi)應(yīng)用Activity就通過(guò)Messenger連接了一個(gè)服務(wù)進(jìn)程,然后點(diǎn)擊say hello會(huì)彈出hello,trampcr,這表示了Activity通過(guò)Messenger將Message發(fā)送給了服務(wù)進(jìn)程。如下圖:

使用AIDL
AIDL是一種接口描述語(yǔ)言,通常用于進(jìn)程間通信。
使用AIDL的步驟:
-
創(chuàng)建AIDL,在main下新建一個(gè)文件夾aidl,然后在aidl下新建AIDL文件,這時(shí)系統(tǒng)會(huì)自動(dòng)為該文件創(chuàng)建一個(gè)包名。
aidl文件中會(huì)有一個(gè)默認(rèn)的basicType方法,我們?yōu)樗黾右粋€(gè)getName方法。代碼如下:interface IMyAidlInterface { /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString); String getName(String nickName); }以上是我們自己創(chuàng)建的aidl文件,系統(tǒng)還會(huì)自動(dòng)生成aidl代碼,所在位置為:build/generated/source/aidl下debug和release,但是此時(shí)debug下沒(méi)有任何東西,可以rebuild或運(yùn)行一下程序,再次打開(kāi)debug,發(fā)現(xiàn)生成了一個(gè)包和一個(gè)aidl文件。
-
在java下新建一個(gè)類AIDLService繼承自Service。代碼如下:
public class AIDLService extends Service { IMyAidlInterface.Stub mStub = new IMyAidlInterface.Stub() { @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { } @Override public String getName(String nickName) throws RemoteException { return "aidl " + nickName; } }; @Nullable @Override public IBinder onBind(Intent intent) { return mStub; } } -
在AndroidManifest.xml中注冊(cè),并給一個(gè)進(jìn)程名,是該服務(wù)成為一個(gè)獨(dú)立的進(jìn)程。
<service android:name=".AIDLService" android:process="com.aidl.test.service"/> -
在MainActivity中進(jìn)行與AIDLService之間的進(jìn)程間通信。代碼如下:
public class MainActivity extends AppCompatActivity { private Button mBtnAidl; private IMyAidlInterface mIMyAidlInterface; ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mIMyAidlInterface = IMyAidlInterface.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mBtnAidl = (Button) findViewById(R.id.btn_aidl); bindService(new Intent(MainActivity.this, AIDLService.class), mServiceConnection, BIND_AUTO_CREATE); mBtnAidl.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(mIMyAidlInterface != null){ try { String name = mIMyAidlInterface.getName("I'm nick"); Toast.makeText(MainActivity.this, "name = " + name, Toast.LENGTH_SHORT).show(); } catch (RemoteException e) { e.printStackTrace(); } } } }); } }
在Activity中利用bindService與AIDLService進(jìn)行連接,通過(guò)IMyAidlInterface實(shí)例與AIDLService進(jìn)程進(jìn)行通信,如下圖所示:

五.序列化插件
Parcelable code generate:自動(dòng)生成實(shí)現(xiàn)了Parcelable接口的對(duì)象。
六 .Android使用場(chǎng)景介紹
0.前言
在Android中,默認(rèn)情況下,同一應(yīng)用的所有組件均運(yùn)行在同一進(jìn)程中,且大多數(shù)應(yīng)用都不會(huì)改變這一點(diǎn)。不過(guò),單進(jìn)程開(kāi)發(fā)并不是Android應(yīng)用的全部,今天我們就來(lái)說(shuō)說(shuō)Android中的多進(jìn)程開(kāi)發(fā)以及多進(jìn)程的使用場(chǎng)景。
1.進(jìn)程
我們都知道Android系統(tǒng)是基于Linux改造而來(lái)的,進(jìn)程系統(tǒng)也是一脈相承,進(jìn)程其實(shí)就是程序的具體實(shí)現(xiàn)。當(dāng)程序第一次啟動(dòng),Android會(huì)啟動(dòng)一個(gè)Linux進(jìn)程(具體由Zygote fork出來(lái))以及一個(gè)主線程,默認(rèn)的情況下,所有組件都將運(yùn)行在該進(jìn)程內(nèi)。同一個(gè)應(yīng)用由系統(tǒng)分配一個(gè)獨(dú)立的Linux賬戶,該應(yīng)用產(chǎn)生的所有進(jìn)程,都會(huì)是這同一個(gè)Linux賬戶。
2.使用多進(jìn)程
在開(kāi)發(fā)中,我們通常會(huì)使用修改清單文件的android:process來(lái)達(dá)到多進(jìn)程的目的。如果android:process的value值以冒號(hào)開(kāi)頭的話,那么該進(jìn)程就是私有進(jìn)程,如果是以其他字符開(kāi)頭,那么就是公有進(jìn)程,這樣擁有相同ShareUID的不同應(yīng)用可以跑在同一進(jìn)程里。至于創(chuàng)建進(jìn)程的具體源碼分析,網(wǎng)上有一篇很詳細(xì)的文章,在這就不重復(fù)造輪子了,有需要的朋友可以前往理解Android進(jìn)程創(chuàng)建流程。
還有一種方法開(kāi)啟進(jìn)程,是通過(guò)JNI利用C/C++,調(diào)用fork()方法來(lái)生成子進(jìn)程,一般開(kāi)發(fā)者會(huì)利用這種方法來(lái)做一些daemon進(jìn)程,來(lái)實(shí)現(xiàn)防殺?;畹刃Ч?。
3.進(jìn)程優(yōu)先級(jí)
Android利用重要性層次結(jié)構(gòu),就是將最重要的保留,殺掉不重要的進(jìn)程。
Android將重要性層次結(jié)構(gòu)分為5個(gè)層級(jí),具體可以查看Android開(kāi)發(fā)——Android進(jìn)程保活招式大全中1.1部分的內(nèi)容,這里就不贅述了。
根據(jù)進(jìn)程中當(dāng)前活動(dòng)組件的重要程度,Android會(huì)將進(jìn)程評(píng)定為它可能達(dá)到的最高級(jí)別。例如,如果某進(jìn)程托管著服務(wù)和可見(jiàn)Activity,則會(huì)將此進(jìn)程評(píng)定為可見(jiàn)進(jìn)程,而不是服務(wù)進(jìn)程。
此外,一個(gè)進(jìn)程的級(jí)別可能會(huì)因其他進(jìn)程對(duì)它的依賴而有所提高。例如進(jìn)程A中的內(nèi)容提供程序?yàn)檫M(jìn)程B中的客戶端提供服務(wù),或者進(jìn)程A中的服務(wù)綁定到進(jìn)程B中的組件,則進(jìn)程A始終被視為至少與進(jìn)程B同樣重要。
由于運(yùn)行服務(wù)的進(jìn)程其級(jí)別高于托管后臺(tái)Activity的進(jìn)程,因此啟動(dòng)長(zhǎng)時(shí)間運(yùn)行操作的Activity最好為該操作啟動(dòng)服務(wù),而不是簡(jiǎn)單地創(chuàng)建工作線程。例如,正在將圖片上傳到網(wǎng)站的Activity應(yīng)該啟動(dòng)服務(wù)來(lái)執(zhí)行上傳,這樣一來(lái),即使用戶退出Activity仍可在后臺(tái)繼續(xù)執(zhí)行上傳操作。使用服務(wù)可以保證無(wú)論Activity發(fā)生什么情況該操作至少具備“服務(wù)進(jìn)程”優(yōu)先級(jí)。同理,廣播接收器也應(yīng)使用服務(wù),而不是簡(jiǎn)單地將耗時(shí)冗長(zhǎng)的操作放入線程中。
4. Low Memory Killer
Android的Low Memory Killer基于Linux的OOM機(jī)制,Low Memory Killer會(huì)根據(jù)進(jìn)程的adj級(jí)別以及所占的內(nèi)存,來(lái)決定是否殺掉該進(jìn)程,adj越大,占用內(nèi)存越多,進(jìn)程越容易被殺掉。
關(guān)于adj的分級(jí),我們可以參考ProcessList.java,這里的常量定義了ADJ的分級(jí)。
UNKNOWN_ADJ = 16
//級(jí)別最低級(jí)的進(jìn)程,通常是被緩存的進(jìn)程,但是系統(tǒng)也不清楚緩存的內(nèi)容
CACHED_APP_MAX_ADJ = 15
//這是一個(gè)只托管不可見(jiàn)的活動(dòng)的進(jìn)程,因此可以在沒(méi)有任何中斷的情況下被殺死
CACHED_APP_MIN_ADJ = 9
//緩存進(jìn)程,沒(méi)有英文解釋
SERVICE_B_ADJ = 8
//不活躍的服務(wù),不像adj=5的服務(wù)那么活躍
//在root以后,有的系統(tǒng)優(yōu)化大師會(huì)把所有服務(wù)都調(diào)成adj=8來(lái)達(dá)到內(nèi)存優(yōu)化的目的
//因?yàn)楫?dāng)所有人adj都比較高時(shí),這樣才能保證名正言順的殺進(jìn)程
PREVIOUS_APP_ADJ = 7
//被切換的進(jìn)程,一般是用戶前一個(gè)使用的進(jìn)程。兩個(gè)應(yīng)用來(lái)回切換,那么前一個(gè)應(yīng)用一般adj設(shè)置為7
HOME_APP_ADJ = 6
//與主應(yīng)用程序有交互的進(jìn)程
SERVICE_ADJ = 5
//活躍的服務(wù)進(jìn)程
HEAVY_WEIGHT_APP_ADJ = 4
//高權(quán)重進(jìn)程
BACKUP_APP_ADJ = 3
//正在備份的進(jìn)程
PERCEPTIBLE_APP_ADJ = 2
//可感知進(jìn)程,通常是前臺(tái)Service進(jìn)程
VISIBLE_APP_ADJ = 1
//可見(jiàn)進(jìn)程
FOREGROUND_APP_ADJ = 0
//前臺(tái)進(jìn)程
剩下的就是adj值為負(fù)數(shù)的進(jìn)程,基本上都是系統(tǒng)集成,不在本文的討論范圍內(nèi)。負(fù)數(shù)進(jìn)程是不會(huì)被lmk殺掉的。
5.如何查看進(jìn)程優(yōu)先級(jí)和設(shè)備的內(nèi)存臨界值
首先通過(guò)adb shell ps指令查找對(duì)應(yīng)進(jìn)程的pid。
然后通過(guò)adb shell cat /proc/${pid}/oom_adj(設(shè)備需要root)返回對(duì)應(yīng)進(jìn)程的adj值。
我們可以通過(guò)adb shell cat查看下面兩個(gè)文件,cat之前請(qǐng)先用chmod賦予權(quán)限,adj代表的是oom_score_adj的值,對(duì)應(yīng)的minfree則代表內(nèi)存臨界值。
/sys/module/lowmemorykiller/parameters/adj
/sys/module/lowmemorykiller/parameters/minfree
比如我的測(cè)試機(jī)小米4C測(cè)試機(jī)對(duì)應(yīng)的值就是:
adj: 0,58,117,176,529,1000
這個(gè)值其實(shí)是oom_score_adj的值,用這個(gè)值17再除1000四舍五入取整數(shù),就是對(duì)應(yīng)的adj的值,例如第二個(gè)值58即為5817/1000 = 1,對(duì)應(yīng)的adj也就是1,1000默認(rèn)就是15。所以這6個(gè)值對(duì)應(yīng)的adj是0,1,2,3,9,15。
minfree: 18432,23040,27648,32256,56250,81250
這個(gè)值是頁(yè)值,一頁(yè)等于4KB,換算成MB大概是72,90,108,126,220,318
當(dāng)可用內(nèi)存小于318MB的時(shí)候,系統(tǒng)開(kāi)始?xì)dj=15的進(jìn)程,以此類推。
6.什么情況需要使用多進(jìn)程
舉個(gè)例子,現(xiàn)在要做一款音樂(lè)播放器,現(xiàn)在有以下幾種方案:
A.在Activity中直接播放音樂(lè)。
B.啟動(dòng)后臺(tái)Service,播放音樂(lè)。
C.啟動(dòng)前臺(tái)Service,播放音樂(lè)。
D.在新的進(jìn)程中,啟動(dòng)后臺(tái)Service,播放音樂(lè)。
E.在新的進(jìn)程中,啟動(dòng)前臺(tái)Service,播放音樂(lè)。
A方案
我們的播放器是直接在activity中啟動(dòng)的。首先這么做肯定是不對(duì)的,我們需要在后臺(tái)播放音樂(lè),所以當(dāng)activity退出后就播不了了,之所以給出這個(gè)例子是為了控制變量作對(duì)比。
音樂(lè)播放器無(wú)非是打開(kāi)app,選歌,播放,退到桌面,切其他應(yīng)用。我們選取了三個(gè)場(chǎng)景,打開(kāi)、按home、按back退回桌面。讓我們看一下A的相對(duì)應(yīng)的oom_adj、oom_score、oom_score_adj的值。



從上述三個(gè)場(chǎng)景的結(jié)果來(lái)看,當(dāng)我們應(yīng)用在前臺(tái)的時(shí)候,無(wú)論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)程來(lái)回切換,上一個(gè)進(jìn)程就會(huì)被設(shè)為7。當(dāng)我們按Back鍵的時(shí)候,adj就會(huì)被設(shè)為9,也就是緩存進(jìn)程,優(yōu)先級(jí)比較低,有很大的幾率被殺掉。
B方案
B直接啟動(dòng)一個(gè)后臺(tái)service并播放音樂(lè),讓我們來(lái)看下B的對(duì)應(yīng)的打開(kāi)、按下Home切換、按下Back退出相應(yīng)的adj、score、score_adj的值。



三種狀態(tài)的adj、score_adj的值和A都是一樣的,只有score有一點(diǎn)出入,其實(shí)分析源碼得知,LMK殺進(jìn)程的時(shí)候,score的影響其實(shí)并不大,所以我們暫時(shí)忽略它。所以adj和score_adj的值都相同卻內(nèi)存不足的情況下,這兩個(gè)應(yīng)用誰(shuí)占得內(nèi)存更大,誰(shuí)就會(huì)被殺掉。不過(guò)鑒于A實(shí)在activity中播放音樂(lè),所以B還是比A略好的方案。
這里有朋友肯定要問(wèn)了,為什么切到后臺(tái)后,adj的值是7而不是5,后臺(tái)不是還有service在跑嗎?
我們通過(guò)查看源碼可以找出來(lái),當(dāng)切換Home的時(shí)候,會(huì)調(diào)用ActivityStack.java的finishCurrentActivityLocked函數(shù),然后調(diào)用到了ActivityManagerService.java的computeOomAdjLocked函數(shù),在這里對(duì)進(jìn)程的ADJ值進(jìn)行重新計(jì)算。當(dāng)進(jìn)程為PreviousProcess情況,則ADJ=7。具體的計(jì)算流程請(qǐng)看computeOomAdjLocked計(jì)算流程。
if (app == mPreviousProcess && app.activities.size() > 0) {
if (adj > ProcessList.PREVIOUS_APP_ADJ) {
adj = ProcessList.PREVIOUS_APP_ADJ;
schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
app.cached = false;
app.adjType = "previous";
}
if (procState > ActivityManager.PROCESS_STATE_LAST_ACTIVITY) {
procState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
}
}
C方案
C的話是啟動(dòng)一個(gè)前臺(tái)Service來(lái)播放音樂(lè)。讓我們來(lái)看一下對(duì)應(yīng)的值。



在前臺(tái)的時(shí)候,和AB是一樣的,adj都是0,當(dāng)切到后臺(tái),或者back結(jié)束時(shí),C對(duì)應(yīng)的adj都是2,也就是可感知進(jìn)程。adj=2可以說(shuō)是很高優(yōu)先級(jí)了。一般adj<5的應(yīng)用不會(huì)被殺掉。因此總的來(lái)說(shuō),C方案相對(duì)于B來(lái)說(shuō)更穩(wěn)定,用戶體驗(yàn)更好。不過(guò)有一點(diǎn)不足是必須啟動(dòng)一個(gè)前臺(tái)service。不過(guò)現(xiàn)在大部分的音樂(lè)類軟件都會(huì)提供一個(gè)前臺(tái)service,也就不是什么缺點(diǎn)了。
D****方案
D把應(yīng)用進(jìn)行了拆分,把用于播放音樂(lè)的service放到了新的進(jìn)程內(nèi),讓我們看一下對(duì)應(yīng)的值。



上面三張圖對(duì)應(yīng)的是D應(yīng)用主進(jìn)程的ADJ相關(guān)值,我們可以看出來(lái),跟A類似,adj都是0,7,9。由于少了service部分,內(nèi)存使用變少,最后計(jì)算出的oom_score_adj也更低了,意味著主進(jìn)程部分也更不容易被殺死。下面我們看下拆分出的service的相關(guān)值。

因?yàn)槭莝ervice進(jìn)程,所以不受打開(kāi),關(guān)閉,切換所影響,我們可以看到service的adj值一直會(huì)是5,也就是活躍的服務(wù)進(jìn)程,相比于B來(lái)說(shuō),優(yōu)先級(jí)高了不少。不過(guò)對(duì)于C來(lái)說(shuō),其實(shí)這個(gè)方案反倒不如C的adj=2的前臺(tái)進(jìn)程更穩(wěn)定。但是D可以自主釋放主進(jìn)程,使D實(shí)際所占用的內(nèi)存很小,從而不容易被殺掉。C、D各有利弊。
E方案
E也是使用了多進(jìn)程,并且在新進(jìn)程中,使用了前臺(tái)service,先來(lái)看下對(duì)應(yīng)的值。



這個(gè)不多解釋,和ABD基本差不多,都是0,7,9。我們看下拆分出來(lái)的進(jìn)程的值。

我們可以看到,這個(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)”。
7.使用多進(jìn)程的其他場(chǎng)景補(bǔ)充
多進(jìn)程還有一種非常有用的場(chǎng)景,就是多模塊應(yīng)用。比如我做的應(yīng)用大而全,里面肯定會(huì)有很多模塊,假如有地圖模塊、大圖瀏覽、自定義WebView等等(這些都是吃內(nèi)存大戶),一個(gè)成熟的應(yīng)用一定是多模塊化的。首先多進(jìn)程開(kāi)發(fā)能為應(yīng)用解決了OOM問(wèn)題,因?yàn)锳ndroid對(duì)內(nèi)存的限制是針對(duì)于進(jìn)程的,所以,當(dāng)我們需要加載大圖之類的操作,可以在新的進(jìn)程中去執(zhí)行,避免主進(jìn)程O(píng)OM。而且假如圖片瀏覽進(jìn)程打開(kāi)了一個(gè)過(guò)大的圖片,java heap申請(qǐng)內(nèi)存失敗,該進(jìn)程崩潰并不影響我主進(jìn)程的使用。
Android開(kāi)發(fā)中怎樣用多進(jìn)程、用多進(jìn)程的好處、多進(jìn)程的缺陷、解決方法
1.怎樣用多進(jìn)程
Android多進(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)程是不能任意訪問(wèn)其他進(jìn)程的內(nèi)存和資源的。
如何讓自己的應(yīng)用擁有多個(gè)進(jìn)程:
四大組件在AndroidManifest文件中注冊(cè)的時(shí)候,有個(gè)屬性android:process這里可以指定組件的所處的進(jìn)程。
默認(rèn)就是應(yīng)用的主進(jìn)程。指定為別的進(jìn)程之后,系統(tǒng)在啟動(dòng)這個(gè)組件的時(shí)候,就先創(chuàng)建(如果還沒(méi)創(chuàng)建的話)這個(gè)進(jìn)程,然后再創(chuàng)建該組件。打印出它的進(jìn)程名稱:重
載Application類的onCreate方法即可。
設(shè)置android:process屬性,要注意:如果是android:process=”:deamon”,以:開(kāi)頭的名字,表示這是一個(gè)應(yīng)用程序的私有進(jìn)程,否則它是一個(gè)全局進(jìn)程。私有進(jìn)程的進(jìn)程名稱是
會(huì)在冒號(hào)前自動(dòng)加上包名,而全局進(jìn)程則不會(huì)。一般我們都是有私有進(jìn)程,很少使用全局進(jìn)程。
2.用多進(jìn)程的好處
好處:
(1)分擔(dān)主進(jìn)程的內(nèi)存壓力。
當(dāng)應(yīng)用越做越大,內(nèi)存越來(lái)越多,將一些獨(dú)立的組件放到不同的進(jìn)程,它就不占用主進(jìn)程的內(nèi)存空間了。當(dāng)然還有其他好處,有心人會(huì)發(fā)現(xiàn)
(2)使應(yīng)用常駐后臺(tái),防止主進(jìn)程被殺守護(hù)進(jìn)程,守護(hù)進(jìn)程和主進(jìn)程之間相互監(jiān)視,有一方被殺就重新啟動(dòng)它。
Android后臺(tái)進(jìn)程里有很多應(yīng)用是多個(gè)進(jìn)程的,因?yàn)樗鼈円qv后臺(tái),特別是即時(shí)通訊或者社交應(yīng)用,不過(guò)現(xiàn)在多進(jìn)程已經(jīng)被用爛了。
典型用法是在啟動(dòng)一個(gè)不可見(jiàn)的輕量級(jí)私有進(jìn)程,在后臺(tái)收發(fā)消息,或者做一些耗時(shí)的事情,或者開(kāi)機(jī)啟動(dòng)這個(gè)進(jìn)程,然后做監(jiān)聽(tīng)等。
壞處:消耗用戶的電量。
多占用了系統(tǒng)的空間,若所有應(yīng)用都這樣占用,系統(tǒng)內(nèi)存很容易占滿而導(dǎo)致卡頓。
應(yīng)用程序架構(gòu)會(huì)變得復(fù)雜,因?yàn)橐幚矶噙M(jìn)程之間的通信。這里又是另外一個(gè)問(wèn)題了。
3.多進(jìn)程的缺陷
進(jìn)程間的內(nèi)存空間是不可見(jiàn)的。開(kāi)啟多進(jìn)程后,會(huì)引發(fā)以下問(wèn)題:
1)Application的多次重建。
2)靜態(tài)成員的失效。
3)文件共享問(wèn)題。
4)斷點(diǎn)調(diào)試問(wèn)題。
4.解決方法
1)針對(duì)Application的多次重建:
在Application的onCreate中獲取進(jìn)程Id來(lái)判斷不同進(jìn)程,然后做不同的事情。
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
//獲取進(jìn)程Id
int pid = android.os.Process.myPid();
Log.e("m_tag", "MyApplication onCreate pid is " + pid); //根據(jù)進(jìn)程id獲取進(jìn)程名稱
String pName = getProcessName(this,pid);
if("com.xyy.processtest".equals(pName)){
//處理該進(jìn)程的業(yè)務(wù)
}
}
}
public String getProcessName(Context cxt, int pid) {
ActivityManager am = (ActivityManager)
cxt.getSystemService(Context.ACTIVITY_SERVICE);
List runningApps = am.getRunningAppProcesses();
if (runningApps == null) {
return null;
}
for (RunningAppProcessInfo procInfo : runningApps) {
if (procInfo.pid == pid) {
return procInfo.processName;
}
}
return null;
}
2)針對(duì)靜態(tài)成員的失效:
使用Intent或者aidl等進(jìn)程通訊方式傳遞內(nèi)容,不能用靜態(tài)或單例模式。
3)針對(duì)文件共享問(wèn)題:
多進(jìn)程情況下會(huì)出現(xiàn)兩個(gè)進(jìn)程在同一時(shí)刻訪問(wèn)同一個(gè)數(shù)據(jù)庫(kù)文件的情況。這就可能造成資源的競(jìng)爭(zhēng)訪問(wèn),導(dǎo)致諸如數(shù)據(jù)庫(kù)損壞、數(shù)據(jù)丟失等。在多線程的情況下我們有鎖機(jī)制控制資源的共享,但是在多進(jìn)程中比較難,雖然有文件鎖、排隊(duì)等機(jī)制,但是在Android里很難實(shí)現(xiàn)。解決辦法就是多進(jìn)程的時(shí)候不并發(fā)訪問(wèn)同一個(gè)文件,比如子進(jìn)程涉及到操作數(shù)據(jù)庫(kù),就可以考慮調(diào)用主進(jìn)程進(jìn)行數(shù)據(jù)庫(kù)的操作。
4)針對(duì)斷點(diǎn)調(diào)試問(wèn)題:
調(diào)試就是跟蹤程序運(yùn)行過(guò)程中的堆棧信息,由于每個(gè)進(jìn)程都有自己獨(dú)立的內(nèi)存空間和各自的堆棧,無(wú)法實(shí)現(xiàn)在不同的進(jìn)程間調(diào)試。因此要改為同一進(jìn)程:調(diào)試時(shí)去掉AndroidManifest.xml中android:process標(biāo)簽,這樣保證調(diào)試狀態(tài)下是在同一進(jìn)程中,堆棧信息是連貫的。待調(diào)試完成后,再將標(biāo)簽復(fù)原。
參考:http://m.itdecent.cn/p/ce1e35c84134
http://blog.csdn.net/seu_calvin/article/details/5393217
http://blog.csdn.net/SPENCER_HALE/article/details/54968092 http://blog.csdn.net/Simon_Crystin/article/details/70315106