您的位置:首页 > 移动开发 > Android开发

Android进程间通信(IPC)常用方式

2016-03-30 17:31 351 查看

进程间通信方式

Android
开发中我们可以通过
Intent
ContentProviders
来实现进程间通信,如果不限于
Android
特有的话,我们还可以使用
File
Socket
等方式,反正只要进程间能交换信息就行了。

Intent
,我们平时使用的时候好像都没感觉出是在进程间通信。其实
Android
中进程间的通信是非常频繁的,应用里打开一个新的
Activity
都涉及到了进程间通信,应用里调用打电话、调用浏览器等等都涉及到了。

实际上
Intent
ContentProviders
都是对
Binder
更高级别的抽象,方便我们平时使用。

常用方式

上面说到的一些方式都是系统经过高度封装的,而我们的业务需求可能比较特别,使用上面的方式可能不是特别适合,比如:“我们的音乐播放器希望在独立的进程中播放音乐”。

我们至少得控制音乐的开始、暂停、显示进程这些功能吧,那就需要进程间的通信了。这个时候使用系统经过高度封装的方式都好像显得不太灵活。根据官方文档我们发现有两种相对底层一些的方式,
Messenger
AIDL


在相对底层一点的进程间通信,
Messenger
是最简单的方式,
Messenger
会在单一线程中创建包含所有请求的队列,这样我们就不需要处理线程安全方面的事宜。

Messenger
实际上是以
AIDL
作为其底层结构的。

Messenger的用法

单向通信

客户端进程相关代码:

public class MainActivity extends AppCompatActivity {

private Messenger mService = null;

// 绑定远程服务成功后相应回调方法
private ServiceConnection mServiceConnection = new ServiceConnection() {

// 绑定成功后会调用该方法
@Override
public void onServiceConnected(ComponentName name, IBinder service) {

mService = new Messenger(service);
}

@Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Intent intent = new Intent(this, RemoteService.class);
// 绑定服务
bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
}

/**
* 点击按钮向远程服务发送消息
* @param view
*/
public void onClick(View view) {
// 获取一个what值为0的消息对象
Message msg = Message.obtain(null, 0);
try {
// 将消息对象通过Messenger传递到远程服务器
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}

@Override
protected void onStop() {
super.onStop();

unbindService(mServiceConnection);
}
}


布局XML代码就不贴了,很简单,就一个按钮。

上面的代码也很简单,就得我们平常绑定服务的做法是一样的,唯一的区别就是在绑定成功回调方法
onServiceConnected()
中我们根据返回的
IBinder
实例化了一个
Messenger
对象,当我们点击按钮的时候,通过该
Messenger
对象发送一个消息到远程服务端。

服务端进程代码:

/**
* 远程服务端
*/
public class RemoteService extends Service {

// 用来处理客户端传过来的消息
class ServerSideHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
Log.e("Fiend", "我是远程服务端,我收到客户端传递过来的信息了。");
break;
}
}
}

// 实例化一个Messenger对象,并传入Handler
final Messenger mMessenger = new Messenger(new ServerSideHandler());

/**
* 客户端绑定服务端的时候将调用该方法
* @param intent
* @return
*/
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}

}


服务端的代码也很简单,创建一个
Handler
来处理消息,实例化一个
Messenger
与该
Handler
关联,最后通过
onBind()
方法将
Messenger
IBinder
给返回。在客户端通过该
IBinder
重建一个
Messenger


我们来看一下运行结果:



确实成功了,而且也确实是在两个进程间。想要让服务运行在别的进程只需要声明的时候指定它的
android:process
属性就可以了。

但是我们只是客户端向服务端发送了信息,那服务端如何向客户端发送信息呢?

双向通信

客户端改动地方:

/**
* 点击按钮向远程服务发送消息
* @param view
*/
public void onClick(View view) {
// 获取一个what值为0的消息对象
Message msg = Message.obtain(null, 0);

// 将客户端的Messenger对象放到消息中传递到服务端
msg.replyTo = new Messenger(new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.e("Fiend", "我是客户端,收到服务端的回复了");
break;
}
}
});

try {
// 将消息对象通过Messenger传递到远程服务器
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}


客户端代码只需要在发送消息之前将本地的一个
Messenger
对象放到消息里一起传递到远程服务端即可。

服务端改动地方:

// 用来处理客户端传过来的消息
class ServerSideHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
Log.e("Fiend", "我是远程服务端,我收到客户端传递过来的信息了。");
try {
// 通过客户端的Messenger回复一个what值为1的消息
msg.replyTo.send(Message.obtain(null, 1));
} catch (RemoteException e) {
e.printStackTrace();
}
break;
}
}
}


服务端改动的代码也非常简单,只是在收到客户端消息的时候, 通过客户端的
Messenger
回复一个消息。这样就实现了本地客户端与远程服务端的通信了。

对于大多数的应用来说,
Messenger
就能满足IPC的需求了,完全没必要使用
AIDL
,而且
Messenger
AIDL
简单得多。如果对于服务需要执行多线程处理的,则应使用
AIDL
,否则使用
Messenger
就可以了。

AIDL的用法

使用
AIDL
和使用
Messenger
的步骤基本上是类似的。使用
AIDL
需要自己定义好一个接口作为客户端和服务端通信的规则,手工写一个这样的接口比较复杂,所以
Android
给我们提供了一个工具来自动生成。

想要自动生成通信的接口,则需要创建一个以
.aidl
结尾的文件,然后按平常我们定义接口的方式做就好了。下面以
Android Studio
来讲解生成过程。

新建一个项目,名字随便取:AIDLExample

将工程目录结构以
Android
的形式展示:



点击项目,右键,新建一个
AIDL
文件:



打开新建的AIDL文件
IMyAidlInterface.aidl
,编写通信规则:



编写完
IMyAidlInterface.aidl
后,需要重新
Build
一下项目,然将工程目录结构以
Project
的形式展示,就可以找到生成的真正接口:



至此
AIDL
接口就定义好了,剩下的步骤比较简单,和之前讲过的类似。

我们先来编写服务端,直接新建一个
Service
并在配置文件中将其配置为
android:process=":remote"
,确保它运行在另一个进程中。

// 服务端
public class MyService extends Service {

IMyAidlInterface.Stub iBinder = new IMyAidlInterface.Stub() {
// 我们在aidl文件中定义的通信规则
@Override
public String getMsg() throws RemoteException {
return "我来自MyService";
}
};

@Override
public IBinder onBind(Intent intent) {
return iBinder;
}
}


代码很简单,在绑定的时候将带有我们自己定义的规则的
IBinder
返回给客户端。
XXX.Stub iBinder = new XXX.Stub() {···}
这样的写法是固定的,记住就好了,将
XXX
替换成你的
AIDL
接口名称就可以了。

我们来看一下客户端代码:

// 客户端
public class MainActivity extends AppCompatActivity {

private IMyAidlInterface mService;
private boolean isBound;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// 绑定服务端
Intent intent = new Intent(this, MyService.class);
bindService(intent, mServiceConnection, BIND_AUTO_CREATE);

}

// 绑定回调
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 获取AIDL接口对象,这样就可以用来通信了
mService = IMyAidlInterface.Stub.asInterface(service);
isBound = true;
}

@Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
isBound = false;
}
};

// 按钮点击回调方法
public void btnClick(View view) {
if (isBound) {
try {
// 调用服务端方法
String result = mService.getMsg();
Log.e("Fiend", "客户端:" + result);
} catch (RemoteException e) {
e.printStackTrace();
}
} else {
Log.e("Fiend", "还没有绑定成功");
}
}

@Override
protected void onDestroy() {
super.onDestroy();

if (isBound) {
unbindService(mServiceConnection);
}
}
}


看起来代码有点多,其实并没有什么陌生的内容,都是我们平时非常熟悉的一些代码。应用启动后就绑定远程服务端,点击按钮调用远程服务端的方法,获取到后将结果打印出来。结果如下:



成功调用另一个进程中的方法。

onServiceConnected()
方法里的这句代码
mService = IMyAidlInterface.Stub.asInterface(service);
,属于固定写法,和之前的服务端写法一样,记住就好了。

上面这种IPC方式是属于同步的,所谓同步是指,客户端调用后会等待服务端返回后才会继续向下执行。我们来修改一下客户端代码:

public void btnClick(View view) {
if (isBound) {
try {
Log.e("Fiend", "开始调用服务端方法");
// 调用服务端方法
String result = mService.getMsg();
Log.e("Fiend", "客户端:" + result);
} catch (RemoteException e) {
e.printStackTrace();
}
} else {
Log.e("Fiend", "还没有绑定成功");
}
}


没有改什么实质性的,只是在调用服务端方法之前打印了一个
Log
,方便我们之前对比时间用。

改一下服务端的代码:

IMyAidlInterface.Stub iBinder = new IMyAidlInterface.Stub() {
// 我们在aidl文件中定义的通信规则
@Override
public String getMsg() throws RemoteException {
// 5秒后再返回结果
SystemClock.sleep(5 * 1000);
return "我来自MyService";
}
};


同样没有修改多少,只是延迟5秒再返回结果。我们来看一下打印结果:



从截图可以看出,客户端确实等服务端返回后再继续执行的,所以是同步。因此,时刻记住客户端调用的时候在
工作线程
调用,否则有可能阻塞主线程。那想要异步该如何做?

异步调用

想要以
AIDL
方式异步调用,需要用到关键字
oneway
,它可以作用在接口上也可以作用在方法上。异步方法必须返回
void


异步接口

// 所有方法都是异步的
oneway interface IAsynchronousInterface {
void method1();
void method2();
}


异步方法

interface IAsynchronousInterface {
// 这个方法是异步执行的
oneway void method1();
void method2();
}


异步已经可以了,那结果如何返回呢?通常异步都是以回调接口的方式,在这里也是一样的。我们修改上面的之前演示的示例,增加一个回调接口,方便服务端调用客户端的方法,也就是所谓的反向调用。

增加一个回调
AIDL
接口定义:



增加回调接口必须重新建立一个
.aidl
结尾的文件,
IMyAidlInterfaceCallback.aidl
具体内容如下:

// 用于服务端回调
interface IMyAidlInterfaceCallback {

// 结果处理
void handleResult(String result);
}


修改
IMyAidlInterface.aidl
的内容:

import com.fiend.aidlexample.IMyAidlInterfaceCallback;

// 和我们平常定义一个接口语法一样
oneway interface IMyAidlInterface {

// 定义了一个方法(所谓的通信规则)
void getMsg(IMyAidlInterfaceCallback callback);
}


getMsg()
方法的返回改为
void
,并将新定义的回调接口作为参数。这里必须显示
import
接口,否则编译会报错。

修改服务端部分代码:

IMyAidlInterface.Stub iBinder = new IMyAidlInterface.Stub() {
@Override
public void getMsg(IMyAidlInterfaceCallback callback) throws RemoteException {
// 5秒后再返回结果
SystemClock.sleep(5 * 1000);
// 通过回调接口返回结果
callback.handleResult("我是异步返回,我来自MyService");
}
//        // 我们在aidl文件中定义的通信规则
//        @Override
//        public String getMsg() throws RemoteException {
//            // 5秒后再返回结果
//            SystemClock.sleep(5 * 1000);
//            return "我来自MyService";
//        }
};


注释掉的部分是我们之前的做法,现在是通过回调接口返回结果。

修改客户端部分代码:

/**
* 回调接口
*/
private IMyAidlInterfaceCallback.Stub mCallback = new IMyAidlInterfaceCallback.Stub() {
@Override
public void handleResult(String result) throws RemoteException {
Log.e("Fiend", "客户端:" + result);
}
};

public void btnClick(View view) {
if (isBound) {
try {
Log.e("Fiend", "开始调用服务端方法");
//                String result = mService.getMsg();
//                Log.e("Fiend", "客户端:" + result);
// 调用服务端方法,将回调接口传过去
mService.getMsg(mCallback);
Log.e("Fiend", "结束调用服务端方法");
} catch (RemoteException e) {
e.printStackTrace();
}
} else {
Log.e("Fiend", "还没有绑定成功");
}
}


增加了一个回调接口,修改了调用服务端方法,之前是调用
getMsg()
并返回结果,现在是调用
getMsg(mCallback)
把回调方法传过去,没有返回值。返回结果在回调接口中处理。

我们来看一下运行结果:



通过返回时间对比,可以看到,调用完远程服务方法就立刻返回了。而需要返回的数据是在
5
秒后通过回调接口返回的。

至此,我们就实现了
AIDL
方式的异步调用了。

AIDL支持的数据类型

AIDL
默认支持这么几种数据类型:

Java
基本数据类型,如
int
long
boolean
等(除了
short
)

String
类型

CharSequence


List
类型,所有
List
中的元素必须是
AIDL
支持的类型,如
List<String>


Map
类型,所有
Map
中的元素必须是
AIDL
支持的类型,如
Map<String, Integer>


List
Map
的接收方类型必须为
ArrayList
HashMap


如果默认的类型不能满足你的需要,还可以自定义类型,自定义类型必须支持序列化,也就是实现
Parcelable
接口。具体可以参考官网

以上我们介绍的
AIDL
用法都是在同一个工程里,只是将
Service
指定运行在了不同的进程中,因此我们的
.aidl
文件可以只写一份,但是,如果我们的
Service
是在另一个应用(
apk
)中,那么另一个应用中也必须有和我们项目中相同的
.aidl
文件,连包名也必须一样。

总结

Android
中实现进程间通信在高层次抽象可以很方便的使用
Intent
等方式来操作,相对底层的方式我们可以使用
Messenger
AIDL
,大多数情况下我们使用
Messenger
就可以达到我们想要的效果了,而且使用也比
AIDL
简单,所以尽量用
Messenger
,实在不行再考虑
AIDL
,在介绍
AIDL
的时候对于支持的数据类型并没有深入的讲解与演示,可以上官网看看。

参考文献

官方文档

Android Binder by Thorsten Schreiber from Ruhr-Universität Bochum

Deep Dive into Android IPC/Binder Framework at Android Builders Summit 2013

Efficient Android Threading by Anders Goransson
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: