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
相关文章推荐
- Android知识库
- android学习四(Activity的生命周期)
- Android learn web site
- Android使用ViewPager实现无限循环滑动及轮播(附源码)
- Android使用ViewPager实现无限循环滑动及轮播(附源码)
- [置顶] ubuntu下Qt for Android的安装及环境配置
- Android 在代码中动态设置字体颜色需要注意的问题
- 解决Android设置软键盘搜索键以及监听搜索键点击时发生两次事件的问题
- 日常开发——Android多线程下载
- Android Handler 异步消息处理机制的妙用 创建强大的图片加载类
- Android 四大组件之Activity 基础总结(1)
- 使用JME3开发的Android 3D游戏 - 落樱之剑v2.2
- Android:ScrollView中嵌套ViewPager和ListView示例
- Android框架模式之-MVP简单使用
- Android Wear开发 - 学习指南
- android 按钮点击效果实现 在studio下出现的错误
- android 关于Toast重复显示解决方法
- Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系
- android弹窗dialog和AlertDialog
- Android使用多线程实现断点下载