1、概述
IPC(Inter-Process Communication),含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程。
2、Android中的IPC场景
(1)单应用
一个应用因为某种原因自身需要采用多进程模式来实现,至于原因,比如,有些模块因为特殊原因需要运行在单的的进程中,或者为增大一个应用可使用的内存所以需要通过多进程来获取多份内存空间。Android对单个应用使用的最大内存做了限制,早期版本是16MB,不同设备有不同的大小。
(2)多应用
当前应用需要向其他应用获取数据,由于两个应用,所以必须采取扩进程的方式来获取数据,甚至我们通过系统提供的ContentProvide去查询数据的时候也是一种进程间通信,只不过通信细节被系统内部屏蔽了。
3、开启多进程
在Android中使用多线程只有一种方法,那就是给四大组件(Activity、Service、Receiver、ContentProvider)在AndroidMenifest中指定android:process属性,初次之外没有其他办法,也就是说我们无法给一个线程或者一个实体类指定其运行时所在的进程。其实还有另一种非常规的多进程方法,那就是通过JNI在native层去fork一个新的线程,不常用的方法。
android:process=":remote"
android:process="com.chunsoft.remote"
:的含义是指要在当前的进程名前附加上当前的包名,第二种是完整的命名方式,不会附加包名信息,进程名以“:“开头的进程属于当前应用的私有进程,其他应用的组建不可以和它跑在同一个进程中,而另外一种属于全局进程,其他应用通过ShareUID方式可以和它跑在同一个进程中。
Android系统会为每个应用分配一个唯一的UID,具有相同UID的应用才能共享数据。这里要说明的是,两个应用通过ShareUID跑在同一个进程中是有要求的,需要这两个应用有相同的ShareUID并且签名相同才可以。在这种情况下,它们可以互相访问对方的私有数据,比如data目录、组件信息,还可以共享内存数据,或者他们看起来就是一个应用的两部分。
4、多线程的问题
(1)静态成员和单例模式完全失效
Android为每个应用分配一个独立的虚拟机,或者说为每个进程,不同的虚拟机在内存分配上有不同的地址控件,这就导致在不同的虚拟机中访问同一个对象会产生多个副本。
(2)线程同步机制完全失效
不同进程锁的不是同一个对象
(3)ShareParences的可靠性下降
不支持两个进程同时写
(4)Application会多次创建
由于系统要在创建新的进程同时分配独立的虚拟机,所以这个过程就是启动一个用用的过程。
5、使用Bundle通信
用于android四大组件间的进程间通信。
android的四大组件中的三个主件(Activity、Service、Receiver)都可使用Bundle传递数据(Bundle实现了Parcelable接口),所以如果要实现四大组件间的进程间通信完全可以使用Bundle来实现简单方便。
6、使用文件共享
Android系统基于Linux,使得其并发读、写文件可以没限制地进行,甚至两个线程同时同一文件进行写操作都是允许的,这种方式在单线程读写的时候比较好用,如果有多个线程并发读写的话需要限制线程的同步读写 。
另外 SharePreference是个特例,它底层基于xml实现 ,但是系统对它的读写会基于缓存,也就是说再多进程模式下就变得不那么可靠了,有很大几率丢失数据。
下面展示在一个Activity中序列化对象到文件系统中,从另一个进程中恢复这个对象:
//在MainActivity中序列化
public void persistToFile() {
new Thread(new Runnable() {
@Override
public void run() {
User user = new User(1,"Androids",false);
File dir = Serialization.this.getFilesDir();//this.getCacheDir();
String dirstr = dir.toString();
File file = new File(dirstr, "chunsoftFile.txt");
try {
file.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ObjectOutputStream objectOutputStream = null;
try {
objectOutputStream = new ObjectOutputStream(new FileOutputStream(file));
objectOutputStream.writeObject(user);
Log.e(TAG,"persist user:"+user);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
objectOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}
//在SecondActivity中反序列化对象
private void recoverFromFile() {
new Thread(new Runnable() {
@Override
public void run() {
User user = null;
File file = new File(ReadActivity.this.getFilesDir().toString(), "chunsoftFile.txt");
if (file.exists()) {
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(new FileInputStream(file));
user = (User) objectInputStream.readObject();
Log.e(TAG, "recover user:" + user);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally {
try {
objectInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}).start();
}
7、使用Messenger
Messenger可以翻译为信使,通过它可以在不同进程中传递Message对象,在Message中放入我们需要传递的数据,就可以实现数据间的进程间传递了。Messenger是一种轻量级的IPC方案,它的底层实现是AIDL,它对AIDL做了封装,我们可以简便的进行进程间通信。由于它一次处理一个请求,因此在服务端我们不用考虑线程同步的问题,这是因为服务端不存在并发执行的情形。
实现一个Messenger有如下几个步骤,分为服务端和客户端:
(1)服务端进程
首先我们需要在服务端创建一个Service来处理客户端的连接请求,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的onBind中返回这个Messenger对象底层的Binder即可。
public class MessengerService extends Service {
private static final String TAG = "MessengerService";
private static class MessengerHandler extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.e(TAG,"receive msg from Client:"+msg.getData().get("msg"));
break;
default:
super.handleMessage(msg);
}
}
}
public MessengerService() {
}
//和MessengerHandler关联
private final Messenger mMessenger = new Messenger(new MessengerHandler());
//返回它里面的Binder对象
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
(2)客户端
客户端首先需要绑定远程进程的MessengerService,绑定成功后,根据服务端创建的binder对象创建Messenger对象那个并使用此对象向服务端发送消息,下面的代码在Bundle中间向服务端发送了一句话,在上面的服务端代码中会打印这句话:
public class MessengerActivity extends AppCompatActivity {
private static final String TAG = "MessengerAcitivity";
private Messenger mService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger_acitivty);
Intent intent = new Intent(this,MessengerService.class);
bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = new Messenger(service);
Message msg = Message.obtain(null,1);
Bundle data = new Bundle();
data.putString("msg","Hello,this is client");
msg.setData(data);
//Log.e(TAG,"receive msg from Client:"+msg.getData().get("msg"));
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onDestroy() {
unbindService(mConnection);
super.onDestroy();
}
}
在Messenger中进行传递数据必须将数据放入Message中,而Message和Messenger都实现了Parcelable接口,因此可以跨进程传输。
上面是客户端给服务端发送信息,下面实现客户端给服务端发送信息,服务端返回信息:
(1)服务端
服务端主要修改MessengerHandler,通过Messenger回复信息给客户端
private static class MessengerHandler extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.e(TAG,"receive msg from Client:"+msg.getData().get("msg"));
Messenger client = msg.replyTo;
Message replyMessage = Message.obtain(null,0);
Bundle bundle = new Bundle();
bundle.putString("reply","已收到你的消息");
replyMessage.setData(bundle);
try {
client.send(replyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
(2)客户端
为了接收服务端的回复,客户端也需要准备一个接收消息的Messenger和Handler:
private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());
private static class MessengerHandler extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
Log.e("TAG","接收消息来自服务端:" + msg.getData().getString("reply"));
break;
default:
super.handleMessage(msg);
}
}
}
另外,当客户端发送消息的时候,需要把接收服务端回复的Messenger通过Message的replyTo参数传递给服务端。
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = new Messenger(service);
Message msg = Message.obtain(null,1);
Bundle data = new Bundle();
data.putString("msg","Hello,this is client");
msg.setData(data);
//注意这句话
msg.replyTo = mGetReplyMessenger;
//Log.e(TAG,"receive msg from Client:"+msg.getData().get("msg"));
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
总结:客户端给服务端发送消息的时候所用的Messenger是通过绑定服务端,然后依据onBind返回的Binder对象为参数来创建Messenger,而服务端在回应客户端的时候所用的Messenger是客户端在刚刚发送消息的时候将自身创建的Messenger作为刚刚发送消息的Message的replyTo参数传递给服务端的,所以在服务端直接读取出这个Messenger。
8、使用AIDL
Messenger是以串行的方式处理客户端发来的消息,如果有大量的消息同时发送到服务端,服务端智能一个个处理,如果有大量的并发请求,那么Messenger酒不合适了。Messenger只能传递消息不能跨进程调用方法。
AIDL可以跨进程调用方法,它是Messenger的底层实现。
(1)服务端
服务端首先创建一个Service用来监听客户端的连接请求,然后创建一个文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个接口。
(2)客户端
客户端首先需要绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转化成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。
(3)AIDL接口的创建
import com.chunsoft.testipc.Book;
interface IBookManager {
List <Book> getBookList();
void addBook(in Book book);
}
AIDL中除了基本数据类型,其他类型的参数必须表上方向:
in:表示输入型参数
out:表示输出型参数
inout:表示输入输出型参数
AIDL接口中支持方法,不支持声明静态变量,这一点区别传统接口。
(4)远程服务端Service实现
首先在onCreate中初始化添加两本书,然后创建一个Binder对象那个并在OnBinder中返回它,这个对象继承至iBookManager,并实现了它内部的AIDL方法。
public class MessengerService extends Service {
private static final String TAG = "MessengerService";
private static class MessengerHandler extends Handler{
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.e(TAG,"receive msg from Client:"+msg.getData().get("msg"));
Messenger client = msg.replyTo;
Message replyMessage = Message.obtain(null,0);
Bundle bundle = new Bundle();
bundle.putString("reply","已收到你的消息");
replyMessage.setData(bundle);
try {
client.send(replyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
public MessengerService() {
}
//和MessengerHandler关联
private final Messenger mMessenger = new Messenger(new MessengerHandler());
//返回它里面的Binder对象
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
(5)客户端的实现
客户端首先要绑定远程服务,绑定成功后将服务端返回的Binder对象转换成AIDL接口,然后就可以通过这个接口去调用服务端的远程方法了。
//客户端实现
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, BookManagerService.class);
bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
try {
List<Book> list = bookManager.getBookList();
Log.i(TAG,"query book list:" + list.getClass().getCanonicalName());
Log.i(TAG,"query book list:" + list.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onDestroy() {
unbindService(mConnection);
super.onDestroy();
}
}
上面就简单的实现AIDL的通信,AIDL还可以通过观察者模式实现注册、监听。
9、使用ContentProvider
ConetentProvider是Android中提供专门用于不同应用间进行数据共享的方式,从这一点来看,它天生就适合进程间通信,和Messenger一样,ContentProVider的底层同样也是Binder。
系统预置了许多ContentProvider,比如通讯录信息、日程表信息。要跨进程访问这些消息,只需要通过ContentResolver的query、update、insert和delete方法即可。
创建一个自定义ContentProvider,继承ContentProvider类并实现六个抽象方法:
(1)onCreate
代表ContentProvider的创建,一般来说我们需要做一些初始化工作;
(2)getType
用来返回一个Uri请求所对应的MIME类型(媒体类型),比如图片,视频等。如果不关注这个选项设置为null,或者“/”
(3)query、update、insert和delete
实现对数据表的增删查改功能。
10、使用socket
Socket实现进程间的通信。Socket也称为“套接字”,是网络通信中的概念,它分为流式套接字和用户数据包套接字两种分别对应于网络的传输控制层中的TCP和UDP协议。Socket本身可以支持传输任意字节流。
1)首先在远程Service建立一个TCP服务,然后在主界面中连接TCP服务,连接上了以后,就可以给服务端发消息,然后服务端随机的回应我们一条信息。我们的服务端可以和多个客户建立连接并响应。
(2)服务端。当Service启动时,会在线程中建立TCP服务,这里监听的是8688端口,然后就可以等待客户端的连接请求。当有客户端连接时,就会生成一个新的Socket,通过每次新创建的Socket就可以分别和不同的客户端通信了。服务端每收到一次客户端的消息就会随机回复一句话给客户端。当客户端断开连接时,服务端这边也会相应的关闭对应的Socket并结束通话线程。
本文主要介绍了Android进程间通信的六种方式,每种方式有对应的适用场景。
下面总结下:
名称 | 优点 | 缺点 | 适用场景 |
Bundle | 简单易用 | 只能传输Bundle支持的数据类型 | 四大组件间的进程间通信 |
文件共享 | 简单易用 | 不适合并发场景,并且无法做到进程间即时通信 | 无并发访问情形,交换简单的数据实时性不高的场景 |
AIDL | 功能强大,支持一对多并发通信,支持实时通信 | 使用稍复杂,需要处理好线程同步 | 一对多通信且有RPC要求 |
Messenger | 功能一般,支持一对多串行通信 | 不能很好处理高并发情形,不支持RPC,数据通过Message进行传输,因此只能传输Bundle支持的数据类型 | 低并发的一对多即时通信,无RPC需求,或者无需返回结果的RPC需求 |
ContentProvider | 在数据源访问方面功能强大,支持一对多并发数据共享,可通过Call方法扩展其他操作 | 可以理解为受约束的AIDL,主要提供数据源的CRUD操作 | 一对多的进程间数据共享 |
Socket | 功能强大,可以通过网络传输字节流,支持一对多并发实时通信 | 实现细节稍微有点繁琐,不能支持直接的RPC | 网络数据交换 |
RPC(Remote Procedure call Protocol):远程过程调用协议