最近在外面面试,多次被问到跨进程通信,第一次以为人家问的是 AIDL 的使用于是简明扼要的说了句:了解,但是没有在项目中使用过。后来面试的时候这个问题被提及的频率太高了,于是回来把《Android开发艺术探索》又翻了一遍,这次带着问题来看书效率确实很高,因此有了本篇文章的总结
IPC 是Inter-Process Communication
的缩写,意思是进程间通信或者说跨进程通信。通信就如同我们写信、发邮件、打电话、发微信一样,在代码实现方式上也有如下几种:
既然实现方式达六种之多,那么像我这种也选择困难症的患者应该如何来选择呢?可以参考下表来选择适合你自己的业务场景
名称 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
Bundle | 简单易用 | 只能传输Bundle支持的数据类型 | 四大组件间的进程间通信 |
文件共享 | 简单易用 | 不支持并发,无法即时通信 | 无并发访问、数据简单且无实时性要求的场景 |
AIDL | 支持一对多并发通信,支持实时通信 | 使用复杂,需要自行处理线程同步 | 一对多通信,且有 RPC 需求 |
Message | 支持一对多串行通信,支持实时通信 | 不支持高并发、不支持RPC、只能传输Bundle支持的数据类型 | 低并发的一对多即时通信,无RPC需求或者无需要返回结果的RPC需求 |
ContentProvider | 擅长数据资源访问,支持一对多并发数据共享,可扩展 | 受约束的AIDL,主要提供数据源的CRDU操作 | 一对多的进程间数据共享 |
Socket | 通过网络传输字节流,支持一对多并发实时通信 | 实现细节烦琐,不支持直接的RPC | 网络数据交换 |
*RPC 是Remote Procedure Call
,意思是跨进程回调的意思
上面介绍了六种实现方式,接下来进入主题:详细介绍AIDL的使用。
AIDL 是 Android Interface Definition Language
的缩写,意思是Android
接口定义语言,用于让某个Service
与多个应用程序组件之间进行跨进程通信,从而可以实现多个应用程序共享同一个Service
的功能。其使用可以简单的概括为服务端和客户端,类似于Socket
一样,服务端服务于所有客户端,支持一对多服务。但是服务端如何服务客户端呢?就像酒店里的客人入住以后,叫服务员打扫一下卫生,需要按铃一样,服务端也需要创建一套自己的响应系统,即 AIDL 接口。
但是这个 AIDL 接口和普通接口不一样,其内部仅支持六种数据类型:
String
和CharSequence
ArrayList
),且集合的每个元素都必须能够被 AIDL 支持HashMap
),且每个元素的 key 和 value 都必须被 AIDL 支持Parcelable
的实现类创建过程就不贴图了,直接上代码:
// JobsInterface.aidl
package com.vincent.keeplive;
// Declare any non-default types here with import statements
// 使用import语句在此声明任何非默认类型
interface JobsInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
basicTypes
方法是示例,意思是支持的基本数据类型,我们直接删除即可,然后添加两个我们需要测试的方法:
// JobsInterface.aidl
package com.vincent.keeplive;
// Declare any non-default types here with import statements
// 使用import语句在此声明任何非默认类型
import com.vincent.keeplive.Offer;
interface JobsInterface {
List queryOffers();
void addOffer(in Offer offer);
}
// Offer.aidl
package com.vincent.keeplive;
// Declare any non-default types here with import statements
parcelable Offer;
创建 AIDL 注意事项:
parcelable
对象,那么必须新建一个和它同名的 AIDL 文件,如上面示例。然后在Module的build.gradle中加入下面图片中的代码先上代码再说注意事项:
/**
* 服务端service
*/
class RemoteService : Service() {
private val TAG = this.javaClass.simpleName
private val mList = mutableListOf()
private val mBinder = object :JobsInterface.Stub(){
override fun queryOffers(): MutableList {
return mList;
}
override fun addOffer(offer: Offer) {
mList.add(offer)
}
}
override fun onCreate() {
super.onCreate()
mList.add(Offer(5000,"智联招聘"))
}
override fun onBind(intent: Intent): IBinder {
return mBinder
}
}
创建服务端注意事项:
Binder
线程池执行,当服务端一对多时需要考虑方法的同步List
接口(或者 Map
接口),Binder
就会按照List
(或者Map
)的规范去访问数据并形成 ArrayList
(或者HashMap
) 返回给客户端。重点是服务端不用考虑自己是什么List
(Map
)。我们不生产代码,我们只是代码的搬运工。在这里,我就直接复制书中的代码了:
class MainActivity : AppCompatActivity() {
private val TAG = this.javaClass.simpleName
private val mConnection = object :ServiceConnection{
override fun onServiceDisconnected(name: ComponentName?) {
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val manager = JobsInterface.Stub.asInterface(service)
val list = manager.queryOffers()
Log.e(TAG,"list type:${list.javaClass.canonicalName}")
Log.e(TAG,"queryOffers:${list.toString()}")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
bindService(Intent(this, RemoteService::class.java),mConnection,Context.BIND_AUTO_CREATE)
}
override fun onDestroy() {
unbindService(mConnection)
super.onDestroy()
}
}
创建客户端注意事项:
ArrayList
(HashMap
)类型日志:
list type:java.util.ArrayList
queryOffers:[[salary:5000, company:智联招聘]]
以上就是一次完整的 IPC 通信了,但是这样的通信只能是单向的。就好像 APP 只能访问服务器,而服务器不能访问 APP 一样,但是现在人家服务器已经有推送了,我们的服务端怎么即时通信呢?接下来就看看通过观察者实现即时通信。
原理就是声明一个 AIDL 接口,然后在服务端所实现的 AIDL 接口中通过注册和注销来添加和删除声明的 AIDL 接口。然后在服务端需要发消息给客户端的时候遍历所有已注册的接口来发起通信。
代码说起比较枯燥,接下来就通过代码实战来看看具体过程吧!
package com.vincent.keeplive.aidl;
import com.vincent.keeplive.aidl.Offer;
// offer 观察接口
interface IOnNewOfferArrivedInterface {
void onNewOfferArrived(in Offer offer);
}
// JobsInterface.aidl
package com.vincent.keeplive.aidl;
// Declare any non-default types here with import statements
// 使用import语句在此声明任何非默认类型
import com.vincent.keeplive.aidl.Offer;
import com.vincent.keeplive.aidl.IOnNewOfferArrivedInterface;
interface JobsInterface {
List queryOffers();
void addOffer(in Offer offer);
void registerListener(IOnNewOfferArrivedInterface listener);
void unregisterListener(IOnNewOfferArrivedInterface listener);
}
/**
* 文件描述:服务端service
*
@author 烤鱼
*
@date 2019/4/14 0014
*
@update 2019/4/14 0014
*
版本号:1
*
*/
class RemoteService : Service() {
private val TAG = this.javaClass.simpleName
// offer 容器
private val mList = mutableListOf()
// aidl 接口专用容器
private val mListenerList = RemoteCallbackList()
private val mBinder = object : JobsInterface.Stub(){
override fun registerListener(listener: IOnNewOfferArrivedInterface?) {
mListenerList.register(listener)
}
override fun unregisterListener(listener: IOnNewOfferArrivedInterface?) {
mListenerList.unregister(listener)
}
override fun queryOffers(): MutableList {
return mList;
}
override fun addOffer(offer: Offer) {
mList.add(offer)
// 向客户端通信
val size = mListenerList.beginBroadcast()
for (i in 0 until size ){
val listener = mListenerList.getBroadcastItem(i)
listener.onNewOfferArrived(offer)
}
mListenerList.finishBroadcast()
}
}
override fun onCreate() {
super.onCreate()
mList.add(Offer(5000, "智联招聘"))
}
override fun onBind(intent: Intent): IBinder {
Handler().postDelayed({
mBinder.addOffer(Offer(4500,"51job"))
},1000)
return mBinder
}
}
/**
* 客户端
*/
class MainActivity : AppCompatActivity() {
private val TAG = this.javaClass.simpleName
var manager:JobsInterface? = null
private val mConnection = object :ServiceConnection{
override fun onServiceDisconnected(name: ComponentName?) {
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
manager = JobsInterface.Stub.asInterface(service)
val list = manager?.queryOffers()
Log.e(TAG,"list type:${list?.javaClass?.canonicalName}")
Log.e(TAG,"queryOffers:${list.toString()}")
manager?.registerListener(mArrivedListener)
// service?.linkToDeath({
// // Binder 连接死亡回调 此处需要重置 manager 并发起重连
// },0)
}
}
private val mArrivedListener = object : IOnNewOfferArrivedInterface.Stub(){
override fun onNewOfferArrived(offer: Offer?) {
Log.e(TAG,"ThreadId:${Thread.currentThread().id} offer:${offer}")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
bindService(Intent(this, RemoteService::class.java),mConnection,Context.BIND_AUTO_CREATE)
}
override fun onDestroy() {
manager?.let {
if(it.asBinder().isBinderAlive){
it.unregisterListener(mArrivedListener)
}
}
unbindService(mConnection)
super.onDestroy()
}
}
RemoteCallbackList
是专门用来处理 AIDL 接口的容器:public class RemoteCallbackList
内部通过ArrayMap来保存客户端实现的 AIDL 接口:ArrayMap
其中的Binder
是客户端底层传输信息的Binder
作为key
,AIDL 接口作为value
RemoteCallbackList
无法像List
一样操作数据,在获取元素个数或者注册、注销接口的时候需要按照示例操作,其中beginBroadcast
和finishBroadcast
必须配对使用,否则会有异常beginBroadcast() called while already in a broadcast
或者finishBroadcast() called outside of a broadcast
。
RemoteCallbackList
在register
方法中会触发IBinder.linkToDeath
,在unregister
方法中会触发IBinder.unlinkToDeath
方法。
即时通信的注意事项
Binder
线程池,同时客户端线程会被挂起。服务端方法运行在Binder
线程池当中,可以执行耗时任务,非必要不建议单独开起工作线程进行异步任务。同理,当服务端调用客户端的方法时服务端挂起,被调用的方法运行在客户端的Binder
线程池,同样需要注意耗时任务的线程切换ServiceConnection.onServiceDisconnected()
,该方法运行在客户端的 UI 线程;另一个是Binder.DeathRecipient.binderDied()
,该方法运行在客户端的Binder
线程池,不能访问 UI日志:
queryOffers:[[salary:5000, company:智联招聘]]
ThreadId:1262 offer:[salary:4500, company:51job]
默认情况下,我们的远程访问任何人都可以使用,这不是我们希望看到的,因此需要添加权限验证。权限验证可以在服务端的onBind()
方法中执行,也可以在onTransact()
方法中执行,既可以自定义权限验证,也可以通过包名的方式验证。
示例:
private val mBinder = object : JobsInterface.Stub(){
......
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
// 验证权限 返回false代表权限未验证通过
val check = checkCallingOrSelfPermission("com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE")
if(check == PackageManager.PERMISSION_DENIED){
return false
}
val packages = packageManager.getPackagesForUid(Binder.getCallingUid())
if(packages != null && packages.size>0){
if(!packages[0].endsWith("keeplive")){
return false
}
}
return super.onTransact(code, data, reply, flags)
}
}
// AndroidManifest
自定义权限注意事项
Intent().setClassName("com.vincent.keeplive","com.vincent.keeplive.RemoteService")
(被验证方直接跳过此步骤)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { requestPermissions(arrayOf("com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE"),1000) }
使用Android studio创建的AIDL编译时找不到自定义类的解决办法
自定义权限