Android进程间通信

来源:互联网 发布:詹妮弗劳伦斯 知乎 编辑:程序博客网 时间:2024/06/03 18:35

今日科技快讯

昨日,世预赛亚洲区12强赛,国足坐镇长沙主场迎战韩国队。第34分钟,于大宝为国足打破僵局,下半场,门将曾诚连续做出神扑,帮助国足将1-0的比分守到终场。这是12强赛以来,国足取得的首场胜利,将晋级的希望继续保留了下去。

作者简介

明天就是周末了,这里提前祝大家周末愉快!

本篇来自 夏雨 的投稿,详细地介绍了跨进程通信的两种方案,希望能够帮助到大家。

夏雨 的博客地址:

http://blog.csdn.net/yulyu

前言

进程间通信(Inter-Process Communication),简称 IPC,就是指进程与进程之间进行通信.一般来说,一个app只有一个进程,但是可能会有多个线程,所以我们用得比较多的是多线程通信,比如 Handler,AsyncTask.

但是在一些特殊的情况下,我们app会需要多个进程,或者是我们在远程服务调用时,就需要跨进程通信了

设置多进程

Android设置多进程的步骤很简单,只用在清单文件中为四大组件加上 process 属性:

( :messager 最终的进程名会变成 包名+:messager)

虽然多进程设置起来很简单,但是使用的时候却会有一系列的问题:

两个进程对应的是不同的内存区域

1. Application对象会创建多次

2. 静态成员不共用

3. 同步锁失效

4. 单例模式失效

5. 数据传递的对象必须可序列化

可序列化

进程间通信传递的对象是有严格要求的,除了基本数据类型,其他对象要想可以传递,必须可序列化,Android实现可序列化一般是通过实现 Serializable 或者是 Parcelable

如果你在进程通信中不需要传非基本数据类型的对象,那么你可以不了解序列化,但是可序列化是进程间通信的基础,所以还是建议不了解的朋友先熟悉一下

笔者之前介绍过序列化的相关知识,这里就不重复介绍了:

序列化–Serializable与Parcelable

http://blog.csdn.net/yulyu/article/details/56481665

通信

跨进程通信的方法有很多,比如通过 Intent传递,通过 AIDL 以及 Messager通信,通过 socket通信,这里主要介绍的是基于 Binder 的 AIDL 和 Messager。

Intent

Intent 进行数据的传递是我们平时最常用的,他的原理其实是对于 Binder 的封装,但是他只能做到单向的数据传递,所以并不能很好的实现跨进程通信,我们这里就不展开来介绍了

Messenger

Messenger 的底层也是基于 Binder 的,其实应该说他是在 AIDL 的基础上封装了一层.

一般来说安卓中使用 Binder 主要是通过绑定服务(bindService),服务端(这里指的不是后台,是指其中一个进程)主要是运行 Service,客户端通过 bindService 获取到相关的 Binder,Binder 就作为桥梁进行跨进程的通信.

这里我们先演示同一个应用内的多进程通信

  • 服务器端

首先我们先创建一个Service:

并在清单文件中配置他的进程:

在 Service 里面创建一个 Hander 用来接受消息:

在 Service 里面创建一个 Messenger,并把 Handler 放入其中:

private final static Messenger mMessenger = new Messenger(mHandler);

重写 onBind 方法,返回 Messenger 里面的 Binder:

public IBinder onBind(Intent intent) {
   return mMessenger.getBinder();}
  • 客户端

创建一个对象实现 ServiceConnection:

绑定服务:

绑定服务后,会调用 ServiceConnection 的 onServiceConnected 方法,通过 Messenger 发送消息,服务器端的 Handler 就能够收到消息了:

这样的话我们就能够通过 bindService 获取到一个包含 Binder 的 Messenger 进行通信了,但是我们目前只实现了客户端对服务器端传递消息,那么服务器端如何对客户端传递消息呢?

我们先对服务器端的代码进行修改,首先修改 Service 的 Handler:

接着我们在客户端也增加一个 Handler 和 Messenger 来处理消息:

还有一个比较关键的地方,就是要在客户端发送消息的时候把客户端的 Messenger 通过消息传送到服务器端

这样一来,服务器端和客户端就能很好的实现跨进程通信了.

如果需要传送数据的话,可以通过 Bundle 设置数据,除了基本数据类型,还可以通过消息传送可序列化的对象

发送方:

接收方:

  • 弊端

上面我们已经实现了跨进程通信,但是这里面其实是有弊端的,服务端处理客户端的消息是串行的,必须一个一个来处理,所以如果是并发量比较大的时候,通过 Messenger 来通信就不太适合了

  • 注意

上面演示的是应用内跨进程通信,绑定服务可以通过显示意图来绑定,但是如果是跨应用的进程间通信,那么就需要用到隐式意图了.这里有一点需要注意的就是,在 5.0 以后隐式意图开启或者绑定 service 要 setPackage(Service的包名),不然会报错

AIDL

上面提到过通过 Messenger 跨进程不适合并发量大的情况,那么如果并发量大的话,我们用什么来处理呢?那就可以通过 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.

主要意思就是你可以用 Messenger 处理简单的跨进程通信,但是高并发量的要用AIDL

我们还是先演示一下同一个应用内的跨进程通信.

  • 服务端

首先我们创建一个 Service:

然后在清单文件里面设置 Service 的进程:

然后右键选择新建 AIDL 文件,Android Studio就会帮你在你的aidl目录的同名文件夹下面创建一个AIDL文件

在AIDL文件里面会有一个接口,并声明了一个方法,那个方法主要是告诉你AIDL支持哪些数据类型传输,所以我们把这个方法删掉,我们再自己声明一个方法,用于之后的调用

(注意:每次修改了AIDI文件后,需要同步一下才会生效,因为每次同步后,Android Studio会在 项目/build/generated/source/aidl/debug 目录下生成相应的Java文件)

我们在 Service 中创建一个 Binder,并在 onBind 的时候返回:

  • 客户端

创建自定义一个类实现 ServiceConnection:

绑定服务,当绑定成功时会走 Connection的onServiceConnected 方法,并把 Binder 传过来

在 onServiceConnected 方法里面通过 asInterface 获取服务器传过来的对象,并调用服务端的方法

现在客户端就可以调用 sell方法 来进行跨进程通信了,但目前只能传输基本数据类型的数据,那么如果想要传其他数据呢?那么我们接着往下讲:

  • 通过AIDL传送复杂数据

首先我们要知道AIDL支持那么数据类型

1. 基本数据类型

2. 实现了Parcelable接口的对象

3. List:只支持ArrayList,并且里面的元素需要时AIDL支持的

4. Map:只支持HashMap,并且里面的key和value都需要是被AIDL支持的

那么我们定义一个对象 Product 实现 Parcelable 接口,Product 我们设置了两个字段:

接着我们需要在 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方法,并再创建两个新方法:

这里有三个需要注意的地方

(1). IShop.aidl虽然跟Product.aidl在同一个包下,但是这里还是需要手动import进来

(2). 这里声明方法时,需要在参数前面增加一个tag,这个tag有三种,in,out,inout,这里表示的是这个参数可以支持的流向:

  • in: 这个对象能够从客户端到服务器,但是作为返回值从服务器到客户端的话数据不会传送过去(不会为null,但是字段都没有赋值)

  • out: 这个对象能够作为返回值从服务器到客户端,但是从客户端到服务器数据会为空(不会为null,但是字段都没有赋值)

  • inout: 能从客户端到服务器,也可以作为返回值从服务器到客户端

用一张图来总结: 

(不要都设为inout,要看需求来设置,因为会增加开销)

(3). 默认实现 Parcelable 的模版只支持 in ,如果需要需要支持 out 或 inout 需要手动实现 readFromParcel 方法:

现在就可以在客户端中通过 IShop 调用方法来进行通信了

不同应用间的多进程通信(AIDL)

上面我们介绍了同一个应用内的进程间通信,接下来我们就来介绍不同应用之间的进程间通信

  • 服务器端

首先我们需要把 Product.java 放到aidl目录相同名字的文件夹下(如果要提供服务给其他app,最好把需要的对象都放在aidl目录下,这样比较容易拷贝) 

但是这个时候你运行程序的话,编译会提示说找不到 Product,那是因为Android Studio默认会去java目录下找,这时候需要在build.gradle文件 android{ } 中间增加一段代码,让aidl目录里面的java文件也能被识别

接着我们为 Service 增加 intent-filter,这样其他应用才能通过隐式意图绑定服务,服务器端的修改就结束了

  • 客户端

我们需要创建一个新的应用来作为客户端,并且把服务器端的 aidl 目录下的所有文件都拷贝过来,这里要注意的就是里面的目录不能改变,需要与以前一致:

点击同步,Android Studio会自动生成相应的java文件供我们使用

这个时候我们需要通过隐式意图来绑定服务了(注意:5.0以后隐式意图开启或者绑定service要setPackage,不然会报错)

mIntent.setAction("action.xiayu");mIntent.setPackage("com.xiayu.aidldemo");

接下来的操作就和之前一样了,创建一个类实现 ServiceConnection:

绑定服务

bindService(mIntent, mXiayuConnection, BIND_AUTO_CREATE);

通过 ServiceConnection 的 onServiceConnected 里面的 IBinder 进行通信

解除绑定的时候释放资源

这样我们就可以通过获得的IShop进行不同应用之间的进程间通信了

最后再提几点用到服务时需要注意的地方(很简单,但是有些人经常会忽略这几点)

1: startService和stopService需要用同一个Intent对象

2: bindService和unbindService需要用同一个ServiceConnection对象

3: 5.0以后隐式意图开启或者绑定service要setPackage(包名)

更多

每天学习累了,看些搞笑的段子放松一下吧。关注最具娱乐精神的公众号,每天都有好心情。

如果你有好的技术文章想和大家分享,欢迎向我的公众号投稿,投稿具体细节请在公众号主页点击“投稿”菜单查看。

欢迎长按下图 -> 识别图中二维码或者扫一扫关注我的公众号:

原创粉丝点击