想进阶,AIDL你得学习一下

想进阶,AIDL你得学习一下


/   今日科技快讯   /


5月13日,美国当地时间周六,SpaceX首席执行官伊隆·马斯克(Elon Musk)发布推文宣称,该公司将于本周发射60颗太空互联网卫星。


/   作者简介   /


本篇文章来自Vincent_9920的投稿,分享了他对跨进程通信中AIDL的理解,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章。


Vincent_9920的博客地址:

https://juejin.im/user/593def80fe88c2006a2707df


/   IPC 概念介绍   /


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


  • Bundle

  • 文件共享

  • Message

  • AIDL

  • ContentProvider

  • Socket


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


想进阶,AIDL你得学习一下


RPC 是Remote Procedure Call,意思是跨进程回调的意思。上面介绍了六种实现方式,接下来进入主题:详细介绍AIDL的使用。


/   AIDL 的使用   /


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


  1. 基本数据类型

  2. String和CharSequence

  3. List 接口(会自动将List接口转为 ArrayList),且集合的每个元素都必须能够被 AIDL 支持

  4. Map 接口(会自动将 Map 接口转为 HashMap),且每个元素的 key 和 value 都必须被 AIDL 支持

  5. Parcelable 的实现类

  6. AIDL 接口本身


AIDL接口的创建


创建过程就不贴图了,直接上代码:



// 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中加入下面图片中的代码


想进阶,AIDL你得学习一下


  • 除了基本类型数据,其它类型的参数必须标上方向: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) 返回给客户端。重点是服务端不用考虑自己是什么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()
    }
}


创建客户端注意事项:


  1. 调用客户端的方法可以理解为调用服务器方法,即耗时操作的时候需要开启工作线程

  2. 服务端返回的数据类型如上面所言,只能是ArrayList(HashMap)类型


日志:



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


RemoteCallbackList在register方法中会触发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")


自定义权限步骤如下:


定义权限



<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"/>


如果定义的权限为危险权限,在6.0以上的系统需要动态申请



if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 requestPermissions(arrayOf("com.vincent.keeplive.permission.ACCESS_OFFER_SERVICE"),1000
}


参考1

https://www.cnblogs.com/shiwei-bai/p/5207313.html


参考2

https://www.cnblogs.com/liuzhipenglove/p/7102889.html


源码

https://github.com/Vicent9920/KeepLive


推荐阅读:

一篇文章带你看遍Google I/O 2019大会

今天我们深入探讨一下如何写好异步代码

程序媛说源码:AsyncTask在子线程创建与调用的那些事儿


欢迎关注我的公众号

学习技术或投稿


想进阶,AIDL你得学习一下


想进阶,AIDL你得学习一下

长按上图,识别图中二维码即可关注