提到的进程间通信(IPC:Inter-Process Communication),在Android系统中,一个进程是不能直接访问另一个进程的内存的,需要提供一些机制在不同的进程之间进行通信,Android官方推出了AIDL(Android Interface Definition Language),它是基于Binder机制的,具体Binder机制的东西就很多了,网上很多资料,我们就不在这里说了,推荐文章。
后面两种可以跨进程通信,是基于Binder机制的通信方式。第一种我们多用于service直接的通信,但是当sevice被设为远程服务时(设为:remote),我们就要用后面两种方式来进行通信了。
在确定使用什么机制之前,首先了解应用场景。Android系统中,如果组件与服务通信是在同一进程,就使用第一种方式;如果是跨进程通信,使用第二种和第三种,两者不同在于,Messenger不能处理多线程并发请求。
对用于service中,一般使用bindService()启动服务。这是一种比startService更复杂的启动方式,同时使用这种方式启动的service也能完成更多的事情,比如其他组件可向其发送请求,接受来自它的响应,甚至通过它来进行IPC等等。我们通常将绑定它的组件称为客户端,而称它为服务端。 如果要创建一个支持绑定的service,我们必须要重写它的onBind()方法。这个方法会返回一个IBinder对象,它是客户端用来和服务器进行交互的接口。而要得到IBinder接口,我们通常有三种方式:继承Binder类,使用Messenger类,使用AIDL。
服务端配置
public class BinderService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
public class MyBinder extends Binder{
public void setMessage(String s,BinderActivity.CallBack callBack){
Log.i("Binder_Service", s );
callBack.sendMsg("从服务端发来的Binder响应");
}
}
}
客户端配置
public class BinderActivity extends AppCompatActivity {
private BinderService.MyBinder mMyBinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_binder);
setTitle("客户端");
//绑定服务
Intent intent = new Intent(BinderActivity.this,BinderService.class);
bindService(intent, conn, BIND_AUTO_CREATE);
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mMyBinder.setMessage("从客服端发来的Binder请求",new CallBack() {
@Override
public void sendMsg(String s) {
Log.i("Binder_Client", s );
}
});
}
});
}
/**
* 服务回调方法
*/
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mMyBinder = (BinderService.MyBinder) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
mMyBinder = null;
}
};
@Override
protected void onDestroy() {
super.onDestroy();
//解绑服务,回收资源
unbindService(conn);
}
public interface CallBack{
void sendMsg(String s);
}
}
manifest注册
注意不要使用远程服务,如果一定要使用远程服务就要用后面的两种跨进程方式。
结果
服务端打印日志:
08-28 15:57:21.009 2199-2199/com.veer.aidl I/Binder_Service: 从客服端发来的Binder请求
客户端打印日志:
08-28 15:57:21.009 2199-2199/com.veer.aidl I/Binder_Client: 从服务端发来的Binder响应
以前讲到跨进程通信,我们总是第一时间想到AIDL(Android接口定义语言),实际上,使用Messenger在很多情况下是比使用AIDL简单得多的,两种比较就能看出来。
大家看到Messenger可能会很轻易的联想到Message,然后很自然的进一步联想到Handler——没错,Messenger的核心其实就是Message以及Handler来进行线程间的通信。下面讲一下通过这种方式实现IPC的步骤:
服务端在其 Handler 中(具体地讲,是在 handleMessage() 方法中)接收每个 Message
服务端代码
public class MessengerService extends Service {
private Messenger mMessenger = new Messenger(new MessengerHandler());
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
private class MessengerHandler extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 0:
Log.i("Binder_Service", "从客户端发来的Messenger请求");
Message message = Message.obtain(null,1);
try {
msg.replyTo.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
}
客户端代码
public class MessengerActivity extends AppCompatActivity {
private Messenger mMessenger;
private Messenger mMyClientMessenger = new Messenger(new MyClientHandler());
@SuppressLint("WrongConstant")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger);
setTitle("客服端");
Intent intent = new Intent();
intent.setAction("com.veer.aidl.MessengerService");
intent.setPackage("com.veer.aidl");
bindService(intent, mCoon, BIND_AUTO_CREATE);
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
Message message = Message.obtain(null,0);
message.replyTo = mMyClientMessenger;
mMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
private class MyClientHandler extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
Log.i("Binder_client", "从服务端发来的Messenger请求,已经接受到通知");
break;
default:
super.handleMessage(msg);
}
}
}
/**
* 服务回调方法
*/
private ServiceConnection mCoon = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mMessenger = new Messenger(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onDestroy() {
super.onDestroy();
//解绑服务,回收资源
unbindService(mCoon);
}
}
manifest注册
运行结果
服务端
客户端
用Messenger来进行IPC的话整体的流程是非常清晰的,Message在其中起到了一个信使的作用,通过它客户端与服务端的信息得以互通。
AIDL,即Android Interface Definition Language,Android接口定义语言。它是一种IDL语言,可以拿来生成用于IPC的代码。在我看来,它其实就是一个模板。为什么这样说呢?在我们的使用中,实际上起作用的并不是我们写的AIDL代码,而是系统根据它生成的一个IInterface实例的代码。而如果大家多生成几个这样的实例,然后把它们拿来比较,你会发现它们都是有套路的——都是一样的流程,一样的结构,只是根据具体的AIDL文件的不同有细微的变动。所以其实AIDL就是为了避免我们一遍遍的写一些千篇一律的代码而出现的一个模板。
那么如何使用AIDL来通过bindService()进行线程间通信呢?基本上有下面这些步骤:
通过得到的实例调用其暴露的方法
1、服务端
创建aidl接口
在面下面创建aidl文件夹,然后创建我们的aidl接口文件,创建完了、之后一定要重新编译一下项目,因为Androidstudio会自动帮我创建一下文件。
这里是binder机制应用层的原理,感兴趣的同学可以了解下。
然后开始写服务端代码
public class AidlService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
public class MyBinder extends IMyAidlInterface.Stub {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
@Override
public int add(int num1, int num2) throws RemoteException {
Log.i("Aidl_Service", "从客户端发来的AIDL请求:num1->" + num1 + "::num2->" + num2);
return num1+num2;
}
};
}
这里也比较简单,和service中binder类似,但不是集成binder ,我们定义的aidl接口。
服务端manifest注册
客户端配置
客户端需要把服务端的aidl文件copy到客服端上来,放置的位置同服务端。然后一样,重新编译项目。
客户端代码
public class AidlActivity extends AppCompatActivity {
IMyAidlInterface iMyAidlInterface;
private TextView mTvService;
@SuppressLint("WrongConstant")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_aidl);
setTitle("客户端");
mTvService = (TextView) findViewById(R.id.tv_service);
//绑定服务
Intent intent = new Intent();
intent.setAction("com.veer.aidl.AidlService");
intent.setPackage("com.veer.aidl");
bindService(intent, conn, BIND_AUTO_CREATE);
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
add();
}
});
}
/**
* 点击“AIDL”按钮事件
*/
public void add() {
try {
if(iMyAidlInterface!=null){
int res = iMyAidlInterface.add(1, 2);
mTvService.setText("从服务端调用成功的结果:" + res);
Log.i("Aidl_Client", "从服务端调用成功的结果:" + res);
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
/**
* 服务回调方法
*/
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
iMyAidlInterface = null;
}
};
@Override
protected void onDestroy() {
super.onDestroy();
//解绑服务,回收资源
unbindService(conn);
}
}
这里和binder方式,获取ibinder的方式不同。
服务端日志
客户端日志
这样我们就完成了两个应用间的通信了。
首先,在实现的难度上,肯定是Messenger要简单的多——至少不需要写AIDL文件了(虽然如果认真的究其本质,会发现它的底层实现还是AIDL)。另外,使用Messenger还有一个显著的好处是它会把所有的请求排入队列,因此你几乎可以不用担心多线程可能会带来的问题。
但是这样说来,难道AIDL进行IPC就一无是处了么?当然不是,如果是那样的话它早就被淘汰了。一方面是如果项目中有并发处理问题的需求,或者会有大量的并发请求,这个时候Messenger就不适用了——它的特性让它只能串行的解决请求。另外,我们在使用Messenger的时候只能通过Message来传递信息实现交互,但是在有些时候也许我们需要直接跨进程调用服务端的方法,这个时候又怎么办呢?只能使用AIDL。
所以,这两种IPC方式各有各的优点和缺点,具体使用哪种就看具体的需要了——当然,能使用简单的就尽量使用简单的吧。
Android跨进程通信的知识点其实很多,我们这里只是简单介绍了用法,还需要我们更加深入的了解。我也一直会更新博客,项目我会放到GitHub上,一直维护,增加更加的功能。GitHub地址:https://github.com/fuweiwei/VAidl
源码下载地址
GitHub地址