Android多進程講解和使用

Jianwei's blog

首頁

分類

關于

歸檔

標簽

巧用Android多進程,微信,微博等主流App都在用

發表于2017-02-26|分類于Android|0|閱讀次數241

前言

對于進程的概念,來到這里的都是編程修仙之人,就不再啰嗦了,相信大家倒著、跳著、躺著、各種姿勢都能背出來。

為什么要使用多進程,一個進程不就可以了嗎?

相信很多同學在實際開發中,基本都不會去給app劃分進程,而且,在Android中使用多進程,還可能需要編寫額外的進程通訊代碼,還可能帶來額外的Bug,這無疑加大了開發的工作量,在很多創業公司中工期也不允許,這導致了整個app都在一個進程中。

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

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

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

微信移動開發團隊在《Android內存優化雜談》一文中就說到:“對于webview,圖庫等,由于存在內存系統泄露或者占用內存過多的問題,我們可以采用單獨的進程。微信當前也會把它們放在單獨的tools進程中”。

下面我們使用adb查看一下微信和微博的進程信息(Android 5.0以下版本可直接在“設置 -> 應用程序”相關條目中查看):

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

可以看到,微信的確有一個tools進程,而新浪微博也有image相關的進程,而且它們當中還有好些其它的進程,比如微信的push進程,微博的remote進程等,這里可以看出,他們不單單只是把上述的WebView、圖庫等放到單獨的進程,還有推送服務等也是運行在獨立的進程中的。一個消息推送服務,為了保證穩定性,可能需要和UI進程分離,分離后即使UI進程退出、Crash或者出現內存消耗過高等情況,仍不影響消息推送服務。

可見,合理使用多進程不僅僅是有多大好處的問題,我個人認為而且是很有必要的。

所以說,我們最好還是根據自身情況,考慮一下是否需要拆分進程。這也是本文的初衷:給大家提供一個多進程的參考思路,在遇到上述問題和場景的時候,可以考慮用多進程的方法來解決問題,又或者,在面試的時候,跟面試官聊到這方面的知識時候也不至于尷尬。

為什么需要“跨進程通訊”?

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

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

由于各個進程運行在相對獨立的內存空間,所以它們是不能直接通訊的,因為程序里的變量、對象等初始化后都是具有內存地址的,舉個簡單的例子,讀取一個變量的值,本質是找到變量的內存地址,取出存放的值。不同的進程,運行在相互獨立的內存(其實就可以理解為兩個不同的應用程序),顯然不能直接得知對方變量、對象的內存地址,這樣的話也自然不能訪問對方的變量,對象等。此時兩個進程進行交互,就需要使用跨進程通訊的方式去實現。簡單說,跨進程通訊就是一種讓進程與進程之間可以進行交互的技術。

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

四大組件間傳遞Bundle;

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

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

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

使用ContentProvider,常用于多進程共享數據,比如系統的相冊,音樂等,我們也可以通過ContentProvider訪問到;

使用Socket傳輸數據。

接下來本文將重點介紹使用AIDL進行多進程通訊,因為AIDL是Android提供給我們的標準跨進程通訊API,非常靈活且強大(貌似面試也經常會問到,但是真正用到的也不多…)。上面所說的Messenger也是使用AIDL實現的一種跨進程方式,Messenger顧名思義,就像是一種串行的消息機制,它是一種輕量級的IPC方案,可以在不同進程中傳遞Message對象,我們在Message中放入需要傳遞的數據即可輕松實現進程間通訊。但是當我們需要調用服務端方法,或者存在并發請求,那么Messenger就不合適了。而四大組件傳遞Bundle,這個就不需要解釋了,把需要傳遞的數據,用Intent封裝起來傳遞即可,其它方式不在本文的討論范圍。

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

使用AIDL實現一個多進程消息推送

像圖片選擇這樣的多進程需求,可能并不需要我們額外編寫進程通訊的代碼,使用四大組件傳輸Bundle就行了,但是像推送服務這種需求,進程與進程之間需要高度的交互,此時就繞不過進程通訊這一步了。

下面我們就用即時聊天軟件為例,手動去實現一個多進程的推送例子,具體需求如下:

UI和消息推送的Service分兩個進程;

UI進程用于展示具體的消息數據,把用戶發送的消息,傳遞到消息Service,然后發送到遠程服務器;

Service負責收發消息,并和遠程服務器保持長連接,UI進程可通過Service發送消息到遠程服務器,Service收到遠程服務器消息通知UI進程;

即使UI進程退出了,Service仍需要保持運行,收取服務器消息。

實現思路

先來整理一下實現思路:

創建UI進程(下文統稱為客戶端);

創建消息Service(下文統稱為服務端);

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

客戶端和服務端進行綁定(bindService);

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

例子具體實現

為了閱讀方便,下文中代碼將省略非重點部分,可以把本文完整代碼Clone到本地再看文章:

https://github.com/V1sk/AIDL

Step0. AIDL調用流程概覽

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

客戶端使用bindService方法綁定服務端;

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

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

AIDL調用過程

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

Step1.客戶端使用bindService方法綁定服務端

1.1 創建客戶端和服務端,把服務端配置到另外的進程

創建客戶端 -> MainActivity;

創建服務端 -> MessageService;

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

上面描述的客戶端、服務端、以及把服務端配置到另外進程,體現在AndroidManifest.xml中,如下所示:

android:name=".service.MessageService"

android:enabled="true"

android:exported="true"

android:process=":remote"/>

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

1.2 綁定MessageService到MainActivity

創建MessageService

此時的MessageService就是剛創建的模樣,onBind中返回了null,下一步中我們將返回一個可操作的對象給客戶端。


public class MessageService extends Service{

publicMessageService(){

}

@Override

publicIBinderonBind(Intent intent){

returnnull;

}

}

客戶端MainActivity調用bindService方法綁定MessageService

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

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

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

bindService & startService區別:

使用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{

privatestaticfinalString TAG ="MainActivity";

@Override

protectedvoidonCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

setupService();

}

/**

* unbindService

*/

@Override

protectedvoidonDestroy(){

unbindService(serviceConnection);

super.onDestroy();

}

/**

* bindService & startService

*/

privatevoidsetupService(){

Intent intent =newIntent(this, MessageService.class);

bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);

startService(intent);

}

ServiceConnection serviceConnection =newServiceConnection() {

@Override

publicvoidonServiceConnected(ComponentName name, IBinder service){

Log.d(TAG,"onServiceConnected");

}

@Override

publicvoidonServiceDisconnected(ComponentName name){

Log.d(TAG,"onServiceDisconnected");

}

};

}

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

2.1 首先,什么是Binder?

要說Binder,首先要說一下IBinder這個接口,IBinder是遠程對象的基礎接口,輕量級的遠程過程調用機制的核心部分,該接口描述了與遠程對象交互的抽象協議,而Binder實現了IBinder接口,簡單說,Binder就是Android SDK中內置的一個多進程通訊實現類,在使用的時候,我們不用也不要去實現IBinder,而是繼承Binder這個類即可實現多進程通訊。

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

在這里就要引出本文中的主題了——AIDL

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

2.3 定義AIDL接口

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

AIDL支持的數據類型:

Java 編程語言中的所有基本數據類型(如 int、long、char、boolean 等等)

String和CharSequence

Parcelable:實現了Parcelable接口的對象

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

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

其他注意事項:

在AIDL中傳遞的對象,必須實現Parcelable序列化接口;

在AIDL中傳遞的對象,需要在類文件相同路徑下,創建同名、但是后綴為.aidl的文件,并在文件中使用parcelable關鍵字聲明這個類;

跟普通接口的區別:只能聲明方法,不能聲明變量;

所有非基礎數據類型參數都需要標出數據走向的方向標記。可以是 in、out 或 inout,基礎數據類型默認只能是 in,不能是其他方向。

下面繼續我們的例子,開始對AIDL的講解~

2.4 創建一個AIDL接口,接口中提供發送消息的方法(Android Studio創建AIDL:項目右鍵 -> New -> AIDL -> AIDL File),代碼如下:


package com.example.aidl;

importcom.example.aidl.data.MessageModel;

interface MessageSender{

void sendMessage (in MessageModel messageModel);

}

一個比較尷尬的事情,看了很多文章,從來沒有一篇能說清楚in、out、inout這三個參數方向的意義,后來在stackoverflow上找到比較能理解的答案(stackoverflow原文鏈接),我翻譯一下大概意思:

被“in”標記的參數,就是接收實際數據的參數,這個跟我們普通參數傳遞一樣的含義。在AIDL中,“out” 指定了一個僅用于輸出的參數,換而言之,這個參數不關心調用方傳遞了什么數據過來,但是這個參數的值可以在方法被調用后填充(無論調用方傳遞了什么值過來,在方法執行的時候,這個參數的初始值總是空的),這就是“out”的含義,僅用于輸出。而“inout”顯然就是“in”和“out”的合體了,輸入和輸出的參數。區分“in”、“out”有什么用?這是非常重要的,因為每個參數的內容必須編組(序列化,傳輸,接收和反序列化)。in/out標簽允許Binder跳過編組步驟以獲得更好的性能。

上述的MessageModel為消息的實體類,該類在AIDL中傳遞,實現了Parcelable序列化接口,代碼如下:

public class MessageModel implements Parcelable{

privateString from;

privateString to;

privateString content;

...

Setter & Getter

...

@Override

publicintdescribeContents(){

return0;

}

//...

序列化相關代碼

//...

}

手動實現Parcelable接口比較麻煩,安利一款AS自動生成插件android-parcelable-intellij-plugin

創建完MessageModel這個實體類,別忘了還有一件事要做:”在AIDL中傳遞的對象,需要在類文件相同路徑下,創建同名、但是后綴為.aidl的文件,并在文件中使用parcelable關鍵字聲明這個類“。代碼如下:

package com.example.aidl.data;

parcelable MessageModel;

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

項目結構

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

MessageSender.aidl -> 定義了發送消息的方法,會自動生成名為MessageSender.Stub的Binder類,在服務端實現,返回給客戶端調用

MessageModel.java -> 消息實體類,由客戶端傳遞到服務端,實現了Parcelable序列化

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

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

2.5 在服務端創建MessageSender.aidl這個AIDL接口自動生成的Binder對象,并返回給客戶端調用,服務端MessageService代碼如下:

public class MessageService extends Service{

private static final String TAG ="MessageService";

public MessageService(){

}

IBinder messageSender =newMessageSender.Stub() {

@Override

public void sendMessage(MessageModel messageModel)throwsRemoteException{

Log.d(TAG,"messageModel: "+ messageModel.toString());

}

};

@Override

public IBinder onBind(Intent intent){

return messageSender;

}

}

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

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

調用步驟如下:

在客戶端的onServiceConnected方法中,拿到服務端返回的Binder對象;

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

取得MessageSender對象后,像普通接口一樣調用方法即可。

此時客戶端代碼如下:

public class MainActivity extends AppCompatActivity{

privateMessageSender messageSender;

@Override

protected voido nCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

setupService();

}

//...

private voids etupService(){

Intent intent =newIntent(this, MessageService.class);

bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);

startService(intent);

}

ServiceConnection serviceConnection =newServiceConnection() {

@Override

public void onServiceConnected(ComponentName name, IBinder service){

//使用asInterface方法取得AIDL對應的操作接口

messageSender = MessageSender.Stub.asInterface(service);

//生成消息實體對象

MessageModel messageModel =newMessageModel();

messageModel.setFrom("client user id");

messageModel.setTo("receiver user id");

messageModel.setContent("This is message content");

//調用遠程Service的sendMessage方法,并傳遞消息實體對象

try{

messageSender.sendMessage(messageModel);

}catch(RemoteException e) {

e.printStackTrace();

}

}

@Override

public void onServiceDisconnected(ComponentName name){

}

};

}

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

這里有兩點要說:

服務端已經接收到客戶端發送過來的消息,并正確打印;

服務端和客戶端區分兩個進程,PID不一樣,進程名也不一樣;

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

如果寫到這里全劇終,那跟咸魚有什么區別…

知其然,知其所以然。

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

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

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

messageSender.sendMessage(messageModel);

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

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

public interface MessageSender extends android.os.IInterface{

public static abstract class Stub extends android.os.Binderimplementscom.example.aidl.MessageSender{

private static final java.lang.String DESCRIPTOR ="com.example.aidl.MessageSender";

/**

* 把IBinder對象轉換為 com.example.aidl.MessageSender 接口

* 判斷IBinder是否處于相同進程,相同進程返回Stub實現的com.example.aidl.MessageSender接口

* 不同進程,則返回Stub.Proxy實現的com.example.aidl.MessageSender接口

*/

public static com.example.aidl.MessageSenderasInterface(android.os.IBinder obj){

if((obj ==null)) {

return null;

}

android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);

if(((iin !=null) && (iininstanceofcom.example.aidl.MessageSender))) {

return((com.example.aidl.MessageSender) iin);

}

return new com.example.aidl.MessageSender.Stub.Proxy(obj);

}

/**

* 同一進程時,不會觸發

*

* 不同進程時,asInterface會返回Stub.Proxy,客戶端調用 messageSender.sendMessage(messageModel)

* 實質是調用了 Stub.Proxy 的 sendMessage 方法,從而觸發跨進程數據傳遞,

* 最終Binder底層將處理好的數據回調到此方法,并調用我們真正的sendMessage方法

*/

@Override

public boolean onTransact(intcode, android.os.Parcel data, android.os.Parcel reply,intflags)throwsandroid.os.RemoteException{

switch(code) {

caseINTERFACE_TRANSACTION: {

reply.writeString(DESCRIPTOR);

returntrue;

}

caseTRANSACTION_sendMessage: {

data.enforceInterface(DESCRIPTOR);

com.example.aidl.data.MessageModel _arg0;

if((0!= data.readInt())) {

_arg0 = com.example.aidl.data.MessageModel.CREATOR.createFromParcel(data);

}else{

_arg0 =null;

}

this.sendMessage(_arg0);

reply.writeNoException();

return true;

}

}

return super.onTransact(code, data, reply, flags);

}

private static class Proxy implements com.example.aidl.MessageSender{

private android.os.IBinder mRemote;

Proxy(android.os.IBinder remote) {

mRemote = remote;

}

/**

* Proxy中的sendMessage方法,并不是直接調用我們定義的sendMessage方法,而是經過一頓的Parcel讀寫,

* 然后調用mRemote.transact方法,把數據交給Binder處理,transact處理完畢后會調用上方的onTransact方法,

* onTransact拿到最終得到的參數數據,調用由我們真正的sendMessage方法

*/

@Override

public void sendMessage(com.example.aidl.data.MessageModel messageModel)throwsandroid.os.RemoteException{

android.os.Parcel _data = android.os.Parcel.obtain();

android.os.Parcel _reply = android.os.Parcel.obtain();

try{

_data.writeInterfaceToken(DESCRIPTOR);

if((messageModel !=null)) {

_data.writeInt(1);

messageModel.writeToParcel(_data,0);

}else{

_data.writeInt(0);

}

//調用Binder的transact方法進行多進程數據傳輸,處理完畢后調用上方的onTransact方法

mRemote.transact(Stub.TRANSACTION_sendMessage, _data, _reply,0);

_reply.readException();

}finally{

_reply.recycle();

_data.recycle();

}

}

}

staticfinalintTRANSACTION_sendMessage = (android.os.IBinder.FIRST_CALL_TRANSACTION +0);

}

public void sendMessage(com.example.aidl.data.MessageModel messageModel)throwsandroid.os.RemoteException;

}

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

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

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

跨進程的回調接口

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

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

package com.example.aidl;

import com.example.aidl.data.MessageModel;

interface MessageReceiver{

voido nMessageReceived(in MessageModel receivedMessage);

}

接下來我們把回調接口注冊到服務端去,修改我們的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接口,接下來我們需要做出對應的變更:

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

在客戶端中實現MessageReceiver接口,并通過MessageSender注冊到服務端。

客戶端變更,修改MainActivity:

public class MainActivity extends AppCompatActivity{

private MessageSender messageSender;

@Override

protected voido nCreate(Bundle savedInstanceState){

//...

}

/**

* 1.unregisterListener

* 2.unbindService

*/

@Override

protectedvoidonDestroy(){

//解除消息監聽接口

if(messageSender !=null&& messageSender.asBinder().isBinderAlive()) {

try{

messageSender.unregisterReceiveListener(messageReceiver);

}catch(RemoteException e) {

e.printStackTrace();

}

}

unbindService(serviceConnection);

super.onDestroy();

}

//消息監聽回調接口

private MessageReceiver messageReceiver =newMessageReceiver.Stub() {

@Override

public voido nMessageReceived(MessageModel receivedMessage)throwsRemoteException{

Log.d(TAG,"onMessageReceived: "+ receivedMessage.toString());

}

};

ServiceConnection serviceConnection =newServiceConnection() {

@Override

public void onServiceConnected(ComponentName name, IBinder service){

//使用asInterface方法取得AIDL對應的操作接口

messageSender = MessageSender.Stub.asInterface(service);

//生成消息實體對象

MessageModel messageModel =newMessageModel();

//...

try{

//把接收消息的回調接口注冊到服務端

messageSender.registerReceiveListener(messageReceiver);

//調用遠程Service的sendMessage方法,并傳遞消息實體對象

messageSender.sendMessage(messageModel);

}catch(RemoteException e) {

e.printStackTrace();

}

}

@Override

public voido nServiceDisconnected(ComponentName name){

}

};

}

客戶端主要有3個變更:

增加了messageReceiver對象,用于監聽服務端的消息通知;

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

onDestroy時候解除messageReceiver的注冊。

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

public classMessageService extends Service{

private static final String TAG ="MessageService";

private AtomicBoolean serviceStop =newAtomicBoolean(false);

//RemoteCallbackList專門用來管理多進程回調接口

private RemoteCallbackList listenerList =newRemoteCallbackList<>();

public MessageService(){

}

IBinder messageSender =newMessageSender.Stub() {

@Override

public void sendMessage(MessageModel messageModel)throwsRemoteException{

Log.e(TAG,"messageModel: "+ messageModel.toString());

}

@Override

public void registerReceiveListener(MessageReceiver messageReceiver)throwsRemoteException{

listenerList.register(messageReceiver);

}

@Override

public void unregisterReceiveListener(MessageReceiver messageReceiver)throwsRemoteException{

listenerList.unregister(messageReceiver);

}

};

@Override

publicI Binder onBind(Intent intent){

return messageSender;

}

@Override

public voido nCreate(){

super.onCreate();

newThread(newFakeTCPTask()).start();

}

@Override

public void onDestroy(){

serviceStop.set(true);

super.onDestroy();

}

//模擬長連接,通知客戶端有新消息到達

private class FakeTCPTask implements Runnable{

@Override

public void run(){

while(!serviceStop.get()) {

try{

Thread.sleep(5000);

}catch(InterruptedException e) {

e.printStackTrace();

}

MessageModel messageModel =newMessageModel();

messageModel.setFrom("Service");

messageModel.setTo("Client");

messageModel.setContent(String.valueOf(System.currentTimeMillis()));

/**

* RemoteCallbackList的遍歷方式

* beginBroadcast和finishBroadcast一定要配對使用

*/

final intlistenerCount = listenerList.beginBroadcast();

Log.d(TAG,"listenerCount == "+ listenerCount);

for(inti =0; i < listenerCount; i++) {

MessageReceiver messageReceiver = listenerList.getBroadcastItem(i);

if(messageReceiver !=null) {

try{

messageReceiver.onMessageReceived(messageModel);

}catch(RemoteException e) {

e.printStackTrace();

}

}

}

listenerList.finishBroadcast();

}

}

}

}

服務端主要變更:

MessageSender.Stub實現了注冊和反注冊回調接口的方法;

增加了RemoteCallbackList來管理AIDL遠程接口;

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

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

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

DeathRecipient

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

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

DeathRecipient的使用如下:

聲明DeathRecipient對象,實現其binderDied方法,當binder死亡時,會回調binderDied方法;

給Binder對象設置DeathRecipient對象。

在客戶端MainActivity聲明DeathRecipient:


/**

* Binder可能會意外死忙(比如Service Crash),Client監聽到Binder死忙后可以進行重連服務等操作

*/

IBinder.DeathRecipient deathRecipient =newIBinder.DeathRecipient() {

@Override

public void binderDied(){

Log.d(TAG,"binderDied");

if(messageSender !=null) {

messageSender.asBinder().unlinkToDeath(this,0);

messageSender =null;

}

////TODO:2017/2/28 重連服務或其他操作

setupService();

}

};

ServiceConnection serviceConnection =newServiceConnection() {

@Override

publicvoidonServiceConnected(ComponentName name, IBinder service){

//...

try{

//設置Binder死亡監聽

messageSender.asBinder().linkToDeath(deathRecipient,0);

}catch(RemoteException e) {

e.printStackTrace();

}

}

//...

};

Binder中兩個重要方法:

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

unlinkToDeath -> Binder死亡的情況下,解除該代理。

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

權限驗證

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

介紹兩種常用驗證方法:

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

在服務端的onTransact方法校驗客戶端包名,不通過校驗直接return false,校驗通過執行正常的流程。

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

android:name="com.example.aidl.permission.REMOTE_SERVICE_PERMISSION"

android:protectionLevel="normal"/>

服務端檢查權限的方法:


IBinder messageSender =newMessageSender.Stub() {

//...

@Override

public boolean onTransact(intcode, Parcel data, Parcel reply,intflags)throwsRemoteException{

/**

* 包名驗證方式

*/

String packageName =null;

String[] packages = getPackageManager().getPackagesForUid(getCallingUid());

if(packages !=null&& packages.length >0) {

packageName = packages[0];

}

if(packageName ==null|| !packageName.startsWith("com.example.aidl")) {

Log.d("onTransact","拒絕調用:"+ packageName);

return false;

}

return super.onTransact(code, data, reply, flags);

}

};

@Override

public IBinder onBind(Intent intent){

//自定義permission方式檢查權限

if(checkCallingOrSelfPermission("com.example.aidl.permission.REMOTE_SERVICE_PERMISSION") == PackageManager.PERMISSION_DENIED) {

return null;

}

returnmessageSender;

}

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

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

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

public class MyApp extends Application{

@Override

public voido nCreate(){

super.onCreate();

Log.d("process name", getProcessName());

}

//取得進程名

privateStringgetProcessName(){

ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);

List runningApps = am.getRunningAppProcesses();

if(runningApps ==null) {

returnnull;

}

for(ActivityManager.RunningAppProcessInfo procInfo : runningApps) {

if(procInfo.pid == Process.myPid()) {

return procInfo.processName;

}

}

return null;

}

}

每個進程創建,都會調用Application的onCreate方法,這是一個需要注意的地方,我們也可以根據當前進程的pid,拿到當前進程的名字去做判斷,然后做一些我們需要的邏輯,我們這個例子,拿到的兩個進程名分別是:

客戶端進程:com.example.aidl

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

總結

多進程app可以在系統中申請多份內存,但應合理使用,建議把一些高消耗但不常用的模塊放到獨立的進程,不使用的進程可及時手動關閉;

實現多進程的方式有多種:四大組件傳遞Bundle、Messenger、AIDL等,選擇適合自己的使用場景;

Android中實現多進程通訊,建議使用系統提供的Binder類,該類已經實現了多進程通訊而不需要我們做底層工作;

多進程應用,Application將會被創建多次;

注,原文地址:http://cjw-blog.net/2017/02/26/AIDL/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容