Android開發(fā)高級進(jìn)階——多進(jìn)程間通信

如果一個(gè)進(jìn)程占用內(nèi)存超過了這個(gè)內(nèi)存限制荣挨,就會報(bào)OOM的問題埃元,很多涉及到大圖片的頻繁操作或者需要讀取一大段數(shù)據(jù)在內(nèi)存中使用時(shí)泥从,很容易報(bào)OOM的問題。為了徹底地解決應(yīng)用內(nèi)存的問題舔腾,Android引入了多進(jìn)程的概念溪胶,它允許在同一個(gè)應(yīng)用內(nèi),為了分擔(dān)主進(jìn)程的壓力稳诚,將占用內(nèi)存的某些頁面單獨(dú)開一個(gè)進(jìn)程哗脖,比如Flash、視頻播放頁面,頻繁繪制的頁面等才避。

一. 什么是多進(jìn)程橱夭?


多進(jìn)程就是多個(gè)進(jìn)程的意思,那么什么是進(jìn)程呢桑逝?

當(dāng)一個(gè)應(yīng)用在開始運(yùn)行時(shí)棘劣,系統(tǒng)會為它創(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)程不能任意訪問當(dāng)前進(jìn)程的內(nèi)存和資源
  • 系統(tǒng)給每個(gè)進(jìn)程分配的內(nèi)存會有限制

根據(jù)上邊的引言和進(jìn)程的特點(diǎn)可以看出糙俗,使用多進(jìn)程的場景為:需要使apk所使用的內(nèi)存限制擴(kuò)大。

二. 進(jìn)程的等級


按優(yōu)先級可以分為五類预鬓,優(yōu)先級從高到低排列:

Android進(jìn)程分類
  1. 前臺進(jìn)程:該進(jìn)程包含正在與用戶進(jìn)行交互的界面組件巧骚,比如一個(gè)Activity。在接收關(guān)鍵生命周期方法時(shí)會讓一個(gè)進(jìn)程臨時(shí)提升為前臺進(jìn)程珊皿,包括任何服務(wù)的生命周期方法onCreate()和onDestroy()和任何廣播接收器onReceive()方法网缝。這樣做確保了這些組件的操作是有效的原子操作,每個(gè)組件都能執(zhí)行完成而不被殺掉蟋定。
  2. 可見進(jìn)程:該進(jìn)程中的組件雖然沒有和用戶交互,但是仍然可以被看到草添。activity可見的時(shí)候不一定在前臺驶兜。一個(gè)簡單的例子是前臺的 activity 使用對話框啟動了一個(gè)新的 activity 或者一個(gè)透明 activity 。另一個(gè)例子是當(dāng)調(diào)用運(yùn)行時(shí)權(quán)限對話框時(shí)(事實(shí)上它就是一個(gè) activityT洞纭)抄淑。
  3. 服務(wù)進(jìn)程:該進(jìn)程包含在執(zhí)行后臺操作的服務(wù)組件,比如播放音樂的Service驰后。對于許多在后臺做處理(如加載數(shù)據(jù))而沒有立即成為前臺服務(wù)的應(yīng)用都屬于這種情況肆资。
    請?zhí)貏e注意從onStartCommand()返回的常量,如果服務(wù)由于內(nèi)存壓力被殺掉灶芝,它表示控制什么發(fā)生什么:
    START_STICKY表示希望系統(tǒng)可用的時(shí)候自動重啟服務(wù)郑原,但不關(guān)心是否能獲得最后一次的 Intent (例如,可以重建自己的狀態(tài)或者控制自己的 start/stop 生命周期)夜涕。
    START_REDELIVER_INTENT是為那些在被殺死之后重啟時(shí)重新獲得 Intent 的服務(wù)的犯犁,直到用傳遞給 onStartCommand() 方法的 startId 參數(shù)調(diào)用stopSelf()為止。這里會使用 Intent 和 startId 作為隊(duì)列完成工作女器。
    START_NOT_STICKY用于那些殺掉也沒關(guān)系的服務(wù)酸役。這適合那些管理周期性任務(wù)的服務(wù),它們只是等待下一個(gè)時(shí)間窗口工作。
  4. 后臺進(jìn)程:該進(jìn)程包含的組件沒有與用戶交互涣澡,用戶也看不到 Service贱呐。在一般操作場景下,設(shè)備上的許多內(nèi)存就是用在這上面的入桂,使可以重新回到之前打開過的某個(gè) activity 吼句。
  5. 空進(jìn)程:沒有任何界面組件、服務(wù)組件事格,或觸發(fā)器組件惕艳,只是出于緩存的目的而被保留(為了更加有效地使用內(nèi)存而不是完全釋放掉),只要 Android 需要可以隨時(shí)殺掉它們驹愚。

三. 多進(jìn)程的創(chuàng)建


Android多進(jìn)程創(chuàng)建很簡單远搪,只需要在AndroidManifest.xml的聲明四大組件的標(biāo)簽中增加”android:process”屬性即可。命名之后逢捺,就成了一個(gè)單獨(dú)的進(jìn)程谁鳍。

process分私有進(jìn)程和全局進(jìn)程:

  • 私有進(jìn)程的名稱前面有冒號,例如:
<service android:name=".MusicService"   
           android:process=":musicservice"/>
  • 全局進(jìn)程的名稱前面沒有冒號劫瞳,例如:
<service android:name=".MusicService"   
           android:process="com.trampcr.musicdemo.service"/>

為了節(jié)省系統(tǒng)內(nèi)存倘潜,在退出該Activity的時(shí)候可以將其殺掉(如果沒有人為殺掉該進(jìn)程,在程序完全退出時(shí)該進(jìn)程會被系統(tǒng)殺掉)志于。

多進(jìn)程被創(chuàng)建好了涮因,應(yīng)用運(yùn)行時(shí)就會對進(jìn)程進(jìn)行初始化,如果一個(gè)application中有多個(gè)進(jìn)程伺绽,在進(jìn)行全局初始化時(shí)养泡,多進(jìn)程會被初始化多次。

解決辦法:判斷當(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)存是不可見的惩妇,也就是說多個(gè)進(jìn)程間內(nèi)存是不共享的株汉。那么進(jìn)程間是如何進(jìn)行通信的呢?

Android中提供了三種方法:

  • 系統(tǒng)實(shí)現(xiàn)屿附。
  • AIDL(Android Interface Definition Language郎逃,Android接口定義語言):大部分應(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;
        }
    }
}

通過以上代碼,可以看到Messenger的使用方法:

  1. 服務(wù)實(shí)現(xiàn)一個(gè)Handler搞监,由其接收來自客戶端的每個(gè)調(diào)用的回調(diào)水孩。
  2. Handler用于創(chuàng)建Messenger對象(對Handler的引用)。
  3. Messenger創(chuàng)建一個(gè)IBinder琐驴,服務(wù)通過onBind()使其返回客戶端俘种。
  4. 客戶端使用IBinder將Messenger(引用服務(wù)的Handler)實(shí)例化,然后使用后者將Message對象發(fā)送給服務(wù)绝淡。
  5. 服務(wù)在其Handler中(具體地講宙刘,是在handleMessage()方法中)接收每個(gè)Message。

這樣牢酵,客戶端并沒有調(diào)用服務(wù)的“方法”悬包。而客戶端傳遞的“消息”(Message對象)是服務(wù)在其Handler中接收的。

以上代碼實(shí)現(xiàn)的應(yīng)用茁帽,剛打開會彈出一個(gè)binding玉罐,binding表示打開應(yīng)用Activity就通過Messenger連接了一個(gè)服務(wù)進(jìn)程,然后點(diǎn)擊say hello會彈出hello,trampcr潘拨,這表示了Activity通過Messenger將Message發(fā)送給了服務(wù)進(jìn)程。如下圖:

MessengerService進(jìn)程與MessengerActivity之間的通信

使用AIDL

AIDL是一種接口描述語言饶号,通常用于進(jìn)程間通信铁追。

使用AIDL的步驟:

  1. 創(chuàng)建AIDL,在main下新建一個(gè)文件夾aidl茫船,然后在aidl下新建AIDL文件琅束,這時(shí)系統(tǒng)會自動為該文件創(chuàng)建一個(gè)包名。
    aidl文件中會有一個(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)還會自動生成aidl代碼然眼,所在位置為:build/generated/source/aidl下debug和release艾船,但是此時(shí)debug下沒有任何東西,可以rebuild或運(yùn)行一下程序,再次打開debug屿岂,發(fā)現(xiàn)生成了一個(gè)包和一個(gè)aidl文件践宴。

  1. 在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;
    }
}
  1. 在AndroidManifest.xml中注冊爷怀,并給一個(gè)進(jìn)程名阻肩,是該服務(wù)成為一個(gè)獨(dú)立的進(jìn)程。
<service android:name=".AIDLService"   
            android:process="com.aidl.test.service"/>
  1. 在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)行連接烤惊,通過IMyAidlInterface實(shí)例與AIDLService進(jìn)程進(jìn)行通信,如下圖所示:

AIDLService進(jìn)程與MainActivity之間的通信.gif

五.序列化插件


Parcelable code generate:自動生成實(shí)現(xiàn)了Parcelable接口的對象吁朦。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末柒室,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子喇完,更是在濱河造成了極大的恐慌伦泥,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锦溪,死亡現(xiàn)場離奇詭異不脯,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)刻诊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門防楷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人则涯,你說我怎么就攤上這事复局。” “怎么了粟判?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵亿昏,是天一觀的道長。 經(jīng)常有香客問我档礁,道長角钩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任呻澜,我火速辦了婚禮递礼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘羹幸。我一直安慰自己脊髓,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布栅受。 她就那樣靜靜地躺著将硝,像睡著了一般恭朗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上袋哼,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天冀墨,我揣著相機(jī)與錄音,去河邊找鬼涛贯。 笑死诽嘉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的弟翘。 我是一名探鬼主播虫腋,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼稀余!你這毒婦竟也來了悦冀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤睛琳,失蹤者是張志新(化名)和其女友劉穎盒蟆,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體师骗,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡历等,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了辟癌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寒屯。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖黍少,靈堂內(nèi)的尸體忽然破棺而出寡夹,到底是詐尸還是另有隱情,我是刑警寧澤厂置,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布菩掏,位于F島的核電站,受9級特大地震影響昵济,放射性物質(zhì)發(fā)生泄漏患蹂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一砸紊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧囱挑,春花似錦醉顽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽系草。三九已至,卻和暖如春唆涝,著一層夾襖步出監(jiān)牢的瞬間找都,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工廊酣, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留能耻,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓亡驰,卻偏偏與公主長得像晓猛,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子凡辱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容