本文Demo見:https://github.com/w1374720640/IPCThroughServices
結合Demo閱讀本文效果更好
利用Service進行進程間通信有兩種方式,分別是Messenger和AIDL,Messenger底層是基于AIDL的封裝,使用更加簡潔高效,無需考慮并發問題,只能串行通信,有并發需求的只能用AIDL,不能用Messenger,一般情況下使用Messenger即可滿足日常需求。Messenger和AIDL跨進程通信只能傳遞基本數據類型及實現Parcelable接口的類。
通常提供服務的進程稱為服務端,獲取服務的稱為客戶端,客戶端通過bindService()
的方式綁定服務端,獲取IBinder的實例。本文創建了兩個項目,包名分別為com.example.service
和com.example.client
,對應服務端和客戶端,下文不再重復說明。
Messenger
原理
查看Messenger源碼可以發現,Messenger包含一個IMessenger的成員變量mTarget,通過mTarget可以向Handler傳遞Message消息。獲取mTarget對象有兩種方式,一種是在構造器中利用Handler獲取mTarget的實例:
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
另一種方法是在構造器中通過IBinder對象獲取mTarget的實例:
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
注:可能有的同學發現Android sdk中沒有IMessenger類,顯示紅色字體,那是因為IMessenger是一個AIDL文件,完整路徑為
android.os.IMessenger.aidl
,只有一個抽象方法void send(in Message msg);
(暫時忽略Message前面的in),AIDL文件編譯后會生成同名的Java文件,Android sdk中不包含AIDL文件及編譯后生成的臨時文件,所以系統找不到IMessenger類,下文會詳細介紹相關知識。
服務端創建Messenger對象用第一個構造器,客戶端綁定服務端時用第二個構造器獲取Messenger對象,兩個進程間傳遞的對象為Messenger對象中的mTarget變量,通過mTarget對象可以跨進程發送Message給Handler:
//Messenger.java
public void send(Message message) throws RemoteException {
mTarget.send(message);
}
使用方法
模擬客戶端從服務端獲取一個100以內的隨機數。
在服務端新建一個RemoteMessengerService繼承Service,并在AndroidManifest.xml中注冊隱式啟動方式
<service android:name=".RemoteMessengerService">
<intent-filter>
<action android:name="com.example.service.RemoteMessengerService"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
未指定隱式啟動的要加屬性android:exported="true"
表示可以被其他進程啟動,添加<intent-filter>
標簽后默認為true。
在RemoteMessengerService中創建Handler對象mRemoteHandler,調用Messenger的第一個構造器,利用mRemoteHandler創建Messenger對象:
Messenger mRemoteMessenger = new Messenger(mRemoteHandler);
重寫Service的onBind方法,返回Messenger中的mTarget變量:
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mRemoteMessenger.getBinder();
}
在客戶端新建一個MessengerActivity,啟動時綁定服務端:
Intent intent = new Intent();
intent.setAction("com.example.service.RemoteMessengerService");
//Android 5.0以上需要設置包名
intent.setPackage("com.example.service");
bindService(intent,mConnection,BIND_AUTO_CREATE);
綁定成功后根據服務端返回的IBinder對象調用Messenger的第二個構造器,創建Messenger對象,通過Messenger對象可以向服務端發送消息:
ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mRemoteMessenger = new Messenger(service);
isConnect = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
isConnect = false;
}
};
向服務端發送消息:
Message message = Message.obtain();
message.what = 0;
try {
mRemoteMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
然后在服務端注冊的Handler就可以接收到客戶端發送的Message了。
一個簡單的進程間通信就基本完成了,這時只能由客戶端向服務端發送消息,服務端無法向客戶端傳遞數據,要解決這個問題,需要在客戶端新建一個Handler及Messenger,在發送消息時將Messenger對象傳遞給Message的replyTo變量,服務端的Handler收到客戶端的Message后,獲取replyTo變量,通過獲取到的Messenger對象向客戶端發送消息。
修改后的客戶端代碼:
//MessengerActivity.java
Handler mClientHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 0:
Log.d(TAG,"Client receive message:" + msg.arg1);
break;
default:
break;
}
}
};
private Messenger mClientMessenger = new Messenger(mClientHandler);
private void sendMessage(){
Log.d(TAG,"Client sendMessage()");
if (!isConnect) return;
Message message = Message.obtain();
message.what = 0;
// 將客戶端的Messenger對象傳遞到服務端,
// 不設置則只能單向通信,服務端無法向客戶端傳遞信息
message.replyTo = mClientMessenger;
try {
// 向服務端發送消息
mRemoteMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
修改后的服務端代碼:
//RemoteMessengerService.java
Handler mRemoteHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
Log.d(TAG,"Service receive client message,msg.what=" + msg.what);
switch (msg.what){
case 0:
Message message = Message.obtain();
message.what = 0;
Random random = new Random();//獲取隨機數
message.arg1 = random.nextInt(100);
// msg的replyTo變量是客戶端生成的Messenger對象
// 如果為空則不能由服務端向客戶端傳遞消息,只能單向通信
Messenger mClientMessenger = msg.replyTo;
if (mClientMessenger == null) return;
try {
// 向客戶端回傳信息
mClientMessenger.send(message);
Log.d(TAG,"Service reply client message,random Num is:" + message.arg1);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
break;
}
}
};
先運行服務端,再運行客戶端,客戶端向服務端發送消息后Log如下(注意包名):
AIDL
在Android studio中AIDL文件位于app/src/main/aidl
目錄下,客戶端與服務端AIDL文件相同,包名為服務端包名,只能傳遞基本數據類型及實現Parcelable接口的類。
AIDL文件實際為模板文件,用于生成復雜但套路固定的Java文件,生成的Java文件位于app/build/generated/source/aidl/debug/<packagemane>
目錄下,通過Java文件實現IPC通信,生成的Java文件單獨使用具有同等效果,有興趣的可以看看具體的實現,這里不過多講解。
使用方式
模擬客戶端通過用戶ID查詢用戶信息、向服務端添加用戶、服務端調用客戶端方法實現雙向通信。
新建Person類:先設置Person類的成員變量,然后實現Parcelable接口,在類名上alt + enter兩次即可快捷生成Parcelable模板代碼(Parcelable接口具體使用方式自行google),如下所示:
/*
若Person.java文件放在aidl目錄下,需要在app/build.gradle的android標簽中添加
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}
*/
public class Person implements Parcelable{
private int id;
private String name;
private int age;
private String phone;
public Person(){
}
protected Person(Parcel in) {
id = in.readInt();
name = in.readString();
age = in.readInt();
phone = in.readString();
}
public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel in) {
return new Person(in);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(name);
dest.writeInt(age);
dest.writeString(phone);
}
/**
* 實現Parcelable接口時不會自動創建此方法,
* 但如果aidl文件中Person類添加了out或inout標簽時必須手動實現此方法
*/
public void readFromParcel(Parcel dest) {
//注意,此處的讀值順序應當是和writeToParcel()方法中一致的
id = dest.readInt();
name = dest.readString();
age = dest.readInt();
phone = dest.readString();
}
@Override
public String toString() {
return "\"id=" + id + ",name=" + name + ",age=" + age + ",phone=" + phone + "\"";
}
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
}
需要注意的是,模板代碼并沒有生成readFromParcel()
方法,需要我們按照writeToParcel()
的寫入順序依次讀取參數,若未實現readFromParcel()
方法,則aidl中只能用in
修飾Person類,不能用out
或inout
修飾,如上文中提到的IMessenger唯一抽象方法void send(in Message msg);
。那三個修飾符有什么區別呢?以IMessenger的send方法為例:
- 如果用
in
修飾,那msg對象為原對象的副本,msg值的變化不會影響原對象。 - 如果用
out
修飾,無論msg傳入的值是什么,都會在方法內部創建一個新的對象,方法執行結束會將新對象寫入原msg對象,也就是說,無論你輸入什么都忽視,結束后再把你的值改掉(夠霸道的)。 - 如果用
inout
修飾,則會復制輸入對象的值,方法執行完后再寫入原對象。 -
in
修飾的對象執行效率最高,也最常用,out
和inout
因為需要回寫,效率較低,盡量少用。 - 傳遞基本數據類型、String、aidl文件不用也不能添加標簽,默認為
in
。
創建AIDL文件:在服務端app/src/main
目錄下新建aidl/com/example/service
文件夾,然后鼠標點擊File>new>AIDL>AIDL File,輸入文件名,創建RemoteInterface.aidl文件:
// RemoteAidlInterface.aidl
package com.example.service;
//即使在同一個包下,也需要手動導入類
import com.example.service.ClientCallback;
import com.example.service.Person;
//編譯后生成的Java文件在app/build/generated/source/aidl/dubug/<packagename>目錄下
//服務端創建,客戶端調用
interface RemoteInterface {
// 根據用戶ID獲取用戶信息
Person getPersonById(int id);
// 添加用戶,此處用in修飾
void addPerson(in Person person);
// 向服務端注冊監聽
void registClientCallback(ClientCallback callback);
// 取消注冊
void unRegistClientCallback(ClientCallback callback);
}
ClientCallback.aidl文件如下:
// ClientAidlCallback.aidl
package com.example.service;
//客戶端向服務端注冊,客戶端創建,服務端調用
interface ClientCallback {
// 啟動客戶端
void start();
// 停止客戶端
void stop();
}
如果想在進程間傳遞對象,除了需要實現Parcelable接口外,還需要創建一個aidl文件申明該類可用于進程間通信:
// Person.aidl
package com.example.service;
//注意!不是用interface開頭,用parcelable開頭,p小寫,表示Person類可以進行進程間通信
//用interface開頭會生成同名的Java文件,用parcelable開頭不會,只有一個Person.java文件
parcelable Person;
創建完成,clean一下工程,如果編譯報錯說明aidl文件編寫有問題,比如是否正確導包(即使包名相同也需要手動導包),parcelable是否是小寫,傳遞序列化對象前必須加in|out|inout
標記,重命名文件時其他地方不會自動替換新文件名,總之,編寫aidl文件時基本沒有任何提示,編譯不通過肯定是你aidl文件寫的有問題。編譯通過后檢查一下在app/build/generated/source/aidl/dubug/<packagename>
目錄下是否生成相應Java文件。
復制aidl目錄下文件夾及所有文件到客戶端相同目錄下,編譯。
aidl目錄如下圖:
編寫服務端及客戶端代碼
在服務端新建RemoteAidlService繼承Service,在AndroidManifest.xml文件中注冊:
<service android:name=".RemoteAidlService">
<intent-filter>
<action android:name="com.example.service.RemoteAidlService"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
創建RemoteInterface.Stub對象,RemoteInterface.Stub類是RemoteInterface.aidl文件編譯后生成的靜態內部類,創建RemoteInterface.Stub對象需要實現RemoteInterface.aidl中定義的抽象方法,重寫onBind()方法,返回創建的RemoteInterface.Stub對象:
public class RemoteAidlService extends Service {
private static final String TAG = "AidlTest";
private static final int START_ALL_CLIENT = 0;
private static final int STOP_ALL_CLIENT = 1;
// 模擬服務端存儲客戶端傳遞的數據
private List<Person> mPersonList = new ArrayList<>();
// 一個服務端可以對應多個客戶端,即包含多個ClientCallback對象,
// 使用RemoteCallbackList可以在客戶端意外斷開連接時移除ClientCallback,防止DeadObjectException
private RemoteCallbackList<ClientCallback> mCallbackList = new RemoteCallbackList<>();
// 通過修改值確定是否在regist后start客戶端,默認不啟動
private boolean isAutoStartAfterRegist = false;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mRemoteInterface;
}
@Override
public void onDestroy() {
super.onDestroy();
// 服務結束是注意移除所有數據
mCallbackList.kill();
}
/**
* RemoteInterface.Stub為Android根據aidl文件生成的實現類,
* 實現了RemoteInterface接口,間接實現了IBinder接口,
* 客戶端綁定時將mRemoteInterface對象返回給客戶端,
* 在服務端定義,在客戶端調用
*/
private RemoteInterface.Stub mRemoteInterface = new RemoteInterface.Stub() {
@Override
public Person getPersonById(int id) throws RemoteException {
// 返回固定值
Person person = new Person();
person.setId(id);
person.setName("小紅");
person.setAge(18);
person.setPhone("120");
Log.d(TAG, "Service getPersonById()");
return person;
}
@Override
public void addPerson(Person person) throws RemoteException {
mPersonList.add(person);
Log.d(TAG, "Service addPerson(),person="
+ (person == null ? null : person.toString()));
}
@Override
public void registClientCallback(ClientCallback callback) throws RemoteException {
// 向服務端注冊回調
mCallbackList.register(callback);
Log.d(TAG, "Service registClientCallback()");
if (isAutoStartAfterRegist) {
mHandler.sendEmptyMessageDelayed(START_ALL_CLIENT, 3 * 1000);
}
}
@Override
public void unRegistClientCallback(ClientCallback callback) throws RemoteException {
// 服務端取消注冊回調
mCallbackList.unregister(callback);
Log.d(TAG, "Service unRegistClientCallback()");
}
};
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case START_ALL_CLIENT:
startAllClient();
mHandler.sendEmptyMessageDelayed(STOP_ALL_CLIENT, 3 * 1000);
break;
case STOP_ALL_CLIENT:
stopAllClient();
break;
}
}
};
/**
* 調用所有客戶端的start()方法
*/
public void startAllClient() {
Log.d(TAG, "Service startAllClient()");
// 從列表中取數據時先調用beginBroadcast()方法獲取總數,循環取出數據后finishBroadcast()
int size = mCallbackList.beginBroadcast();
for (int i = 0;i < size;i++){
try {
mCallbackList.getBroadcastItem(i).start();
} catch (RemoteException e) {
e.printStackTrace();
}
}
mCallbackList.finishBroadcast();
}
/**
* 調用所有客戶端的stop()方法
*/
public void stopAllClient() {
Log.d(TAG, "Service stopAllClient()");
int size = mCallbackList.beginBroadcast();
for (int i = 0;i < size;i++){
try {
mCallbackList.getBroadcastItem(i).stop();
} catch (RemoteException e) {
e.printStackTrace();
}
}
mCallbackList.finishBroadcast();
}
}
客戶端創建AidlActivity,里邊有四個按鈕,分別對應RemoteInterface.aidl定義的四個方法:getPersonById()、addPerson()、registClientCallback()、unRegistClientCallback(),點擊按鈕調用相應方法。同時實現了ClientCallback.Stub類,向服務端注冊后服務端可以調用客戶端相應方法。
public class AidlActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "AidlTest";
private boolean isConnect;
// 服務端的RemoteInterface對象,綁定服務時創建
private RemoteInterface mRemoteInterface = null;
// 客戶端的ClientCallback對象
// 在服務端注冊后服務端可以調用客戶端方法
private ClientCallback.Stub mClientCallback = new ClientCallback.Stub() {
@Override
public void start() throws RemoteException {
Log.d(TAG, "Client start");
}
@Override
public void stop() throws RemoteException {
Log.d(TAG, "Client stop");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_aidl);
findViewById(R.id.bt_get).setOnClickListener(this);
findViewById(R.id.bt_add).setOnClickListener(this);
findViewById(R.id.bt_regist).setOnClickListener(this);
findViewById(R.id.bt_unregist).setOnClickListener(this);
connectService();
}
ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
isConnect = true;
// 綁定服務后從服務端獲取RemoteInterface對象
mRemoteInterface = RemoteInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
isConnect = false;
// 與服務端意外斷開時自動重連
connectService();
}
};
private void connectService() {
Intent intent = new Intent();
intent.setAction("com.example.service.RemoteAidlService");
// Android 5.0以上需要設置包名
intent.setPackage("com.example.service");
bindService(intent, mConnection, BIND_AUTO_CREATE);
}
private void disConnectService() {
unbindService(mConnection);
isConnect = false;
}
@Override
public void onClick(View v) {
if (!isConnect) return;
switch (v.getId()) {
case R.id.bt_get:
Person person = getPerson(10);
Log.d(TAG, "Client getPerson return, person=" +
(person == null ? null : person.toString()));
break;
case R.id.bt_add:
Person person1 = new Person();
person1.setId(100);
person1.setName("小花");
person1.setAge(16);
person1.setPhone("110");
addPerson(person1);
break;
case R.id.bt_regist:
registCallback();
break;
case R.id.bt_unregist:
unRegistCallback();
break;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
disConnectService();
}
/**
* 從服務端獲取數據
*/
private Person getPerson(int id) {
Log.d(TAG,"Client getPerson()");
if (!isConnect) return null;
Person person = null;
try {
person = mRemoteInterface.getPersonById(id);
} catch (RemoteException e) {
e.printStackTrace();
}
return person;
}
/**
* 向服務端添加數據
*/
private void addPerson(Person person) {
Log.d(TAG,"Client addPerson()");
if (!isConnect) return;
try {
mRemoteInterface.addPerson(person);
} catch (RemoteException e) {
e.printStackTrace();
}
}
/**
* 向服務端注冊回調,注冊后服務端才能調用客戶端方法
*/
private void registCallback() {
Log.d(TAG,"Client registCallback()");
if (!isConnect) return;
try {
mRemoteInterface.registClientCallback(mClientCallback);
} catch (RemoteException e) {
e.printStackTrace();
}
}
/**
* 取消注冊
*/
private void unRegistCallback() {
Log.d(TAG,"Client unRegistCallback()");
if (!isConnect) return;
try {
mRemoteInterface.unRegistClientCallback(mClientCallback);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
先運行服務端,再運行客戶端,分別點擊客戶端的四個按鈕,觀察Log輸出如下
將的RemoteAidlService的isAutoStartAfterRegist
屬性改為true后點擊注冊按鈕,Log如下,注冊后3秒自動調用客戶端的start()方法,再3秒后調用客戶端的stop()方法。
結語
Demo見頂部鏈接,文章參考: