Android多進(jìn)程間通信筆記

一. 什么是多進(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í)從高到低排列:

image
  1. 前臺(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í)行完成而不被殺掉。

  2. 可見(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怼)柏靶。

  3. 服務(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í)間窗口工作谢翎。

  4. 后臺(tái)進(jìn)程:該進(jìn)程包含的組件沒(méi)有與用戶(hù)交互捍靠,用戶(hù)也看不到 Service。在一般操作場(chǎng)景下森逮,設(shè)備上的許多內(nèi)存就是用在這上面的榨婆,使可以重新回到之前打開(kāi)過(guò)的某個(gè) activity 。

  5. 空進(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的使用方法:

  1. 服務(wù)實(shí)現(xiàn)一個(gè)Handler,由其接收來(lái)自客戶(hù)端的每個(gè)調(diào)用的回調(diào)癞蚕。

  2. Handler用于創(chuàng)建Messenger對(duì)象(對(duì)Handler的引用)蕊爵。

  3. Messenger創(chuàng)建一個(gè)IBinder,服務(wù)通過(guò)onBind()使其返回客戶(hù)端桦山。

  4. 客戶(hù)端使用IBinder將Messenger(引用服務(wù)的Handler)實(shí)例化攒射,然后使用后者將Message對(duì)象發(fā)送給服務(wù)。

  5. 服務(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)程。如下圖:

image

使用AIDL

AIDL是一種接口描述語(yǔ)言捣鲸,通常用于進(jìn)程間通信瑟匆。

使用AIDL的步驟:

  1. 創(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文件。

  2. 在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;
     }
    }
    
  3. 在A(yíng)ndroidManifest.xml中注冊(cè)只锻,并給一個(gè)進(jìn)程名,是該服務(wù)成為一個(gè)獨(dú)立的進(jìn)程紫谷。

    <service android:name=".AIDLService"   
             android:process="com.aidl.test.service"/>
    
  4. 在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)行通信笤昨,如下圖所示:

image

五.序列化插件


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的值。

image
image
image

從上述三個(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的值。

image
image
image

三種狀態(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)的值佑刷。

image
image
image

在前臺(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)的值诈唬。

image
image
image

上面三張圖對(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)值。

image

因?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)的值漂辐。

image
image
image

這個(gè)不多解釋?zhuān)虯BD基本差不多泪喊,都是0,7者吁,9窘俺。我們看下拆分出來(lái)的進(jìn)程的值。

image

我們可以看到,這個(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末施戴,一起剝皮案震驚了整個(gè)濱河市反浓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌赞哗,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,948評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辆雾,死亡現(xiàn)場(chǎng)離奇詭異肪笋,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)藤乙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)猜揪,“玉大人,你說(shuō)我怎么就攤上這事坛梁《悖” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,490評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵划咐,是天一觀(guān)的道長(zhǎng)拴念。 經(jīng)常有香客問(wèn)我,道長(zhǎng)褐缠,這世上最難降的妖魔是什么政鼠? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,521評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮队魏,結(jié)果婚禮上公般,老公的妹妹穿的比我還像新娘。我一直安慰自己胡桨,他們只是感情好官帘,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著昧谊,像睡著了一般遏佣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上揽浙,一...
    開(kāi)封第一講書(shū)人閱讀 49,842評(píng)論 1 290
  • 那天状婶,我揣著相機(jī)與錄音,去河邊找鬼馅巷。 笑死膛虫,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的钓猬。 我是一名探鬼主播稍刀,決...
    沈念sama閱讀 38,997評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼敞曹!你這毒婦竟也來(lái)了账月?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,741評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤澳迫,失蹤者是張志新(化名)和其女友劉穎局齿,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體橄登,經(jīng)...
    沈念sama閱讀 44,203評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡抓歼,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評(píng)論 2 327
  • 正文 我和宋清朗相戀三年讥此,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谣妻。...
    茶點(diǎn)故事閱讀 38,673評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡萄喳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蹋半,到底是詐尸還是另有隱情他巨,我是刑警寧澤,帶...
    沈念sama閱讀 34,339評(píng)論 4 330
  • 正文 年R本政府宣布减江,位于F島的核電站染突,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏您市。R本人自食惡果不足惜觉痛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望茵休。 院中可真熱鬧薪棒,春花似錦、人聲如沸榕莺。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,770評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)钉鸯。三九已至吧史,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間唠雕,已是汗流浹背贸营。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,000評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留岩睁,地道東北人钞脂。 一個(gè)月前我還...
    沈念sama閱讀 46,394評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像捕儒,于是被迫代替她去往敵國(guó)和親冰啃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評(píng)論 2 349