最近在外面面试,屡次被问到跨进程通讯,第一次觉得人家问的是 AIDL 的使用因而简明扼要的说了句:了解,可是没有在项目中使用过。后来面试的时候这个问题被说起的频率过高了,因而回来把《Android开发艺术探索》又翻了一遍,此次带着问题来看书效率确实很高,所以有了本篇文章的总结html
IPC 是Inter-Process Communication
的缩写,意思是进程间通讯或者说跨进程通讯。通讯就如同咱们写信、发邮件、打电话、发微信同样,在代码实现方式上也有以下几种:java
既然实现方式达六种之多,那么像我这种也选择困难症的患者应该如何来选择呢?能够参考下表来选择适合你本身的业务场景android
名称 | 优势 | 缺点 | 适用场景 |
---|---|---|---|
Bundle | 简单易用 | 只能传输Bundle支持的数据类型 | 四大组件间的进程间通讯 |
文件共享 | 简单易用 | 不支持并发,没法即时通讯 | 无并发访问、数据简单且无实时性要求的场景 |
AIDL | 支持一对多并发通讯,支持实时通讯 | 使用复杂,须要自行处理线程同步 | 一对多通讯,且有 RPC 需求 |
Message | 支持一对多串行通讯,支持实时通讯 | 不支持高并发、不支持RPC、只能传输Bundle支持的数据类型 | 低并发的一对多即时通讯,无RPC需求或者无须要返回结果的RPC需求 |
ContentProvider | 擅长数据资源访问,支持一对多并发数据共享,可扩展 | 受约束的AIDL,主要提供数据源的CRDU操做 | 一对多的进程间数据共享 |
Socket | 经过网络传输字节流,支持一对多并发实时通讯 | 实现细节烦琐,不支持直接的RPC | 网络数据交换 |
*RPC 是Remote Procedure Call
,意思是跨进程回调的意思git
上面介绍了六种实现方式,接下来进入主题:详细介绍AIDL的使用。github
AIDL 是 Android Interface Definition Language
的缩写,意思是Android
接口定义语言,用于让某个Service
与多个应用程序组件之间进行跨进程通讯,从而能够实现多个应用程序共享同一个Service
的功能。其使用能够简单的归纳为服务端和客户端,相似于Socket
同样,服务端服务于全部客户端,支持一对多服务。可是服务端如何服务客户端呢?就像酒店里的客人入住之后,叫服务员打扫一下卫生,须要按铃同样,服务端也须要建立一套本身的响应系统,即 AIDL 接口。 可是这个 AIDL 接口和普通接口不同,其内部仅支持六种数据类型:面试
劉思奇
提醒,short
并不支持)String
和CharSequence
ArrayList
),且集合的每一个元素都必须可以被 AIDL 支持HashMap
),且每一个元素的 key 和 value 都必须被 AIDL 支持Parcelable
的实现类建立过程就不贴图了,直接上代码:bash
// 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<Offer> 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<Offer>()
private val mBinder = object :JobsInterface.Stub(){
override fun queryOffers(): MutableList<Offer> {
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<Offer> queryOffers();
void addOffer(in Offer offer);
void registerListener(IOnNewOfferArrivedInterface listener);
void unregisterListener(IOnNewOfferArrivedInterface listener);
}
复制代码
/**
* <p>文件描述:服务端service<p>
* <p>@author 烤鱼<p>
* <p>@date 2019/4/14 0014 <p>
* <p>@update 2019/4/14 0014<p>
* <p>版本号:1<p>
*
*/
class RemoteService : Service() {
private val TAG = this.javaClass.simpleName
// offer 容器
private val mList = mutableListOf<Offer>()
// aidl 接口专用容器
private val mListenerList = RemoteCallbackList<IOnNewOfferArrivedInterface>()
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<Offer> {
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<E extends IInterface>
内部经过ArrayMap来保存客户端实现的 AIDL 接口:ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();
其中的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
<uses-permission android:name="com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE"/>
<service android:name=".RemoteService"
android:enabled="true"
android:exported="true"
android:process=":jing"
android:permission="com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE"/>
复制代码
自定义权限注意事项
Intent().setClassName("com.vincent.keeplive","com.vincent.keeplive.RemoteService")
<permission android:name="com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE" android:protectionLevel="normal"/>
(被验证方直接跳过此步骤)<uses-permission android:name="com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE" />
<service android:name=".RemoteService" android:enabled="true" android:exported="true" android:process=":jing" android:permission="com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE"/>
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { requestPermissions(arrayOf("com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE"),1000) }
使用Android studio建立的AIDL编译时找不到自定义类的解决办法