Android IPC 之AIDL

最近在外面面试,屡次被问到跨进程通讯,第一次觉得人家问的是 AIDL 的使用因而简明扼要的说了句:了解,可是没有在项目中使用过。后来面试的时候这个问题被说起的频率过高了,因而回来把《Android开发艺术探索》又翻了一遍,此次带着问题来看书效率确实很高,所以有了本篇文章的总结html

IPC 概念介绍

IPC 是Inter-Process Communication的缩写,意思是进程间通讯或者说跨进程通讯。通讯就如同咱们写信、发邮件、打电话、发微信同样,在代码实现方式上也有以下几种:java

  • Bundle
  • 文件共享
  • Message
  • AIDL
  • ContentProvider
  • Socket

既然实现方式达六种之多,那么像我这种也选择困难症的患者应该如何来选择呢?能够参考下表来选择适合你本身的业务场景android

名称 优势 缺点 适用场景
Bundle 简单易用 只能传输Bundle支持的数据类型 四大组件间的进程间通讯
文件共享 简单易用 不支持并发,没法即时通讯 无并发访问、数据简单且无实时性要求的场景
AIDL 支持一对多并发通讯,支持实时通讯 使用复杂,须要自行处理线程同步 一对多通讯,且有 RPC 需求
Message 支持一对多串行通讯,支持实时通讯 不支持高并发、不支持RPC、只能传输Bundle支持的数据类型 低并发的一对多即时通讯,无RPC需求或者无须要返回结果的RPC需求
ContentProvider 擅长数据资源访问,支持一对多并发数据共享,可扩展 受约束的AIDL,主要提供数据源的CRDU操做 一对多的进程间数据共享
Socket 经过网络传输字节流,支持一对多并发实时通讯 实现细节烦琐,不支持直接的RPC 网络数据交换

*RPC 是Remote Procedure Call,意思是跨进程回调的意思git

上面介绍了六种实现方式,接下来进入主题:详细介绍AIDL的使用。github

AIDL 的使用

AIDL 是 Android Interface Definition Language 的缩写,意思是Android接口定义语言,用于让某个Service与多个应用程序组件之间进行跨进程通讯,从而能够实现多个应用程序共享同一个Service的功能。其使用能够简单的归纳为服务端和客户端,相似于Socket 同样,服务端服务于全部客户端,支持一对多服务。可是服务端如何服务客户端呢?就像酒店里的客人入住之后,叫服务员打扫一下卫生,须要按铃同样,服务端也须要建立一套本身的响应系统,即 AIDL 接口。 可是这个 AIDL 接口和普通接口不同,其内部仅支持六种数据类型:面试

  1. 基本数据类型(经网友 劉思奇 提醒,short并不支持)
  2. StringCharSequence
  3. List 接口(会自动将List接口转为 ArrayList),且集合的每一个元素都必须可以被 AIDL 支持
  4. Map 接口(会自动将 Map 接口转为 HashMap),且每一个元素的 key 和 value 都必须被 AIDL 支持
  5. Parcelable 的实现类
  6. AIDL 接口自己

AIDL接口的建立

建立过程就不贴图了,直接上代码: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 注意事项微信

  • 使用 import 语句在此声明任何非默认类型,即自定义对象须要显示的使用 import 导入进来
  • 若是 AIDL 文件中使用了自定义的 parcelable 对象,那么必须新建一个和它同名的 AIDL 文件,如上面示例。而后在Module的build.gradle中加入下面图片中的代码

  • 除了基本类型数据,其它类型的参数必须标上方向:in、out、inout。in 表示输入;out 表示输出;inout 表示输入输出型的参数,注意按需使用,由于 out 以及 inout 在底层实现是须要必定开销的。
  • AIDL 接口仅支持方法,不支持静态变量,也不支持普通的接口
  • AIDL 全部相关的类在另外一个应用使用的时候须要保证全部文件的路径彻底一致,由于跨进程涉及到序列化和反序列化。假设 A 进程的 a 通过序列化传输到 B 进程,却在相同的文件路径下找不到响应的对象,这是会出错的。

服务端的实现

先上代码再说注意事项:网络

/**
 * 服务端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
    }
}
复制代码

建立服务端注意事项

  1. 由于 AIDL 中的方法是在服务端的Binder线程池执行,当服务端一对多时须要考虑方法的同步
  2. 当服务端的参数实现了List接口(或者 Map 接口),Binder就会按照List(或者Map )的规范去访问数据并造成 ArrayList(或者HashMap) 返回给客户端。重点是服务端不用考虑本身是什么ListMap)。

客户端的实现

咱们不生产代码,咱们只是代码的搬运工。在这里,我就直接复制书中的代码了:

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()
    }
}
复制代码

建立客户端注意事项

  1. 调用客户端的方法能够理解为调用服务器方法,即耗时操做的时候须要开启工做线程
  2. 服务端返回的数据类型如上面所言,只能是ArrayListHashMap)类型

日志:

list type:java.util.ArrayList
queryOffers:[[salary:5000, company:智联招聘]]
复制代码

以上就是一次完整的 IPC 通讯了,可是这样的通讯只能是单向的。就好像 APP 只能访问服务器,而服务器不能访问 APP 同样,可是如今人家服务器已经有推送了,咱们的服务端怎么即时通讯呢?接下来就看看经过观察者实现即时通讯。

即时通讯的实现

即时通讯原理

原理就是声明一个 AIDL 接口,而后在服务端所实现的 AIDL 接口中经过注册和注销来添加和删除声明的 AIDL 接口。而后在服务端须要发消息给客户端的时候遍历全部已注册的接口来发起通讯。

代码提及比较枯燥,接下来就经过代码实战来看看具体过程吧!

即时通讯的实战

1.声明 AIDL 接口

package com.vincent.keeplive.aidl;

import com.vincent.keeplive.aidl.Offer;
//  offer 观察接口
interface IOnNewOfferArrivedInterface {
    void onNewOfferArrived(in Offer offer);
}
复制代码

2.修改服务端 AIDL 接口

// 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);
}
复制代码

3.在服务端使用接口来实现即时通讯

/**
 * <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
    }
}
复制代码

4.客户端接收服务端实时信息

/**
 * 客户端
 */
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

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同样操做数据,在获取元素个数或者注册、注销接口的时候须要按照示例操做,其中beginBroadcastfinishBroadcast必须配对使用,不然会有异常beginBroadcast() called while already in a broadcast或者finishBroadcast() called outside of a broadcast

RemoteCallbackListregister方法中会触发IBinder.linkToDeath,在unregister方法中会触发IBinder.unlinkToDeath方法。

即时通讯的注意事项

  1. 客户端调用服务端的方法,被调用的方法运行在服务端的Binder线程池,同时客户端线程会被挂起。服务端方法运行在Binder线程池当中,能够执行耗时任务,非必要不建议单独开起工做线程进行异步任务。同理,当服务端调用客户端的方法时服务端挂起,被调用的方法运行在客户端的Binder线程池,一样须要注意耗时任务的线程切换
  2. 程序断开链接的回调有两种方式,一个是ServiceConnection.onServiceDisconnected(),该方法运行在客户端的 UI 线程;另外一个是Binder.DeathRecipient.binderDied(),该方法运行在客户端的Binder线程池,不能访问 UI

日志:

queryOffers:[[salary:5000, company:智联招聘]]
ThreadId:1262    offer:[salary:4500, company:51job]
复制代码

AIDL 权限验证

默认状况下,咱们的远程访问任何人均可以使用,这不是咱们但愿看到的,所以须要添加权限验证。权限验证能够在服务端的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")
  • 自定义权限步骤以下:
  1. 定义权限:<permission android:name="com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE" android:protectionLevel="normal"/>(被验证方直接跳过此步骤)
  2. 在项目中使用该权限: <uses-permission android:name="com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE" />
  3. 须要验证的组件添加权限:<service android:name=".RemoteService" android:enabled="true" android:exported="true" android:process=":jing" android:permission="com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE"/>
  4. 若是定义的权限为危险权限,在6.0以上的系统须要动态申请:if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { requestPermissions(arrayOf("com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE"),1000) }

参考:

使用Android studio建立的AIDL编译时找不到自定义类的解决办法

自定义权限

源码

相关文章
相关标签/搜索