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)程)的名稱就是應(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í)從高到低排列:

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

  2. 可見(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?。?。

  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)有與用戶交互,用戶也看不到 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)單,只需要在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的使用方法:

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

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

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

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

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

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è)類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. 在AndroidManifest.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();
                     }
                 }
             }
         });
     }
    }
    

在Activity中利用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.前言

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

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實(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)的值。

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)定,用戶體驗(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)的值。

image
image
image

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

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è)不多解釋,和ABD基本差不多,都是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等等(這些都是吃內(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容