*本篇文章已受权微信公众号 guolin_blog (郭霖)独家发布javascript
进程间通讯(Inter-Process Communication),简称IPC,就是指进程与进程之间进行通讯.通常来讲,一个app只有一个进程,可是可能会有多个线程,因此咱们用得比较多的是多线程通讯,好比handler,AsyncTask.java
可是在一些特殊的状况下,咱们app会须要多个进程,或者是咱们在远程服务调用时,就须要跨进程通讯了android
Android设置多进程的步骤很简单,只用在清单文件中为四大组件加上process属性缓存
<service android:name=".MessagerService"
android:process=":messager">
</service>复制代码
( :messager 最终的进程名会变成 包名+:messager)服务器
虽然多进程设置起来很简单,可是使用的时候却会有一系列的问题微信
(两个进程对应的是不一样的内存区域)多线程
进程间通讯传递的对象是有严格要求的,除了基本数据类型,其余对象要想能够传递,必须可序列化,Android实现可序列化通常是经过实现Serializable或者是Parcelable并发
若是你在进程通讯中不须要传非基本数据类型的对象,那么你能够不了解序列化,可是可序列化是进程间通讯的基础,因此仍是建议不了解的朋友先熟悉一下app
笔者以前介绍过序列化的相关知识,这里就不重复介绍了less
跨进程通讯的方法有不少,好比经过Intent传递,经过AIDL以及Messager通讯,经过socket通讯,这里主要介绍的是基于Binder的AIDL和Messager
Intent进行数据的传递是咱们平时最经常使用的,他的原理实际上是对于Binder的封装,可是他只能作到单向的数据传递,因此并不能很好的实现跨进程通讯,咱们这里就不展开来介绍了
Messager的底层也是基于Binder的,其实应该说他是在AIDL的基础上封装了一层
通常来讲安卓中使用Binder主要是经过绑定服务(bindService),服务端(这里指的不是后台,是指其中一个进程)主要是运行Service,客户端经过bindService获取到相关的Binder,Binder就做为桥梁进行跨进程的通讯.
这里咱们先演示同一个应用内的多进程通讯
首先咱们先建立一个Service,
public class XiayuService extends Service{
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}复制代码
并在清单文件中配置他的进程
<service android:name=".XiayuService"
android:process=":xiayu"
/>复制代码
在Service里面建立一个Hander用来接受消息
private final static Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
System.out.println("地瓜地瓜,我是土豆,我是土豆, 听到请回答,听到请回答");
}
};复制代码
在Service里面建立一个Messager,并把Handler放入其中
private final static Messenger mMessenger = new Messenger(mHandler);复制代码
重写onbind方法,返回Messager里面的Binder
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}复制代码
建立一个对象实现ServiceConnection
private class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//当链接上服务后会调用这个方法
//TODO
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}复制代码
绑定服务
Intent intent = new Intent(MainActivity.this, XiayuService.class);
MyServiceConnection myServiceConnection = new MyServiceConnection();
bindService(intent, myServiceConnection, BIND_AUTO_CREATE);复制代码
绑定服务后,会调用ServiceConnection的onServiceConnected方法,经过Messager发送消息,服务器端的Handler就可以收到消息了
private class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//经过Binder建立Messager
Messenger messenger = new Messenger(service);
//建立msg
Message msg = Message.obtain();
try {
//经过Messager发送消息
messenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}复制代码
这样的话咱们就可以经过bindService获取到一个包含Binder的Messager进行通讯了,可是咱们目前只实现了客户端对服务器端传递消息,那么服务器端如何对客户端传递消息呢?
咱们先对服务器端的代码进行修改,首先修改Service的Handler
(关键代码是 Messenger messenger = msg.replyTo)
private final static Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
System.out.println("地瓜地瓜,我是土豆,我是土豆, 听到请回答,听到请回答");
//获取Messager
Messenger messenger = msg.replyTo;
//建立消息
Message msg_reply = Message.obtain();
try {
//发送
messenger.send(msg_reply);
} catch (RemoteException e) {
e.printStackTrace();
}
}
};复制代码
接着咱们在客户端也增长一个Handler和Messager来处理消息
private final static Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
System.out.println("土豆,土豆,我是地瓜,我已收到你的消息");
}
};
private final static Messenger mReplyMessager = new Messenger(mHandler);复制代码
还有一个比较关键的地方,就是要在客户端发送消息的时候把客户端的Messager经过消息传送到服务器端
(msg.replyTo =mReplyMessager)
private class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Messenger messenger = new Messenger(service);
Message msg = Message.obtain();
//经过msg把客户端的Messager传送到服务器端(关键代码)
msg.replyTo =mReplyMessager;
try {
messenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}复制代码
这样一来,服务器端和客户端就能很好的实现跨进程通讯了.
若是须要传送数据的话,能够经过Bundle设置数据,除了基本数据类型,还能够经过消息传送可序列化的对象
发送方:
Message msg = Message.obtain();
Bundle bundle = new Bundle();
//传输序列化对象
//bundle.putParcelable();
//bundle.putSerializable();
msg.setData(bundle);复制代码
接收方:
Bundle data = msg.getData();
//获取数据
//data.getSerializable()
//data.getParcelable()复制代码
上面咱们已经实现了跨进程通讯,可是这里面实际上是有弊端的,服务端处理客户端的消息是串行的,必须一个一个来处理,因此若是是并发量比较大的时候,经过Messager来通讯就不太适合了
上面演示的是应用内跨进程通讯,绑定服务能够经过显示意图来绑定,可是若是是跨应用的进程间通讯,那么就须要用到隐式意图了.这里有一点须要注意的就是,在5.0之后隐式意图开启或者绑定service要setPackage(Service的包名),否则会报错
mIntent = new Intent();
//设置Package为Service的包名
mIntent.setPackage("com.xiayu.ipcservice");
mIntent.setAction("myMessager");复制代码
上面提到过经过Meaager跨进程不适合并发量大的状况,那么若是并发量大的话,咱们用什么来处理呢?那就能够经过AIDL来进行,这里是Google的描述
Note: Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want
to handle multithreading in your service. If you do not need to perform concurrent IPC across different applications, you
should create your interface by implementing a Binder or, if you want to perform IPC, but do not need to handle
multithreading, implement your interface using a Messenger. Regardless, be sure that you understand Bound Services before
implementing an AIDL.复制代码
主要意思就是你能够用Messager处理简单的跨进程通讯,可是高并发量的要用AIDL
咱们仍是先演示一下同一个应用内的跨进程通讯
首先咱们建立一个Service
public class AIDLService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}复制代码
而后在清单文件里面设置Service的进程
<service android:name=".AIDLService"
android:process=":xiayu"
/>复制代码
而后右键选择新建AIDL文件,Android Studio就会帮你在你的aidl目录的同名文件夹下面建立一个AIDL文件
// IShop.aidl
package com.xiayu.aidldemo;
interface IShop {
//此方法是建立aidl自带的方法,告知你可使用那些数据类型
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}复制代码
在AIDL文件里面会有一个接口,并声明了一个方法,那个方法主要是告诉你AIDL支持哪些数据类型传输,因此咱们把这个方法删掉,咱们再本身声明一个方法,用于以后的调用
(注意:每次修改了AIDI文件后,须要同步一下才会生效,由于每次同步后,Android Studio会在 项目/build/generated/source/aidl/debug 目录下生成相应的java文件)
interface IShop {
//本身声明的方法,用于以后的调用
void sell();
}复制代码
咱们在Service中建立一个Binder,并在onbind的时候返回
public class AIDLService extends Service{
private Binder mBinder = new IShop.Stub() {
@Override
public void sell() throws RemoteException {
System.out.println("客官,您须要点什么?");
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}复制代码
建立自定义一个类实现ServiceConnection
private class XiayuConnection implements ServiceConnection{
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//绑定成功时会调用这个方法
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}复制代码
绑定服务,当绑定成功时会走Connection的onServiceConnected方法,并把Binder传过来
mIntent = new Intent(this, AIDLService.class);
mXiayuConnection = new XiayuConnection();
bindService(mIntent, mXiayuConnection, BIND_AUTO_CREATE);复制代码
在onServiceConnected方法里面经过asInterface获取服务器传过来的对象,并调用服务端的方法
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//获取到服务器传过来的对象
IShop iShop = IShop.Stub.asInterface(service);
try {
iShop.sell();
} catch (RemoteException e) {
e.printStackTrace();
}
}复制代码
如今客户端就能够调用sell方法来进行跨进程通讯了,但目前只能传输基本数据类型的数据,那么若是想要传其余数据呢?那么咱们接着往下讲
首先咱们要知道AIDL支持那么数据类型
那么咱们定义一个对象Product实现Parcelable接口,如何实现Parcelable我这里也不重复介绍了,若是不了解的朋友能够看看笔者以前写的这篇文章
Product咱们设置了两个字段
public class Product implements Parcelable {
public String name;
public int price;
...
}复制代码
接着咱们须要在aidl文件夹的相同目录建立一个相同文件名的aidl文件
(注意,这里咱们是要经过new File的方式建立,而且要本身输入文件后缀aidl,若是你用new AIDL的方式建立的话,他会提示你Interface Name must be unique)
接着咱们须要在这个aidl文件里面输入包名,而且声明一下变量为Parcelable类型
(注意,这里声明的时候是用小写的parcelable)
// Product.aidl
package com.xiayu.aidldemo;
parcelable Product;复制代码
咱们回到以前的IShop.aidl,删掉以前的sell方法,并再建立两个新方法
// IShop.aidl
package com.xiayu.aidldemo;
import com.xiayu.aidldemo.Product;
interface IShop {
Product buy();
void setProduct(in Product product);
}复制代码
这里有三个须要注意的地方
(1)IShop.aidl虽然跟Product.aidl在同一个包下,可是这里仍是须要手动import进来
(2)这里声明方法时,须要在参数前面增长一个tag,这个tag有三种,in,out,inout,这里表示的是这个参数能够支持的流向:
用一张图来总结:
(不要都设为inout,要看需求来设置,由于会增长开销)
(3)默认实现Parcelable的模版只支持in ,若是须要须要支持out或inout须要手动实现readFromParcel方法
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.name);
dest.writeInt(this.price);
}
//手动实现这个方法
public void readFromParcel(Parcel dest) {
//注意,这里的读取顺序要writeToParcel()方法中的写入顺序同样
name = dest.readString();
price = dest.readInt();
}复制代码
如今就能够在客户端中经过IShop调用方法来进行通讯了
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IShop iShop = IShop.Stub.asInterface(service);
try {
//调用方法进行通讯
iShop.setProduct(mProduct);
Product buy = iShop.buy();
} catch (RemoteException e) {
e.printStackTrace();
}
}复制代码
上面咱们介绍了同一个应用内的进程间通讯,接下来咱们就来介绍不一样应用之间的进程间通讯
首先咱们须要把Product.java放到aidl目录相同名字的文件夹下(若是要提供服务给其余app,最好把须要的对象都放在aidl目录下,这样比较容易拷贝)
可是这个时候你运行程序的话,编译会提示说找不到Product,那是由于Android Studio默认会去java目录下找,这时候须要在build.gradle文件 android{ } 中间增长一段代码,让aidl目录里面的java文件也能被识别
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}复制代码
接着咱们为Service增长intent-filter,这样其余应用才能经过隐式意图绑定服务,服务器端的修改就结束了
<service android:name=".AIDLService"
android:process=":xiayu">
<intent-filter> <action android:name="action.xiayu"/> </intent-filter> </service>复制代码
咱们须要建立一个新的应用来做为客户端,而且把服务器端的aidl目录下的全部文件都拷贝过来,这里要注意的就是里面的目录不能改变,须要与之前一致
点击同步,Android Studio会自动生成相应的java文件供咱们使用
这个时候咱们须要经过隐式意图来绑定服务了
(注意:5.0之后隐式意图开启或者绑定service要setPackage,否则会报错)
mIntent.setAction("action.xiayu");
mIntent.setPackage("com.xiayu.aidldemo");复制代码
接下来的操做就和以前同样了,建立一个类实现ServiceConnection
private class XiayuConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//TODO
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}复制代码
绑定服务
bindService(mIntent, mXiayuConnection, BIND_AUTO_CREATE);复制代码
经过ServiceConnection的onServiceConnected里面的IBinder进行通讯
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IShop iShop = IShop.Stub.asInterface(service);
try {
iShop.setProduct(mProduct);
Product buy = iShop.buy();
System.out.println("buy=" + buy.price);
} catch (RemoteException e) {
e.printStackTrace();
}
}复制代码
解除绑定的时候释放资源
public void unbind(View v) {
unbindService(mXiayuConnection);
mXiayuConnection = null;
mIShop = null;
}复制代码
这样咱们就能够经过得到的IShop进行不一样应用之间的进程间通讯了
最后再提几点用到服务时须要注意的地方(很简单,可是有些人常常会忽略这几点)