Android跨進程通信的方式也是比較多的,項目中用的比較多的應該是Messenger和AIDL,主要講一下兩者的實現(xiàn)
跨進程通信的方式
1、四大組件間傳遞Bundle;
2、文件共享,多進程讀寫一個相同的文件,獲取文件內容進行交互;
3、Messenger,利用Handler實現(xiàn)。(適用于多進程、單線程,不需要考慮線程安全),其底層基于AIDL。
4、AIDL(Android Interface Definition Language,Android接口定義語言),大部分應用程序不應該使用AIDL去創(chuàng)建一個綁定服務,因為它需要多線程能力,并可能導致一個更復雜的實現(xiàn)。
5、ContentProvider,常用于多進程共享數(shù)據(jù),比如系統(tǒng)的相冊,音樂等,我們也可以通過ContentProvider訪問到;
6、Socket傳輸數(shù)據(jù)。
Messenger
Messenger的實現(xiàn)比較簡單,底層基于AIDL,適用于多進程、單線程,不需要考慮線程安全;
實現(xiàn)思路
Messenger就是信使的意思;在服務端創(chuàng)建一個信使,在客戶端創(chuàng)建一個信使,當客戶端綁定服務的時候,服務端將信使傳遞給客戶端,客戶端就可以通過服務端的信使發(fā)送消息給服務端;客戶端也可以將自己的信使作為消息發(fā)送給服務端,服務端拿到客戶端的信使就可以發(fā)送消息給客戶端了,就實現(xiàn)了雙方的通信。
具體實現(xiàn)
服務端在onBind方法返回自己的信使給客戶端,等客戶端發(fā)送客戶端的信使過來后進行保存,然后就可以使用客戶端的信使給客戶端發(fā)消息了;
public class MessengerService extends Service {
/**
*客戶端的信使
*/
private Messenger clientMessenger;
@Nullable
@Override
public IBinder onBind(Intent intent) {
//返回自己的信使
return messenger.getBinder();
}
private Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
//接受客戶端的信使
case 1:
clientMessenger = msg.replyTo;
break;
//使用客戶端的信使發(fā)送消息
case 2:
if (clientMessenger != null) {
try {
String name=msg.getData().getString("name");
Bundle bundle = new Bundle();
bundle.putString("name", "messenger is "+name);
Message message = new Message();
message.what=2;
message.setData(bundle);
//發(fā)送消息
clientMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
break;
default:
break;
}
}
};
/**
* 服務本地信使
*/
private Messenger messenger = new Messenger(handler);
}
當然服務需要在AndroidManifest文件中申明為單獨進程運行
<service android:name=".service.MessengerService"
android:enabled="true"
android:exported="true"
android:process="com.yorhp.messenger.name">
<intent-filter>
<action android:name="com.yorhp.messenger.name"/>
</intent-filter>
</service>
客戶端,在服務連接后獲取到服務端的信使,將自己的信使發(fā)送到服務端
@SuppressLint("HandlerLeak")
private Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case 2:
String name = msg.getData().getString("name");
Toast.makeText(MainActivity.this, name, Toast.LENGTH_SHORT).show();
break;
default:
break;
}
}
};
/**
* messenger服務連接監(jiān)聽
*/
private ServiceConnection mMessengerServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
try {
//獲取到服務端信使
serviceMessenger = new Messenger(service);
Message message = new Message();
//將客戶端信使傳遞到服務端
message.replyTo = new Messenger(handler);;
message.what = 1;
//使用服務端信使發(fā)送
serviceMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
綁定服務后就可以使用服務端的信使發(fā)送消息給服務端了,因為實現(xiàn)借助Handler實現(xiàn),所以需要制定相應的協(xié)議,這里代碼發(fā)message.what=1為傳輸客戶端的信使,message.what = 2為請求數(shù)據(jù);
//綁定服務
bindService(new Intent(MainActivity.this, MessengerService.class), mMessengerServiceConnection, BIND_AUTO_CREATE);
//Messenger進行通信
findViewById(R.id.btnMessenger).setOnClickListener(v -> {
try {
Message message = new Message();
message.what = 2;
Bundle bundle=new Bundle();
bundle.putString("name","Tony");
message.setData(bundle);
//使用
serviceMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
});
實現(xiàn)還是比較簡單的,兩個APP間的通信也是一樣的實現(xiàn),只是需要對service進行配置,設置可以被其他APP啟動
<service android:name=".service.MessengerService"
android:enabled="true"
android:exported="true"
android:process="com.yorhp.messenger.name">
<intent-filter>
<action android:name="com.yorhp.messenger.name"/>
</intent-filter>
</service>
啟動方式也有所不同,改為隱式啟動,其他都一樣
Intent intentMessenger = new Intent();
intentMessenger.setAction("com.yorhp.messenger.name");
intentMessenger.setPackage("com.yorhp.interprocesscommunication");
bindService(intentMessenger, mMessengerServiceConnection, BIND_AUTO_CREATE);
AIDL
實現(xiàn)思路
服務端要創(chuàng)建一個Service用來監(jiān)聽客戶端的連接請求,然后創(chuàng)建一個AIDL文件,將暴露給客戶端的接口在這個AIDL文件中申明,最后在Service中實現(xiàn)接口即可;
客戶端需要綁定這個服務,然后將服務器返回的Binder對象轉成AIDL接口所屬的類型,然后就可以調用AIDL中的接口了;AIDL的接口方法是在服務端的Binder線程池中執(zhí)行的,因此當多個客戶端同時連接的時候,會存在多個線程同時訪問的情形,所以看實現(xiàn)的功能可能需要考慮多線程問題。
具體實現(xiàn)
創(chuàng)建AIDL,先在main文件夾下面創(chuàng)建一個aidl的文件夾,然后新建一個AIDL文件,里面會有一個默認的接口,在里面新建接口;系統(tǒng)會在/app/build/generated/aidl_source_output_dir/debug/out/com/yorhp/interprocesscommunication/下面生成Java文件,如果沒有可以rebuild一下
// IMyAidlInterface.aidl
package com.yorhp.interprocesscommunication;
// Declare any non-default types here with import statements
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);
}
然后新建Service,實現(xiàn)這個AIDL接口
public class AIDLService extends Service {
IMyAidlInterface.Stub stub=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 is "+nickName;
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return stub;
}
}
同樣,Service申明為單獨進程運行
<service android:name=".service.AIDLService"
android:enabled="true"
android:exported="true"
android:process="com.yorhp.aidl.test.service">
<intent-filter>
<action android:name="com.yorhp.aild.name"/>
</intent-filter>
</service>
客戶端在服務綁定的時候獲取到AIDL接口對應的對象,調用接口即可
/**
* AIDL服務連接監(jiān)聽
*/
private ServiceConnection mAIDLServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myAidlInterface = IMyAidlInterface.Stub.asInterface(service);
//Log.i("MainActivity","service connected");
Toast.makeText(MainActivity.this, "service connected", Toast.LENGTH_SHORT).show();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
//綁定服務
bindService(new Intent(MainActivity.this, AIDLService.class), mAIDLServiceConnection, BIND_AUTO_CREATE);
//AIDL進行通信
findViewById(R.id.btnAIDL).setOnClickListener(v -> {
try {
String name = null;
name = myAidlInterface.getName("Nick");
Toast.makeText(MainActivity.this, name, Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
e.printStackTrace();
}
});
如果另一個APP訪問這個進程需要將這個aidl文件都復制到另一個APP中,并且包名要一樣,然后隱式調用服務就好了
Intent intent = new Intent();
intent.setAction("com.yorhp.aild.name");
intent.setPackage("com.yorhp.interprocesscommunication");
bindService(intent, bindService, BIND_AUTO_CREATE);
AIDL文件支持:
- 基本數(shù)據(jù)類型
- String和CharSequence、
- List:只支持ArrayList,里面的元素都需要被AIDL支持、
- Map:只支持HashMap,里面的元素都需要被AIDL支持、
- Parcelable:所有實現(xiàn)了Parcelable的對象、
- AIDL:AIDL接口本身也可以;
Parcelable對象
自定義的對象需要實現(xiàn)Parcelable接口,舉個例子,新建一個User對象,實現(xiàn)Parcelable接口
public class User implements Parcelable {
/**
* 姓名
*/
private String name;
/**
* 年齡
*/
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
protected User(Parcel in) {
name = in.readString();
age = in.readInt();
}
public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel(Parcel in) {
return new User(in);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
}
}
在AIDL文件中新增接口,在AIDL中引用Parcelable對象和AIDL對象的時候必須要顯式的import進來,而且Parcelable對象也需要新建一個同名的AIDL文件,并在其中申明它為parcelable對象;
感覺這里關于包名的設定還是有點坑,如果寫寫demo把文件都放在一個文件夾下面沒什么問題,但是稍微修改一下目錄就會出問題,這里還是有一定的規(guī)則的,我把文件目錄展示出來;

首先新建一個User.aidl文件,里面的package是可以不和真實路徑一致的,但是必須和User.java文件的包名一致,不然會報錯
// User.aidl
//這個包名必須和java文件的包名一致,路徑和真實路徑不一樣也可以
package com.yorhp.interprocesscommunication.bean;
// Declare any non-default types here with import statements
parcelable User;
然后修改AIDL接口,需要顯式引用User對象,這個對象必須是User.aidl的文件路徑,不然會報錯,其實引用就是這個AIDL對象,不然申明了干什么
// IMyAidlInterface.aidl
package com.yorhp.interprocesscommunication;
//這個包名必須是User.aidl文件的路徑
import com.yorhp.interprocesscommunication.bean.User;
// Declare any non-default types here with import statements
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);
/**
*獲取用戶
*/
User getUserById(int id);
}
同樣Service實現(xiàn)新的接口
public class AIDLService extends Service {
IMyAidlInterface.Stub stub=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 is "+nickName;
}
@Override
public User getUserById(int id) throws RemoteException {
return new User("Tyhj",1);
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return stub;
}
}
客戶端進行接口調用
//綁定服務
bindService(new Intent(MainActivity.this, AIDLService.class), mAIDLServiceConnection, BIND_AUTO_CREATE);
//AIDL進行通信
findViewById(R.id.btnAIDL).setOnClickListener(v -> {
try {
String name = null;
User user=myAidlInterface.getUserById(0);
name = user.getName();
Toast.makeText(MainActivity.this, name, Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
e.printStackTrace();
}
});
同樣的如果要在另一個APP訪問服務,需要把AIDL和相關的類都拷貝過去,Parcelable類也需要放在同樣的包名下
AIDL對象
在方法中傳入一個監(jiān)聽接口是比較常用的方法,但是在AIDL中是不支持普通接口的,只支持AIDL接口;新建一個AIDL接口,用于監(jiān)聽用戶的變化,當用戶改變時,把最新的用戶信息通知到客戶端;
import com.yorhp.interprocesscommunication.bean.User;
// Declare any non-default types here with import statements
interface IOnUserChangedListener {
/**
*用戶改變監(jiān)聽
*/
void onUserChanged(in User user);
}
AIDL中的in、out、inout的區(qū)別
其中AIDL中除了基本數(shù)據(jù)類型和String外,其他參數(shù)必須標上方向:in、out或者inout;定向tag是AIDL中語法的一部分,其中in、out、inout是三個定向tag。在官網(wǎng)上關于Android定向tag的定義是這樣的:
All non-primitive parameters require a directional tag indicating which way the data goes .
Either in , out , or inout . Primitives are in by default , and connot be otherwise .
意思就是所有非基本類型的參數(shù)都需要一個定向tag來表明數(shù)據(jù)是如何走向的,要不是in,out或者inout?;緮?shù)據(jù)類型默認是in,而且不能是其他tag。
定向 tag 表示了在跨進程通信中數(shù)據(jù)的流向,其中 in 表示數(shù)據(jù)只能由客戶端流向服務端, out 表示數(shù)據(jù)只能由服務端流向客戶端,而 inout 則表示數(shù)據(jù)可以在服務端與客戶端之間雙向流通。其中的數(shù)據(jù)流向是針對在客戶端中的那個傳入方法的對象而言的。
對于in,服務端將會收到客戶端對象的完整數(shù)據(jù),但是客戶端對象不會因為服務端對傳參的修改而發(fā)生變動。類似的行為在Java中的表現(xiàn)是,在Java方法中,對傳進來的參數(shù)進行了深復制,傳進來的參數(shù)不會受到深復制后的對象的影響。這和in的行為有點類似。
對于out,服務端將會收到客戶端對象,該對象不為空,但是它里面的字段為空,但是在服務端對該對象作任何修改之后客戶端的傳參對象都會同步改動。類似的行為在Java中的表現(xiàn)是,在Java方法中,對傳進來的參數(shù)進行忽略,并new一個新對象,所有的操作都是圍繞著這個新對象進行的,最后將該新對象賦值給傳參對象。
對于inout ,服務端將會接收到客戶端傳來對象的完整信息,并且客戶端將會同步服務端對該對象的任何變動。類似的行為在Java中的表現(xiàn)是,在Java方法中,對傳進來的參數(shù)進行修改并返回。
然后繼續(xù)修改IMyAidlInterfaceAIDL文件,新增兩個方法,注冊和取消注冊
//這個包名必須是User.aidl文件的路徑
import com.yorhp.interprocesscommunication.bean.User;
import com.yorhp.interprocesscommunication.IOnUserChangedListener;
// Declare any non-default types here with import statements
interface IMyAidlInterface {
/**
*
*獲取姓名
*/
String getName(String nickName);
/**
*獲取用戶
*/
User getUserById(int id);
/**
*
*注冊監(jiān)聽
*/
void registerListener(IOnUserChangedListener listener);
/**
*
*取消監(jiān)聽
*/
void unRegisterListener(IOnUserChangedListener listener);
}
然后修改AIDLService文件,實現(xiàn)新的接口,模擬了數(shù)據(jù)改變
public class AIDLService extends Service {
/**
* 監(jiān)聽集合,自動進行線程同步,線程安全
*/
private CopyOnWriteArrayList<IOnUserChangedListener> listeners = new CopyOnWriteArrayList<>();
@Override
public void onCreate() {
super.onCreate();
//開啟線程模擬用戶數(shù)據(jù)改變,回調
new Thread(()->{
while (true){
for (IOnUserChangedListener listener:listeners){
try {
//返回用戶數(shù)據(jù)
listener.onUserChanged(new User("Tyhj"+System.currentTimeMillis(),10));
} catch (RemoteException e) {
e.printStackTrace();
}
}
SystemClock.sleep(2000);
}
}).start();
}
IMyAidlInterface.Stub stub = new IMyAidlInterface.Stub() {
@Override
public String getName(String nickName) throws RemoteException {
return "aidl is " + nickName;
}
@Override
public User getUserById(int id) throws RemoteException {
return new User("Tyhj", 1);
}
@Override
public void registerListener(IOnUserChangedListener listener) throws RemoteException {
if (!listeners.contains(listener)) {
listeners.add(listener);
}
}
@Override
public void unRegisterListener(IOnUserChangedListener listener) throws RemoteException {
listeners.remove(listener);
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return stub;
}
}
然后在客戶端調用新的方法,返回的數(shù)據(jù)不在主線程需要做線程切換
/**
* AIDL服務連接監(jiān)聽
*/
private ServiceConnection mAIDLServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myAidlInterface = IMyAidlInterface.Stub.asInterface(service);
//Log.i("MainActivity","service connected");
try {
//注冊監(jiān)聽
myAidlInterface.registerListener(new IOnUserChangedListener.Stub() {
@Override
public void onUserChanged(User user) throws RemoteException {
handler.post(()->{
Toast.makeText(MainActivity.this, user.getName(), Toast.LENGTH_SHORT).show();
});
}
});
} catch (RemoteException e) {
e.printStackTrace();
}
Toast.makeText(MainActivity.this, "service connected", Toast.LENGTH_SHORT).show();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
但是當取消注冊監(jiān)聽的時候失敗了,其實仔細看取消注冊接口的實現(xiàn)listeners.remove(listener);,在不同的進程里面,這兩個listener對象肯定不可能指向一個地址的,傳過來的這個對象肯定是會被轉換并生成成新對象的,因為跨進程傳對象本質就是序列化和反序列化,所以是會失敗的;可以使用RemoteCallbackList,是系統(tǒng)專門用于提供刪除跨進程listener的接口;它的實現(xiàn)是一個Map,key存了傳入listener.asBinder(),就是這個AIDL對象的Binder對象,這個對象對于同一個客戶端是不變的,value就是保存了這個AIDL對象的一個封裝對象;
IBinder binder = callback.asBinder();
try {
Callback cb = new Callback(callback, cookie);
binder.linkToDeath(cb, 0);
mCallbacks.put(binder, cb);
return true;
} catch (RemoteException e) {
return false;
}
RemoteCallbackList不是一個List對象,所以操作也有些不同,修改服務端代碼
public class AIDLService extends Service {
/**
* 監(jiān)聽集合,自動進行線程同步,線程安全
*/
private RemoteCallbackList<IOnUserChangedListener> listeners = new RemoteCallbackList<>();
@Override
public void onCreate() {
super.onCreate();
//開啟線程模擬用戶數(shù)據(jù)改變,回調
new Thread(()->{
while (true){
final int n=listeners.beginBroadcast();
for (int i=0;i<n;i++){
try {
IOnUserChangedListener listener=listeners.getBroadcastItem(i);
//返回用戶數(shù)據(jù)
listener.onUserChanged(new User("Tyhj"+System.currentTimeMillis(),10));
} catch (RemoteException e) {
e.printStackTrace();
}
}
listeners.finishBroadcast();
SystemClock.sleep(2000);
}
}).start();
}
IMyAidlInterface.Stub stub = new IMyAidlInterface.Stub() {
@Override
public String getName(String nickName) throws RemoteException {
return "aidl is " + nickName;
}
@Override
public User getUserById(int id) throws RemoteException {
return new User("Tyhj", 1);
}
@Override
public void registerListener(IOnUserChangedListener listener) throws RemoteException {
listeners.register(listener);
}
@Override
public void unRegisterListener(IOnUserChangedListener listener) throws RemoteException {
listeners.unregister(listener);
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return stub;
}
}
里面需要注意的是listeners.beginBroadcast();和listeners.finishBroadcast();必須配對使用;
斷線重連
為了程序的健壯性,我們還需要考慮服務意外死亡的情況;當服務意外停止的時候我們需要重新連接服務,第一種方法比較簡單,就是在onServiceDisconnected方法中重連服務;第二種方法就是給Binder設置DeathRecipient監(jiān)聽,當Binder死亡時,我們會收到binderDied方法的回調;兩種方式都可以使用,區(qū)別在于onServiceDisconnected是在客戶端的主線程中被調用的,而binderDied是在客戶端的Binder線程池中被回調,使用的時候需要注意一下;
ServiceConnection mServiceConnection=new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
try {
iBinder.linkToDeath(new IBinder.DeathRecipient() {
@Override
public void binderDied() {
//服務關閉,可以在此重連服務
}
},0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
//服務關閉,可以在此重連服務
}
};
權限驗證
默認情況下,遠程服務是任何人都可以連接的,為了保證服務的安全,我們需要在服務中加上權限驗證;
第一種方法是在onBind方法中驗證,如果驗證不通過就返回null,這樣驗證失敗的客戶端就無法綁定服務;驗證方式可以使用權限驗證,我們在AndroidManifest文件中申明權限,隨便取一個名字;
<permission
android:name="com.yorhp.aidl.permission.ACCESS_USER_INFO"
android:protectionLevel="normal" />
然后就可以在onBind方法中進行驗證,如果客戶端連接需要在AndroidManifest里面申明該權限
@Override
public IBinder onBind(Intent intent) {
int check=checkCallingOrSelfPermission("com.yorhp.aidl.permission.ACCESS_USER_INFO");
if(check== PackageManager.PERMISSION_DENIED){
//權限申請失敗,返回null
return null;
}
return mBidner;
}
第二種方式是在AIDL接口的onTransact方法中進行驗證,如果返回了false,服務端就不會終止執(zhí)行AIDL中的方法,從而達到保護服務端的目的;具體驗證方法也可以使用權限驗證,和上面是一樣的;在這個方法里面我們通過getCallingPid()和getCallingUid()可以拿到客戶端所屬應用的Pid和Uid,通過這兩個參數(shù)可以做一些驗證操作,比如可以驗證包名
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
String packageName = null;
String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
if (packages != null && packages.length > 0) {
packageName = packages[0];
}
if (packageName == null || !packageName.startsWith("com.yorhp")) {
//返回失敗
return false;
}
return super.onTransact(code, data, reply, flags);
}
總結
講道理,其實仔細看看還是挺簡單的
項目地址
服務端(也包含客戶端)地址:https://github.com/tyhjh/AIDL-Service
客戶端地址:https://github.com/tyhjh/AIDL-Client