巧用Android多進程

1.為什么要使用多進程洒嗤?

相信很多同學在實際開發(fā)中粗俱,基本都不會去給app劃分進程,而且锥忿,在Android中使用多進程牛郑,還可能需要編寫額外的進程通訊代碼,還可能帶來額外的Bug敬鬓,這無疑加大了開發(fā)的工作量淹朋,在很多創(chuàng)業(yè)公司中工期也不允許,這導致了整個app都在一個進程中钉答。

整個app都在一個進程有什么弊端础芍?

在Android中,虛擬機分配給各個進程的運行內存是有限制值的(這個值可以是32M数尿,48M仑性,64M等,根據機型而定)右蹦,試想一下诊杆,如果在app中,增加了一個很常用的圖片選擇模塊用于上傳圖片或者頭像何陆,加載大量Bitmap會使app的內存占用迅速增加晨汹,如果你還把查看過的圖片緩存在了內存中,那么OOM的風險將會大大增加甲献,如果此時還需要使用WebView加載一波網頁宰缤,我就問你怕不怕!

微信晃洒,微博等主流app是如何解決這些問題的慨灭?

微信移動開發(fā)團隊在《Android內存優(yōu)化雜談》一文中就說到:“對于webview,圖庫等球及,由于存在內存系統(tǒng)泄露或者占用內存過多的問題氧骤,我們可以采用單獨的進程。微信當前也會把它們放在單獨的tools進程中”吃引。
下面我們使用adb查看一下微信和微博的進程信息(Android 5.0以下版本可直接在“設置 -> 應用程序”相關條目中查看):

進入adb shell后筹陵,使用 “ps | grep 條目名稱” 可以過濾出想要查看的進程刽锤。

可以看到,微信的確有一個tools進程朦佩,而新浪微博也有image相關的進程并思,而且它們當中還有好些其它的進程,比如微信的push進程语稠,微博的remote進程等,這里可以看出仙畦,他們不單單只是把上述的WebView、圖庫等放到單獨的進程慨畸,還有推送服務等也是運行在獨立的進程中的莱坎。一個消息推送服務檐什,為了保證穩(wěn)定性,可能需要和UI進程分離谐宙,分離后即使UI進程退出烫葬、Crash或者出現(xiàn)內存消耗過高等情況,仍不影響消息推送服務凡蜻。

可見搭综,合理使用多進程不僅僅是有多大好處的問題,我個人認為而且是很有必要的划栓。
所以說兑巾,我們最好還是根據自身情況,考慮一下是否需要拆分進程忠荞。這也是本文的初衷:給大家提供一個多進程的參考思路蒋歌,在遇到上述問題和場景的時候,可以考慮用多進程的方法來解決問題委煤,又或者堂油,在面試的時候,跟面試官聊到這方面的知識時候也不至于尷尬碧绞。

2.為什么需要“跨進程通訊”府框?

Android的進程與進程之間通訊,有些不需要我們額外編寫通訊代碼讥邻,例如:把選擇圖片模塊放到獨立的進程迫靖,我們仍可以使用startActivityForResult方法院峡,將選中的圖片放到Bundle中,使用Intent傳遞即可系宜。(看到這里照激,你還不打算把你項目的圖片選擇弄到獨立進程么?)

但是對于把“消息推送Service”放到獨立的進程蜈首,這個業(yè)務就稍微復雜點了实抡,這個時候可能會發(fā)生Activity跟Service傳遞對象,調用Service方法等一系列復雜操作欢策。

由于各個進程運行在相對獨立的內存空間,所以它們是不能直接通訊的赏淌,因為程序里的變量踩寇、對象等初始化后都是具有內存地址的,舉個簡單的例子六水,讀取一個變量的值俺孙,本質是找到變量的內存地址,取出存放的值掷贾。不同的進程睛榄,運行在相互獨立的內存(其實就可以理解為兩個不同的應用程序),顯然不能直接得知對方變量想帅、對象的內存地址场靴,這樣的話也自然不能訪問對方的變量,對象等港准。此時兩個進程進行交互旨剥,就需要使用跨進程通訊的方式去實現(xiàn)。簡單說浅缸,跨進程通訊就是一種讓進程與進程之間可以進行交互的技術轨帜。

3.跨進程通訊的方式有哪些?

1.四大組件間傳遞Bundle;

2.使用文件共享方式衩椒,多進程讀寫一個相同的文件蚌父,獲取文件內容進行交互;

3.使用Messenger毛萌,一種輕量級的跨進程通訊方案苟弛,底層使用AIDL實現(xiàn)(實現(xiàn)比較簡單,博主開始本文前也想了一下是否要說一下這個東西朝聋,最后還是覺得沒有這個必要嗡午,Google一下就能解決的問題,就不啰嗦了)冀痕;

4.使用AIDL(Android Interface Definition Language)荔睹,Android接口定義語言狸演,用于定義跨進程通訊的接口;

5.使用ContentProvider僻他,常用于多進程共享數據宵距,比如系統(tǒng)的相冊,音樂等吨拗,我們也可以通過ContentProvider訪問到满哪;

6.使用Socket傳輸數據。

下面開始對AIDL的講解劝篷,各位道友準備好渡劫了嗎哨鸭?

4.使用AIDL實現(xiàn)一個多進程消息推送

像圖片選擇這樣的多進程需求,可能并不需要我們額外編寫進程通訊的代碼娇妓,使用四大組件傳輸Bundle就行了像鸡,但是像推送服務這種需求,進程與進程之間需要高度的交互哈恰,此時就繞不過進程通訊這一步了只估。
下面我們就用即時聊天軟件為例,手動去實現(xiàn)一個多進程的推送例子着绷,具體需求如下:
1.UI和消息推送的Service分兩個進程蛔钙;

2.UI進程用于展示具體的消息數據,把用戶發(fā)送的消息荠医,傳遞到消息Service吁脱,然后發(fā)送到遠程服務器;

3.把服務端配置到獨立的進程(AndroidManifest.xml中指定process標簽)子漩;

4.客戶端和服務端進行綁定(bindService)豫喧;

5.讓客戶端和服務端具備交互的能力。(AIDL使用)幢泼;

5.實現(xiàn)思路

先來整理一下實現(xiàn)思路:

1.創(chuàng)建UI進程(下文統(tǒng)稱為客戶端)紧显;

2.創(chuàng)建消息Service(下文統(tǒng)稱為服務端);

3.把服務端配置到獨立的進程(AndroidManifest.xml中指定process標簽)缕棵;

4.客戶端和服務端進行綁定(bindService)孵班;

5.讓客戶端和服務端具備交互的能力。(AIDL使用)招驴;

6.例子具體實現(xiàn)

為了閱讀方便篙程,下文中代碼將省略非重點部分,可以把本文完整代碼Clone到本地再看文章:https://github.com/shijun931011/AIDL

Step0. AIDL調用流程概覽

開始之前别厘,我們先來概括一下使用AIDL進行多進程調用的整個流程:

1.客戶端使用bindService方法綁定服務端虱饿;

2.服務端在onBind方法返回Binder對象;

3.客戶端拿到服務端返回的Binder對象進行跨進程方法調用;


整個AIDL調用過程概括起來就以上3個步驟氮发,下文中我們使用上面描述的例子渴肉,來逐步分解這些步驟,并講述其中的細節(jié)爽冕。

Step1.客戶端使用bindService方法綁定服務端
1.1 創(chuàng)建客戶端和服務端仇祭,把服務端配置到另外的進程

1.創(chuàng)建客戶端 -> MainActivity;

2.創(chuàng)建服務端 -> MessageService;

3.把服務端配置到另外的進程 -> android:process=”:remote”

上面描述的客戶端颈畸、服務端乌奇、以及把服務端配置到另外進程,體現(xiàn)在AndroidManifest.xml中眯娱,如下所示:

  <application
        android:name=".MyApp"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".ui.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".service.MessageService"
            android:enabled="true"
            android:exported="true"
            android:process=":remote" />
    </application>

開啟多進程的方法很簡單礁苗,只需要給四大組件指定android:process標簽。

1.2 綁定MessageService到MainActivity

創(chuàng)建MessageService:
此時的MessageService就是剛創(chuàng)建的模樣困乒,onBind中返回了null寂屏,下一步中我們將返回一個可操作的對象給客戶端。

public class MessageService extends Service {
    public MessageService() {
    }
    @Override
    public IBinder onBind(Intent intent) {
          return null;
    }
}
客戶端MainActivity調用bindService方法綁定MessageService

這一步其實是屬于Service組件相關的知識娜搂,在這里就比較簡單地說一下,啟動服務可以通過以下兩種方式:

1.使用bindService方法 -> bindService(Intent service, ServiceConnection conn, int flags)吱抚;

2.使用startService方法 -> startService(Intent service);

bindService & startService區(qū)別:
使用bindService方式百宇,多個Client可以同時bind一個Service,但是當所有Client unbind后秘豹,Service會退出携御,通常情況下,如果希望和Service交互既绕,一般使用bindService方法啄刹,使用onServiceConnected中的IBinder對象可以和Service進行交互,不需要和Service交互的情況下凄贩,使用startService方法即可誓军。

正如上面所說,我們是要和Service交互的疲扎,所以我們需要使用bindService方法昵时,但是我們希望unbind后Service仍保持運行,這樣的情況下椒丧,可以同時調用bindService和startService(比如像本例子中的消息服務壹甥,退出UI進程,Service仍需要接收到消息)壶熏,代碼如下:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    private MessageSender messageSender;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setupService();
    }

    /**
     * 1.unregisterListener
     * 2.unbindService
     */
    @Override
    protected void onDestroy() {
        //解除消息監(jiān)聽接口
        if (messageSender != null && messageSender.asBinder().isBinderAlive()) {
            try {
                messageSender.unregisterReceiveListener(messageReceiver);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        unbindService(serviceConnection);
        super.onDestroy();
    }

    /**
     * bindService & startService:
     * 使用bindService方式句柠,多個Client可以同時bind一個Service,但是當所有Client unbind后,Service會退出
     * 通常情況下溯职,如果希望和Service交互精盅,一般使用bindService方法,獲取到onServiceConnected中的IBinder對象缸榄,和Service進行交互渤弛,
     * 不需要和Service交互的情況下,使用startService方法即可甚带,Service主線程執(zhí)行完成后會自動關閉她肯;
     * unbind后Service仍保持運行,可以同時調用bindService和startService(比如像聊天軟件鹰贵,退出UI進程晴氨,Service仍能接收消息)
     */
    private void setupService() {
        Intent intent = new Intent(this, MessageService.class);
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
        startService(intent);
    }

    /**
     * Binder可能會意外死忙(比如Service Crash),Client監(jiān)聽到Binder死忙后可以進行重連服務等操作
     */
    IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            Log.d(TAG, "binderDied");
            if (messageSender != null) {
                messageSender.asBinder().unlinkToDeath(this, 0);
                messageSender = null;
            }
            //// TODO: 2017/2/28 重連服務或其他操作
            setupService();
        }
    };

    //消息監(jiān)聽回調接口
    private MessageReceiver messageReceiver = new MessageReceiver.Stub() {

        @Override
        public void onMessageReceived(MessageModel receivedMessage) throws RemoteException {
            Log.d(TAG, "onMessageReceived: " + receivedMessage.toString());
        }
    };

    ServiceConnection serviceConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //使用asInterface方法取得AIDL對應的操作接口
            messageSender = MessageSender.Stub.asInterface(service);

            //生成消息實體對象
            MessageModel messageModel = new MessageModel();
            messageModel.setFrom("client user id");
            messageModel.setTo("receiver user id");
            messageModel.setContent("This is message content");

            try {
                //設置Binder死亡監(jiān)聽
                messageSender.asBinder().linkToDeath(deathRecipient, 0);
                //把接收消息的回調接口注冊到服務端
                messageSender.registerReceiveListener(messageReceiver);
                //調用遠程Service的sendMessage方法碉输,并傳遞消息實體對象
                messageSender.sendMessage(messageModel);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

}
Stpe2.服務端在onBind方法返回Binder對象
2.1 首先籽前,什么是Binder?

要說Binder,首先要說一下IBinder這個接口敷钾,IBinder是遠程對象的基礎接口枝哄,輕量級的遠程過程調用機制的核心部分,該接口描述了與遠程對象交互的抽象協(xié)議阻荒,而Binder實現(xiàn)了IBinder接口挠锥,簡單說,Binder就是Android SDK中內置的一個多進程通訊實現(xiàn)類侨赡,在使用的時候蓖租,我們不用也不要去實現(xiàn)IBinder,而是繼承Binder這個類即可實現(xiàn)多進程通訊羊壹。

2.2 其次蓖宦,這個需要在onBind方法返回的Binder對象從何而來?

在這里就要引出本文中的主題了——AIDL
多進程中使用的Binder對象油猫,一般通過我們定義好的 .adil 接口文件自動生成稠茂,當然你可以走野路子,直接手動編寫這個跨進程通訊所需的Binder類眨攘,其本質無非就是一個繼承了Binder的類主慰,鑒于野路子走起來麻煩,而且都是重復步驟的工作鲫售,Google提供了 AIDL 接口來幫我們自動生成Binder這條正路共螺,下文中我們圍繞 AIDL 這條正路繼續(xù)展開討論(可不能把人給帶偏了是吧??)

2.3 定義AIDL接口

很明顯,接下來我們需要搞一波上面說的Binder情竹,讓客戶端可以調用到服務端的方法藐不,而這個Binder又是通過AIDL接口自動生成,那我們就先從AIDL搞起,搞之前先看看注意事項雏蛮,以免出事故:
AIDL支持的數據類型:

  1. JAVA Java 編程語言中的所有基本數據類型(如 int涎嚼、long、char挑秉、boolean 等等)

  2. String和CharSequence

  3. Parcelable:實現(xiàn)了Parcelable接口的對象

  4. List:其中的元素需要被AIDL支持法梯,另一端實際接收的具體類始終是 ArrayList,但生成的方法使用的是 List 接口

  5. Map:其中的元素需要被AIDL支持犀概,包括 key 和 value立哑,另一端實際接收的具體類始終是 HashMap,但生成的方法使用的是 Map 接口

其他注意事項:

  1. 在AIDL中傳遞的對象姻灶,必須實現(xiàn)Parcelable序列化接口铛绰;

  2. 在AIDL中傳遞的對象,需要在類文件相同路徑下产喉,創(chuàng)建同名捂掰、但是后綴為.aidl的文件,并在文件中使用parcelable關鍵字聲明這個類曾沈;

  3. 跟普通接口的區(qū)別:只能聲明方法这嚣,不能聲明變量;

  4. 所有非基礎數據類型參數都需要標出數據走向的方向標記塞俱“唐唬可以是 in、out 或 inout敛腌,基礎數據類型默認只能是 in,不能是其他方向惫皱。

下面繼續(xù)我們的例子像樊,開始對AIDL的講解~

2.4 創(chuàng)建一個AIDL接口,接口中提供發(fā)送消息的方法(Android Studio創(chuàng)建AIDL:項目右鍵 -> New -> AIDL -> AIDL File)旅敷,代碼如下:
interface MessageSender {
    void sendMessage(in MessageModel messageModel);

    void registerReceiveListener(MessageReceiver messageReceiver);

    void unregisterReceiveListener(MessageReceiver messageReceiver);
}

一個比較尷尬的事情生棍,看了很多文章,從來沒有一篇能說清楚in媳谁、out涂滴、inout這三個參數方向的意義,后來在stackoverflow上找到能理解答案(https://stackoverflow.com/questions/4700225/in-out-inout-in-a-aidl-interface-parameter-value)晴音,我翻譯一下大概意思:

被“in”標記的參數柔纵,就是接收實際數據的參數,這個跟我們普通參數傳遞一樣的含義锤躁。在AIDL中搁料,“out” 指定了一個僅用于輸出的參數,換而言之,這個參數不關心調用方傳遞了什么數據過來郭计,但是這個參數的值可以在方法被調用后填充(無論調用方傳遞了什么值過來霸琴,在方法執(zhí)行的時候,這個參數的初始值總是空的)昭伸,這就是“out”的含義梧乘,僅用于輸出。而“inout”顯然就是“in”和“out”的合體了庐杨,輸入和輸出的參數选调。區(qū)分“in”、“out”有什么用辑莫?這是非常重要的学歧,因為每個參數的內容必須編組(序列化,傳輸各吨,接收和反序列化)枝笨。in/out標簽允許Binder跳過編組步驟以獲得更好的性能。

上述的MessageModel為消息的實體類揭蜒,該類在AIDL中傳遞横浑,實現(xiàn)了Parcelable序列化接口,代碼如下:

public class MessageModel implements Parcelable {

    private String from;
    private String to;
    private String content;

    public String getFrom() {
        return from;
    }

    public void setFrom(String from) {
        this.from = from;
    }

    public String getTo() {
        return to;
    }

    public void setTo(String to) {
        this.to = to;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return "MessageModel{" +
                "from='" + from + '\'' +
                ", to='" + to + '\'' +
                ", content='" + content + '\'' +
                '}';
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.from);
        dest.writeString(this.to);
        dest.writeString(this.content);
    }

    public MessageModel() {
    }

    protected MessageModel(Parcel in) {
        this.from = in.readString();
        this.to = in.readString();
        this.content = in.readString();
    }

    public static final Parcelable.Creator<MessageModel> CREATOR = new Parcelable.Creator<MessageModel>() {
        @Override
        public MessageModel createFromParcel(Parcel source) {
            return new MessageModel(source);
        }

        @Override
        public MessageModel[] newArray(int size) {
            return new MessageModel[size];
        }
    };
}

手動實現(xiàn)Parcelable接口比較麻煩屉更,安利一款AS自動生成插件android-parcelable-intellij-plugin
創(chuàng)建完MessageModel這個實體類徙融,別忘了還有一件事要做:”在AIDL中傳遞的對象,需要在類文件相同路徑下瑰谜,創(chuàng)建同名欺冀、但是后綴為.aidl的文件,并在文件中使用parcelable關鍵字聲明這個類“萨脑。代碼如下:

package com.example.aidl.data;

parcelable MessageModel;

對于沒有接觸過aidl的同學隐轩,光說就能讓人懵逼,來看看此時的項目結構壓壓驚:

Paste_Image.png

我們剛剛新增的3個文件:

  • MessageSender.aidl -> 定義了發(fā)送消息的方法渤早,會自動生成名為MessageSender.Stub的Binder類职车,在服務端實現(xiàn),返回給客戶端調用

  • MessageModel.java -> 消息實體類鹊杖,由客戶端傳遞到服務端悴灵,實現(xiàn)了Parcelable序列化

  • MessageModel.aidl -> 聲明了MessageModel可在AIDL中傳遞,放在跟MessageModel.java相同的包路徑下

OK骂蓖,相信此時懵逼已解除~

2.5 在服務端創(chuàng)建MessageSender.aidl這個AIDL接口自動生成的Binder對象积瞒,并返回給客戶端調用,服務端MessageService代碼如下:
public class MessageService extends Service {

    private static final String TAG = "MessageService";
  

    public MessageService() {
    }

    IBinder messageSender = new MessageSender.Stub() {

        @Override
        public void sendMessage(MessageModel messageModel) throws RemoteException {
            Log.e(TAG, "messageModel: " + messageModel.toString());
        }

       
    };

    @Override
    public IBinder onBind(Intent intent) {
        return messageSender;
    }

}

MessageSender.Stub是Android Studio根據我們MessageSender.aidl文件自動生成的Binder對象(至于是怎樣生成的涯竟,下文會有答案)赡鲜,我們需要把這個Binder對象返回給客戶端空厌。

2.6 客戶端拿到Binder對象后調用遠程方法

調用步驟如下:

  1. 在客戶端的onServiceConnected方法中撰茎,拿到服務端返回的Binder對象铲咨;

  2. 使用MessageSender.Stub.asInterface方法,取得MessageSender.aidl對應的操作接口潘拱;

  3. 取得MessageSender對象后揩瞪,像普通接口一樣調用方法即可赋朦。

此時客戶端代碼如下:


public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private MessageSender messageSender;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setupService();
    }

    /**
     * bindService & startService:
     * 使用bindService方式,多個Client可以同時bind一個Service李破,但是當所有Client unbind后宠哄,Service會退出
     * 通常情況下,如果希望和Service交互嗤攻,一般使用bindService方法毛嫉,獲取到onServiceConnected中的IBinder對象,和Service進行交互妇菱,
     * 不需要和Service交互的情況下承粤,使用startService方法即可,Service主線程執(zhí)行完成后會自動關閉闯团;
     * unbind后Service仍保持運行辛臊,可以同時調用bindService和startService(比如像聊天軟件,退出UI進程房交,Service仍能接收消息)
     */
    private void setupService() {
        Intent intent = new Intent(this, MessageService.class);
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
        startService(intent);
    }
    ServiceConnection serviceConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //使用asInterface方法取得AIDL對應的操作接口
            messageSender = MessageSender.Stub.asInterface(service);

            //生成消息實體對象
            MessageModel messageModel = new MessageModel();
            messageModel.setFrom("client user id");
            messageModel.setTo("receiver user id");
            messageModel.setContent("This is message content");

            try {
                //設置Binder死亡監(jiān)聽
                messageSender.asBinder().linkToDeath(deathRecipient, 0);
                //把接收消息的回調接口注冊到服務端
                messageSender.registerReceiveListener(messageReceiver);
                //調用遠程Service的sendMessage方法彻舰,并傳遞消息實體對象
                messageSender.sendMessage(messageModel);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

}

在客戶端中我們調用了MessageSender的sendMessage方法,向服務端發(fā)送了一條消息候味,并把生成的MessageModel對象作為參數傳遞到了服務端刃唤,最終服務端打印的結果如下:

Paste_Image.png

這里有兩點要說:

  1. 服務端已經接收到客戶端發(fā)送過來的消息,并正確打影兹骸透揣;

  2. 服務端和客戶端區(qū)分兩個進程,PID不一樣川抡,進程名也不一樣;

到這里须尚,我們已經完成了最基本的使用AIDL進行跨進程方法調用崖堤,也是Step.0的整個細化過程,可以再回顧一下Step.0耐床,既然已經學會使用了密幔,接下來…全劇終。撩轰。胯甩。

知其然昧廷,知其所以然。

我們通過上述的調用流程偎箫,看看從客戶端到服務端木柬,都經歷了些什么事,看看Binder的上層是如何工作的淹办,至于Binder的底層眉枕,這是一個非常復雜的話題,本文不深究怜森。(如果看到這里你又想問什么是Binder的話速挑,請手動倒帶往上看…)

我們先來回顧一下從客戶端發(fā)起的調用流程:

  1. MessageSender messageSender = MessageSender.Stub.asInterface(service);

  2. messageSender.sendMessage(messageModel);

拋開其它無關代碼,客戶端調跨進程方法就這兩個步驟副硅,而這兩個步驟都封裝在 MessageSender.aidl 最終生成的 MessageSender.java 源碼(具體路徑為:build目錄下某個子目錄姥宝,自己找,不爽你來打我啊 ?? )

請看下方代碼和注釋恐疲,前方高能預警…




只看代碼的話腊满,可能會有點懵逼,相信結合代碼再看下方的流程圖會更好理解:

從客戶端的sendMessage開始流纹,整個AIDL的調用過程如上圖所示糜烹,asInterface方法,將會判斷onBind方法返回的Binder是否存處于同一進程漱凝,在同一進程中疮蹦,則進行常規(guī)的方法調用,若處于不同進程茸炒,整個數據傳遞的過程則需要通過Binder底層去進行編組(序列化愕乎,傳輸,接收和反序列化)壁公,得到最終的數據后再進行常規(guī)的方法調用感论。

敲黑板:對象跨進程傳輸的本質就是 序列化,傳輸紊册,接收和反序列化 這樣一個過程比肄,這也是為什么跨進程傳輸的對象必須實現(xiàn)Parcelable接口

跨進程的回調接口

在上面我們已經實現(xiàn)了從客戶端發(fā)送消息到跨進程服務端的功能,接下來我們還需要將服務端接收到的遠程服務器消息囊陡,傳遞到客戶端芳绩。有同學估計會說:“這不就是一個回調接口的事情嘛”,設置回調接口思路是對的撞反,但是在這里使用的回調接口有點不一樣妥色,在AIDL中傳遞的接口,不能是普通的接口遏片,只能是AIDL接口嘹害,所以我們需要新建一個AIDL接口傳到服務端撮竿,作為回調接口。

新建消息收取的AIDL接口MessageReceiver.aidl:

// MessageReceiver.aidl
package com.example.aidl;
import com.example.aidl.data.MessageModel;

interface MessageReceiver {
    void onMessageReceived(in MessageModel receivedMessage);
}

接下來我們把回調接口注冊到服務端去笔呀,修改我們的MessageSender.aidl:

// MessageSender.aidl
package com.example.aidl;
import com.example.aidl.data.MessageModel;
import com.example.aidl.MessageReceiver;

interface MessageSender {
    void sendMessage(in MessageModel messageModel);

    void registerReceiveListener(MessageReceiver messageReceiver);

    void unregisterReceiveListener(MessageReceiver messageReceiver);
}

以上就是我們最終修改好的aidl接口幢踏,接下來我們需要做出對應的變更:

  1. 在服務端中增加MessageSender的注冊和反注冊接口的方法;

  2. 在客戶端中實現(xiàn)MessageReceiver接口凿可,并通過MessageSender注冊到服務端惑折。

客戶端變更,修改MainActivity:


客戶端主要有3個變更:

增加了messageReceiver對象枯跑,用于監(jiān)聽服務端的消息通知惨驶;

onServiceConnected方法中,把messageReceiver注冊到Service中去敛助;

onDestroy時候解除messageReceiver的注冊粗卜。

下面對服務端MessageServie進行變更:


服務端主要變更:

  1. MessageSender.Stub實現(xiàn)了注冊和反注冊回調接口的方法;

  2. 增加了RemoteCallbackList來管理AIDL遠程接口纳击;

  3. FakeTCPTask模擬了長連接通知客戶端有新消息到達续扔。(這里的長連接可以是XMPP,Mina焕数,Mars纱昧,Netty等,這里弄個假的意思意思堡赔,有時間的話咱開個帖子聊聊XMPP)

這里還有一個需要講一下的识脆,就是RemoteCallbackList,為什么要用RemoteCallbackList善已,普通ArrayList不行嗎灼捂?當然不行,不然干嘛又整一個RemoteCallbackList ??换团,registerReceiveListener 和 unregisterReceiveListener在客戶端傳輸過來的對象悉稠,經過Binder處理,在服務端接收到的時候其實是一個新的對象艘包,這樣導致在 unregisterReceiveListener 的時候的猛,普通的ArrayList是無法找到在 registerReceiveListener 時候添加到List的那個對象的,但是它們底層使用的Binder對象是同一個想虎,RemoteCallbackList利用這個特性做到了可以找到同一個對象衰絮,這樣我們就可以順利反注冊客戶端傳遞過來的接口對象了。RemoteCallbackList在客戶端進程終止后磷醋,它能自動移除客戶端所注冊的listener,它內部還實現(xiàn)了線程同步胡诗,所以我們在注冊和反注冊都不需要考慮線程同步邓线,的確是個666的類淌友。(至于使用ArrayList的幺蛾子現(xiàn)象,大家可以自己試試骇陈,篇幅問題震庭,這里就不演示了)

到此,服務端通知客戶端相關的代碼也寫完了你雌,運行結果無非就是正確打印??就不貼圖了器联,可以自己Run一下,打印的時候注意去選擇不同的進程婿崭,不然瞪壞屏幕也沒有日志拨拓。

DeathRecipient

你以為這樣就完了?too young too simple…

不知道你有沒有感覺到氓栈,兩個進程交互總是覺得缺乏那么一點安全感…比如說服務端進程Crash了渣磷,而客戶端進程想要調用服務端方法,這樣就調用不到了授瘦。此時我們可以給Binder設置一個DeathRecipient對象醋界,當Binder意外掛了的時候,我們可以在DeathRecipient接口的回調方法中收到通知提完,并作出相應的操作形纺,比如重連服務等等。

DeathRecipient的使用如下:

  1. 聲明DeathRecipient對象徒欣,實現(xiàn)其binderDied方法逐样,當binder死亡時,會回調binderDied方法帚称;

  2. 給Binder對象設置DeathRecipient對象官研。

在客戶端MainActivity聲明DeathRecipient:

Binder中兩個重要方法:

  1. linkToDeath -> 設置死亡代理 DeathRecipient 對象;

  2. unlinkToDeath -> Binder死亡的情況下闯睹,解除該代理戏羽。

此外,Binder中的isBinderAlive也可以判斷Binder是否死亡楼吃。

權限驗證

就算是公交車始花,上車也得嘀卡對不,如果希望我們的服務進程不想像公交車一樣誰想上就上孩锡,那么我們可以加入權限驗證酷宵。

介紹兩種常用驗證方法:

  1. 在服務端的onBind中校驗自定義permission,如果通過了我們的校驗躬窜,正常返回Binder對象浇垦,校驗不通過返回null,返回null的情況下客戶端無法綁定到我們的服務荣挨;

  2. 在服務端的onTransact方法校驗客戶端包名男韧,不通過校驗直接return false朴摊,校驗通過執(zhí)行正常的流程。

自定義permission此虑,在Androidmanifest.xml中增加自定義的權限:

服務端檢查權限的方法:

根據不同進程甚纲,做不同的初始化工作

相信前一兩年很多朋友還在使用Android-Universal-Image-Loader來加載圖片,它是需要在Application類進行初始化的朦前。打個比如介杆,我們用它來加載圖片,而且還有一個圖片選擇進程韭寸,那么我們希望分配更多的緩存給圖片選擇進程春哨,又或者是一些其他的初始化工作,不需要在圖片選擇進程初始化怎么辦棒仍?

這里提供一個簡單粗暴的方法悲靴,博主也是這么干的…直接拿到進程名判斷,作出相應操作即可:

每個進程創(chuàng)建莫其,都會調用Application的onCreate方法癞尚,這是一個需要注意的地方,我們也可以根據當前進程的pid乱陡,拿到當前進程的名字去做判斷浇揩,然后做一些我們需要的邏輯,我們這個例子憨颠,拿到的兩個進程名分別是:

  1. 客戶端進程:com.example.aidl

  2. 服務端進程:com.example.aidl:remote

總結

  1. 多進程app可以在系統(tǒng)中申請多份內存胳徽,但應合理使用,建議把一些高消耗但不常用的模塊放到獨立的進程爽彤,不使用的進程可及時手動關閉养盗;

  2. 實現(xiàn)多進程的方式有多種:四大組件傳遞Bundle、Messenger适篙、AIDL等往核,選擇適合自己的使用場景;

  3. Android中實現(xiàn)多進程通訊嚷节,建議使用系統(tǒng)提供的Binder類聂儒,該類已經實現(xiàn)了多進程通訊而不需要我們做底層工作;

  4. 多進程應用硫痰,Application將會被創(chuàng)建多次衩婚;

結語

這篇文章斷斷續(xù)續(xù)寫了很久,而且我相信真正使用起來的同學可能不多效斑,選擇這樣一個話題我是吃力不討好… 但是我還是希望可以在這里提供一個完整的解決方案給大家非春。簡單的多進程使用,而且效果顯著的,比如把圖片選擇和WebView配置到獨立的進程奇昙,這個我希望可以大家行動起來坐搔。這篇文章的知識點非常多,理解起來可能不是太容易敬矩,如果有興趣,我建議你手動去寫一下蠢挡,然后不理解的地方弧岳,打斷點看看是什么樣的運行步驟。

對于面試的同學业踏,如果在面試過程中說到多進程禽炬,跟面試官聊得開,估計也是能加點分的勤家,或者在實際工作中腹尖,一些使用多進程可以更好地解決問題的地方,你可以在會議中拍桌猛起伐脖,跟主管說:“我有一個大膽的想法…”热幔,這樣裝逼也不錯(當然,被炒了的話就不關我的事了…)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末讼庇,一起剝皮案震驚了整個濱河市绎巨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蠕啄,老刑警劉巖场勤,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異歼跟,居然都是意外死亡和媳,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門哈街,熙熙樓的掌柜王于貴愁眉苦臉地迎上來留瞳,“玉大人,你說我怎么就攤上這事叹卷『掣郏” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵骤竹,是天一觀的道長帝牡。 經常有香客問我,道長蒙揣,這世上最難降的妖魔是什么靶溜? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上罩息,老公的妹妹穿的比我還像新娘嗤详。我一直安慰自己,他們只是感情好瓷炮,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布葱色。 她就那樣靜靜地躺著,像睡著了一般娘香。 火紅的嫁衣襯著肌膚如雪苍狰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天烘绽,我揣著相機與錄音淋昭,去河邊找鬼。 笑死安接,一個胖子當著我的面吹牛翔忽,可吹牛的內容都是我干的。 我是一名探鬼主播盏檐,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼歇式,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了糯笙?” 一聲冷哼從身側響起贬丛,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎给涕,沒想到半個月后豺憔,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡够庙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年恭应,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耘眨。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡昼榛,死狀恐怖,靈堂內的尸體忽然破棺而出剔难,到底是詐尸還是另有隱情胆屿,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布偶宫,位于F島的核電站非迹,受9級特大地震影響,放射性物質發(fā)生泄漏纯趋。R本人自食惡果不足惜憎兽,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一冷离、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧纯命,春花似錦西剥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至疗我,卻和暖如春匙铡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背碍粥。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留黑毅,地道東北人嚼摩。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像矿瘦,于是被迫代替她去往敵國和親枕面。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內容

  • Jianwei's blog 首頁 分類 關于 歸檔 標簽 巧用Android多進程缚去,微信潮秘,微博等主流App都在用...
    justCode_閱讀 5,917評論 1 23
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn)易结,斷路器枕荞,智...
    卡卡羅2017閱讀 134,659評論 18 139
  • 3.5 Android進程間通信 3.5.1 背景知識 傳統(tǒng)IPC Linux傳統(tǒng)的IPC機制分為如下幾種:管道、...
    jianhuih閱讀 5,545評論 1 5
  • “胖子沒有前途,要么瘦鹦肿,要么死”這句夸張的流行語一定聽過吧矗烛?聽了后是輕輕的一笑而過還是調侃一句“嚇唬人的”。 專業(yè)...
    未央宮主閱讀 355評論 0 4
  • 文|汝之尾巴草 -01- 在很長很長的一段時間里,我對生命都沒有什么概念涣旨。說不上期待或者敬畏歪架,也說不上有多珍惜或是...
    汝之尾巴草閱讀 2,428評論 29 47