一. 什么是多進(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)程)的名稱(chēng)就是應(yīng)用的包名席赂。
進(jìn)程的特點(diǎn):
進(jìn)程是系統(tǒng)資源和分配的基本單位吮铭,而線(xiàn)程是調(diào)度的基本單位。
每個(gè)進(jìn)程都有自己獨(dú)立的資源和內(nèi)存空間
其它進(jìn)程不能任意訪(fǎng)問(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í)可以分為五類(lèi),優(yōu)先級(jí)從高到低排列:
前臺(tái)進(jìn)程:該進(jìn)程包含正在與用戶(hù)進(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)有和用戶(hù)交互芥牌,但是仍然可以被看到烦味。activity可見(jiàn)的時(shí)候不一定在前臺(tái)。一個(gè)簡(jiǎn)單的例子是前臺(tái)的 activity 使用對(duì)話(huà)框啟動(dòng)了一個(gè)新的 activity 或者一個(gè)透明 activity 壁拉。另一個(gè)例子是當(dāng)調(diào)用運(yùn)行時(shí)權(quán)限對(duì)話(huà)框時(shí)(事實(shí)上它就是一個(gè) activityC怼)柏靶。
服務(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)有與用戶(hù)交互捍靠,用戶(hù)也看不到 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)單,只需要在A(yíng)ndroidManifest.xml的聲明四大組件的標(biāo)簽中增加”android:process”屬性即可歪脏。命名之后疑俭,就成了一個(gè)單獨(dú)的進(jìn)程。
process分私有進(jìn)程和全局進(jìn)程:
-
私有進(jìn)程的名稱(chēng)前面有冒號(hào)婿失,例如:
<service android:name=".MusicService" android:process=":musicservice"/>
-
全局進(jìn)程的名稱(chēng)前面沒(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è)線(xiàn)程是共享該進(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)樗枰嗑€(xiàn)程能力吓蘑,并可能導(dǎo)致一個(gè)更復(fù)雜的實(shí)現(xiàn)。
Messenger:利用Handler實(shí)現(xiàn)坟冲。(適用于多進(jìn)程磨镶、單線(xiàn)程,不需要考慮線(xiàn)程安全)健提,其底層基于A(yíng)IDL琳猫。
使用Messenger
如需讓服務(wù)與遠(yuǎn)程進(jìn)程通信,則可使用Messenger為服務(wù)提供接口私痹。
定義一個(gè)MessengerService繼承自Service脐嫂,并在A(yíng)ndroidManifest.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)自客戶(hù)端的每個(gè)調(diào)用的回調(diào)癞蚕。
Handler用于創(chuàng)建Messenger對(duì)象(對(duì)Handler的引用)蕊爵。
Messenger創(chuàng)建一個(gè)IBinder,服務(wù)通過(guò)onBind()使其返回客戶(hù)端桦山。
客戶(hù)端使用IBinder將Messenger(引用服務(wù)的Handler)實(shí)例化攒射,然后使用后者將Message對(duì)象發(fā)送給服務(wù)。
服務(wù)在其Handler中(具體地講恒水,是在handleMessage()方法中)接收每個(gè)Message会放。
這樣,客戶(hù)端并沒(méi)有調(diào)用服務(wù)的“方法”钉凌。而客戶(hù)端傳遞的“消息”(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è)類(lèi)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; } }
-
在A(yíng)ndroidManifest.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(); } } } }); } }
在A(yíng)ctivity中利用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.前言
在A(yíng)ndroid中,默認(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è)主線(xiàn)程术奖,默認(rèn)的情況下礁遵,所有組件都將運(yùn)行在該進(jìn)程內(nèi)。同一個(gè)應(yīng)用由系統(tǒng)分配一個(gè)獨(dú)立的Linux賬戶(hù)采记,該應(yīng)用產(chǎn)生的所有進(jìn)程佣耐,都會(huì)是這同一個(gè)Linux賬戶(hù)。
2.使用多進(jìn)程
在開(kāi)發(fā)中挺庞,我們通常會(huì)使用修改清單文件的android:process來(lái)達(dá)到多進(jìn)程的目的。如果android:process的value值以冒號(hào)開(kāi)頭的話(huà)稼病,那么該進(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ì)它的依賴(lài)而有所提高。例如進(jìn)程A中的內(nèi)容提供程序?yàn)檫M(jìn)程B中的客戶(hù)端提供服務(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)建工作線(xiàn)程拴竹。例如,正在將圖片上傳到網(wǎng)站的Activity應(yīng)該啟動(dòng)服務(wù)來(lái)執(zhí)行上傳剧罩,這樣一來(lái)栓拜,即使用戶(hù)退出Activity仍可在后臺(tái)繼續(xù)執(zhí)行上傳操作。使用服務(wù)可以保證無(wú)論Activity發(fā)生什么情況該操作至少具備“服務(wù)進(jìn)程”優(yōu)先級(jí)惠昔。同理幕与,廣播接收器也應(yīng)使用服務(wù),而不是簡(jiǎn)單地將耗時(shí)冗長(zhǎng)的操作放入線(xiàn)程中镇防。
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)程,一般是用戶(hù)前一個(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)程,以此類(lèi)推俊庇。
6.什么情況需要使用多進(jìn)程
舉個(gè)例子狮暑,現(xiàn)在要做一款音樂(lè)播放器,現(xiàn)在有以下幾種方案:
A.在A(yíng)ctivity中直接播放音樂(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(yíng)實(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的話(huà)是啟動(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)定姻檀,用戶(hù)體驗(yàn)更好命满。不過(guò)有一點(diǎn)不足是必須啟動(dòng)一個(gè)前臺(tái)service。不過(guò)現(xiàn)在大部分的音樂(lè)類(lèi)軟件都會(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類(lèi)似铸磅,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è)不多解釋?zhuān)虯BD基本差不多泪喊,都是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等等(這些都是吃?xún)?nèi)存大戶(hù))钠惩,一個(gè)成熟的應(yīng)用一定是多模塊化的。首先多進(jìn)程開(kāi)發(fā)能為應(yīng)用解決了OOM問(wèn)題族阅,因?yàn)锳ndroid對(duì)內(nèi)存的限制是針對(duì)于進(jìn)程的篓跛,所以,當(dāng)我們需要加載大圖之類(lèi)的操作坦刀,可以在新的進(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)程名稱(chēng)就是應(yīng)用程序包名咕村。我們知道進(jìn)程是系統(tǒng)分配資源和調(diào)度的基本單位场钉,所以每個(gè)進(jìn)程都有自己獨(dú)立的
資源和內(nèi)存空間,別的進(jìn)程是不能任意訪(fǎng)問(wèn)其他進(jìn)程的內(nèi)存和資源的懈涛。
如何讓自己的應(yīng)用擁有多個(gè)進(jìn)程:
四大組件在A(yíng)ndroidManifest文件中注冊(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)建的話(huà))這個(gè)進(jìn)程,然后再創(chuàng)建該組件指郁。打印出它的進(jìn)程名稱(chēng):重
載Application類(lèi)的onCreate方法即可忙上。
設(shè)置android:process屬性,要注意:如果是android:process=”:deamon”闲坎,以:開(kāi)頭的名字疫粥,表示這是一個(gè)應(yīng)用程序的私有進(jìn)程,否則它是一個(gè)全局進(jìn)程腰懂。私有進(jìn)程的進(jìn)程名稱(chēng)是
會(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)等。
壞處:消耗用戶(hù)的電量菱农。
多占用了系統(tǒng)的空間缭付,若所有應(yīng)用都這樣占用,系統(tǒng)內(nèi)存很容易占滿(mǎn)而導(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的多次重建:
在A(yíng)pplication的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)程名稱(chēng)
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í)刻訪(fǎng)問(wèn)同一個(gè)數(shù)據(jù)庫(kù)文件的情況狠毯。這就可能造成資源的競(jìng)爭(zhēng)訪(fǎng)問(wèn),導(dǎo)致諸如數(shù)據(jù)庫(kù)損壞褥芒、數(shù)據(jù)丟失等嚼松。在多線(xiàn)程的情況下我們有鎖機(jī)制控制資源的共享,但是在多進(jìn)程中比較難锰扶,雖然有文件鎖献酗、排隊(duì)等機(jī)制,但是在A(yíng)ndroid里很難實(shí)現(xiàn)坷牛。解決辦法就是多進(jìn)程的時(shí)候不并發(fā)訪(fǎng)問(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://www.reibang.com/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