Android IPC简介
IPC是Inter-Process Communication的缩写,含义就是进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程。
在明确其之前,需要先搞懂几个概念:
线程:CPU可调度的最小单位,是程序执行流的最小单元;线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
进程: 一个执行单元,在PC 和移动设备上一般指一个程序或者应用,一个进程可以包含多个线程。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。
在Android中,为每一个应用程序都分配了一个独立的虚拟机,或者说每个进程都分配一个独立的虚拟机,不同虚拟机在内存分配上都有不同的地址空间,这就导致在不同的虚拟机互相访问数据需要借助其他手段。
下面分别介绍一下在Android中实现IPC的方式:
使用Bundle的方式
我们知道在Android中三大组件(Activity,Service,Receiver)都支持在Intent中传递Bundle数据,由于Bundle实现了Parceable接口,所以它可以很方便的在不同的进程之间进行传输。当我们在一个进程中启动另外一个进程的Activity,Service,Receiver时,我们就可以在Bundle中附加我们所需要传输给远程的进程的信息,并且通过Intent发送出去。这里注意:我们传输的数据必须基本数据类型或者能够被序列化。
1.基本数据类型(int, long, char, boolean, double等)
2.String和CharSequence
3.List:只支持ArrayList,并且里面的元素都能被AIDL支持
4.Map:只支持HashMap,里面的每个元素能被AIDL支持
5.Parcelable:Parcelable的实现类
下面看一个Demo例子:利用Bundle进行进程间通信
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
Bundle bundle = new Bundle();
bundle.putString("data", "测试数据");
intent.putExtras(bundle);
startActivity(intent);
注意:利用Bundle进行进程间通信是很容易的,大家应该注意到,这种方式进行进程间通信只能是单方向的简单数据传输,它使用时有一定的局限性。
使用文件共享的方式
文件共享: 将对象序列化之后保存到文件中,在通过反序列,将对象从文件中读取。
在A进程中写入对象(序列化):
new Thread(new Runnable() {
@Override
public void run() {
User user = new User(1, "user", false);
File cachedFile = new File(CACHE_FILE_PATH);
ObjectOutputStream objectOutputStream = null;
try{
objectOutputStream = new ObjectOutputStream
(new FileOutputStream(cachedFile));
objectOutputStream.writeObject(user);
}catch(IOException e){
e.printStackTrace();
}finally{
objectOutputStream.close();
}
}
}).start();
在B进程中读取文件(反序列化):
new Thread(new Runnable() {
@Override
public void run() {
User user = null;
File cachedFile = new File(CACHE_FILE_PATH);
if(cachedFile.exists()){
ObjectInputStream objectInputStream = null;
try{
objectInputStream = new ObjectInputStream
(new FileInputStream(cachedFile));
user = objectInputStream.readObject(user);
}catch(IOException e){
e.printStackTrace();
}finally{
objectInputStream.close();
}
}
try{
objectOutputStream = new ObjectOutputStream
(new FileOutputStream(cachedFile));
objectOutputStream.writeObject(user);
}catch(IOException e){
e.printStackTrace();
}finally{
objectOutputStream.close();
}
}
}).start();
这样,分属不同的进程成功的获取到了共享的数据。
通过共享文件这种方式来共享数据对文件的格式是没有具体的要求的。比如可以是文件,也可以是Xml、JSON 等。只要读写双方约定一定的格式即可。
注意:同文件共享方式也存在着很大的局限性。即并发读/ 写的问题。读/写会造成数据不是最新。读写很明显会出现错误。文件共享适合在对数据同步要求不高的进程之间进行通信。并且要妥善处理并发读写的问题。
使用Messenger的方式
Messenger 可以翻译为信使,通过该对象,可以在不同的进程中传递Message对象。注意,两个单词不同。
下面就通过服务端(Service)和客户端(Activity)的方式进行演示。
客户端向服务端发送消息,可分为以下几步。
服务端
1.创建Service
2.构造Handler对象,实现handlerMessage方法。
3.通过Handler对象构造Messenger信使对象。
4.通过Service的onBind()返回信使中的Binder对象。
客户端
1.创建Actvity
2.绑定服务
3.创建ServiceConnection,监听绑定服务的回调。
4.通过onServiceConnected()方法的参数,构造客户端Messenger对象
5.通过Messenger向服务端发送消息。
实现服务端
public class MessengerService extends Service{
private Handler MessengerHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
//消息处理.......
Log.i("messenger","接收到客户端的消息--"+msgClient);
};
//创建服务端Messenger
private final Messenger mMessenger = new Messenger(MessengerHandler);
@Override
public IBinder onBind(Intent intent) {
//向客户端返回Ibinder对象,客户端利用该对象访问服务端
return mMessenger.getBinder();
}
@Override
public void onCreate() {
super.onCreate();
}
}
注意:MessengerService需要在AndroidManifest.xml中注册。
实现客户端
public class MessengerActivity extends Activity{
private ServiceConnection conn = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//根据得到的IBinder对象创建Messenger
mService = new Messenger(service);
//通过得到的mService 可以进行通信
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger);
init();
}
private void init() {
intent = new Intent(MessengerActivity.this, MessengerService.class);
bindService(intent, conn, Context.BIND_AUTO_CREATE);
}
/**
* 布局文件中添加了一个按钮,点击该按钮的处理方法
* @param view
*/
public void send(View view) {
try {
// 向服务端发送消息
Message message = Message.obtain();
Bundle data = new Bundle();
data.putString("msg", "lalala");
message.setData(data);
// 发送消息
mService.send(message);
Log.i("messenger","向服务端发送了消息");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void onDestroy(){
unbindService(conn);
super.onDestroy();
}
}
其中有一点需要注意:
我们是通过Message作为媒介去携带数据的。但是,Message的obj 并没有实现序列化(实现Serializable或Parcelable),也就是其不能保存数据。必须使用message.setData()方法去传入一个Bundle对象,Bundle中保存需要传入的数据。
使用AIDL的方式
AIDL是一种IDL语言,用于生成可以在Android设备上两个进程之间进行进程间通信(IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数。
AIDL是IPC的一个轻量级实现,用了对于Java开发者来说很熟悉的语法。Android也提供了一个工具,可以自动创建Stub(类架构,类骨架)。当我们需要在应用间通信时,我们需要按以下几步走:
1.定义一个AIDL接口。
2.为远程服务(Service)实现对应Stub。
3.将服务“暴露”给客户程序使用。
下面简单介绍一下使用AIDL的使用方法:
因为需要服务端和客户端共用aidl文件,所以最好单独建一个包,适合拷贝到客户端。
服务端:
添加如下包名:com.example.ipc.aidl
创建BookAidl.java,该对象需要作为传输。所以需要实现Parcelable。
public class BookAidl implements Parcelable {
public int bookId;
public String bookName;
public BookAidl() {
super();
}
public BookAidl(int bookId, String bookName) {
super();
this.bookId = bookId;
this.bookName = bookName;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
public static final Parcelable.Creator<BookAidl> CREATOR = new Creator<BookAidl>() {
@Override
public BookAidl[] newArray(int size) {
return new BookAidl[size];
}
@Override
public BookAidl createFromParcel(Parcel source) {
BookAidl book = new BookAidl();
book.bookId = source.readInt();
book.bookName = source.readString();
return book;
}
};
@Override
public String toString() {
return "BookAidl [bookId=" + bookId + ", bookName=" + bookName + "]";
}
}
创建BookAidl.aidl文件,并手动添加。
package com.example.ipc.aidl;
Parcelable BookAidl;
创建IBookManager.aidl文件,接口文件,面向客户端调用:
package com.example.ipc.aidl;
import com.example.ipc.aidl.BookAidl;
interface IBookManager{
List<BookAidl> getBookList();
void addBook(in BookAidl book);
}
写完之后clean一下工程,之后会在gen目录下生成对应的java文件。
继续编写服务端,创建Service类。
public class BookService extends Service {
/**
* 支持线程同步,因为其存在多个客户端同时连接的情况
*/
private CopyOnWriteArrayList<BookAidl> list = new CopyOnWriteArrayList<>();
/**
* 构造 aidl中声明的接口的Stub对象,并实现所声明的方法
*/
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<BookAidl> getBookList() throws RemoteException {
return list;
}
@Override
public void addBook(BookAidl book) throws RemoteException {
list.add(book);
Log.i("aidl", "服务端添加了一本书"+book.toString());
}
};
@Override
public void onCreate() {
super.onCreate();
//加点书
list.add(new BookAidl(1, "java"));
list.add(new BookAidl(2, "android"));
}
@Override
public IBinder onBind(Intent intent) {
// 返回给客户端的Binder对象
return mBinder;
}
}
在Service中,主要干了两件事情:
1.实现aidl文件中的接口的Stub对象。并实现方法。
2.将Binder对象通过onBinder返回给客户端。
为了省事,在这里不在另起一个工程了,直接将Service在另一个进程中运行。
<service
android:name="com.example.ipc.BookService"
android:process=":remote" />
客户端
因为在同一个工程中,不需要拷贝aidl包中的文件。如果不在同一个工程,需要拷贝。
public class BookActivity extends AppCompatActivity{
/**
* 接口对象
*/
private IBookManager mService;
/**
* 绑定服务的回调
*/
private ServiceConnection conn = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 获取到书籍管理的对象
mService = IBookManager.Stub.asInterface(service);
Log.i("aidl", "连接到服务端,获取IBookManager的对象");
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book);
// 启动服务
Intent intent = new Intent(this,BookService.class);
bindService(intent, conn, BIND_AUTO_CREATE);
}
/**
* 获取服务端书籍列表
* @param view
*/
public void getBookList(View view){
try {
Log.i("aidl","客户端查询书籍"+mService.getBookList().toString());
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 添加书籍
*/
public void add(View view){
try {
// 调用服务端添加书籍
mService.addBook(new BookAidl(3,"iOS"));
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
客户端的代码和之前的Messenger很类似:
1.绑定服务,监听回调。
2.将回调中的IBinder service通过IBookManager.Stub.asInterface()转化为借口对象。
3.调用借口对象的方法。
使用ContentProvider的方式
ContentProvider是Android中的四大组件之一,为了在应用程序之间进行数据交换,Android提供了ContentProvider,ContentProvider是不同应用之间进行数据交换的API,一旦某个应用程序通过ContentProvider暴露了自己的数据操作的接口,那么不管该应用程序是否启动,其他的应用程序都可以通过接口来操作接口内的数据,包括数据的增、删、改、查等操作。
开发一个ContentProvider的步骤很简单:
1.定义自己的ContentProvider类,该类集成ContentProvider基类;
2.在AndroidMainfest.xml中注册这个ContentProvider,类似于Activity注册,注册时要给ContentProvider绑定一个域名;
3.当我们注册好这个ContentProvider后,其他应用就可以访问ContentProvider暴露出来的数据了。
ContentProvider只是暴露出来可供其他应用操作的数据,其他应用则需要通过ContentProvider来操作
使用ContentResolver操作数据的步骤也很简单:
1.调用Activity的getContentResolver()获取ContentResolver对象;
2.根据调用的ContentResolver的insert()、delete()、update()和query()方法操作数据库即可。
使用Socket的方式
Socaket也是实现进程间通信的一种方式,Socaket也称为“套接字”,网络通信中的概念,通过Socket我们可以很方便的进行网络通信,都可以实现网络通信录,那么实现跨进程通信不是也是相同的嘛,但是Socaket主要还是应用在网络通信中。
Android 进程间通信不同方式的比较
- Bundle:四大组件间的进程间通信方式,简单易用,但传输的数据类型受限。
- 文件共享: 不适合高并发场景,并且无法做到进程间的及时通信。
- Messenger: 数据通过Message传输,只能传输Bundle支持的类型
- ContentProvider:android 系统提供的。简单易用。但使用受限,只能根据特定规则访问数据。
- AIDL:功能强大,支持实时通信,但使用稍微复杂。
- Socket:网络数据交换的常用方式。不推荐使用。