Android进程通信AIDL(留存)

Android端的进程间通信(AIDL)

在Android系统中,每个应用程序启动时系统都会为其开辟一个新的进程,并运行在自己的进程空间中。而在Android平台中一个进程通常不能访问另一个进程的内存空间。而AIDL就是Android平台提供的实现进程间通信的一种机制。

AIDL(Android Interface Definition Language)是一种接口定义语言(IDL)。为了实现两个进程之间的通信,进程需要把对象分解成操作系统能够理解的数据格式,以达到跨越进程的边界,到达另一个进程后再进行组装,然而如果自己去编写这些解析和组装的代码将会非常困难并且枯燥无味,因此Android通过AIDL来协助实现这一过程(后面介绍的aidl接口里面的Stub内部类),客户端和服务端就通过这样的编程接口达成了共识(也即另一种意义的通信协议的统一),以便通过进程间通信(IPC)来完成相互通讯。

官方文档特意提起何时使用AIDL是必要的。仅当允许其他应用程序通过 IPC 方式访问服务,并且服务需要多线程运行时,才必须用到 AIDL。如果不需要进行跨越多个应用的并发 IPC,就应该用通过实现一个Binder对象来创建接口。或者要进行 IPC 但不需要多线程运行,则可使用Messenger对象来实现接口。

在Android端使用AIDL的具体步骤

一、 在服务端创建.aidl文件

在项目工程里面创建一个.aidl文件,并在文件中用java语言声明一个带有若干方法(这些方法包含自己定义的用于交换数据的具体逻辑)的接口,这些方法都可以带有参数和返回值,参数和返回值可以为任意类型,甚至可以是另一个由 AIDL 生成的接口。每个.aidl文件中必须也只能定义一个接口,且只能包含接口的定义和方法声明。AIDL默认支持以下数据类型:

l  java语言所有简单类型(int、long、char、boolean等)

l  String和CharSequence,

l  List: 该 List 中的所有数据只能是这里列出的类型、其他某个基于 AIDL 生成的接口、已声明的自定义 Parcelable 类。 List 还可被用作“泛型”类(如 List<String>)。虽然方法是通过 List 接口生成的,但是实际收到的实体类其实会是一个ArrayList 。

l  Map:该 Map 中的所有数据只能是这里列出的类型、其他某个基于 AIDL 生成的接口、已声明的自定义 Parcelable 类。这里不支持 Map 泛型(比如 Map<String,Integer> 的形式)。 虽然方法是通过 Map 接口生成的,但实际收到的实体类其实会是一个 HashMap。

对于未列入上述列表的类型,即便是定义于接口所在的包中,也必须包含 import 语句。如下图所示的项目工程文件和接口代码示例

package com.scott.aidl;

interface IPerson { 

/** 在这里可以获取服务的进程 ID 并完成一些用于通信的任务 */

String getPid(String someone);

String greet(String someone);  

要把 .aidl 文件保存到项目的 src/目录下,并用java语言编写用于通信的接口,在编译应用程序时,SDK 工具就会在项目的 gen/ 目录中生成 IBinder 接口文件。文件名与 .aidl 文件名相对应,只是后缀名变成了 .java(此java文件即是AIDL协助生成的通信接口)。

关于上述SDK工具协助生成的java接口的说明,此java文件是系统协助生成的,里面封装了一些用于进程间通信具体细节(即前面提到的用于跨越进程的边界而对对象进行的解析和组装)的代码,格式化后的部分关键代码说明如下:

package com.samsung.aidl; 

 

public interface IPerson extends android.os.IInterface { 

/**  每个系统协助生成的aidl的java代码都会包含一个继承自Binder 的 Stub内部类,用于处理进程间通信的细节  */

public static abstract class Stub extends android.os.Binder implements com.samsung.aidl.IPerson

 

        public Stub() { 

            this.attachInterface(this, DESCRIPTOR); 

        } 

 /** 此方法比较重要,用于将传入Ibinder对象转换成Iperson对象,此方法用于在客户端调用,来获得Iperson对象,以便进行方法调用以完成进程间的数据交换 */

public static com.scott.aidl.IPerson asInterface(android.os.IBinder obj) { 

//

……

      } 

 

        public android.os.IBinder asBinder() { 

            return this; 

        } 

}    }

public java.lang.String getPid (java.lang.String someone) throws android.os.RemoteException;

}

 

二、 在服务端实现接口并向客户端公布接口

实现由.aidl文件生成的接口,需要继承Binder接口(如前面定义的IPersion.Stub,对应于后面代码的①),并实现由自己定义的接口方法。向客户端公布接口以供绑定需要继承一个Service并实现其onBind()方法,以便返回自定义的Stub类实例(如上所述,对应于后面代码的②)。同时对此Service服务进行注册,并指定android:action属性,以供客户端绑定。代码示例如下

import com.scott.aidl.IPerson; 

 

public final class AIDLService extends Service { 

    /**  实现由.aidl文件生成的接口,需要继承Binder接口,而IPersion.Stub即是一个Binder子类对象,并实现自己定义的接口方法  */

    IPerson.Stub mBinder = new IPerson.Stub() {                 //①

        @Override 

        public String greet(String someone) throws RemoteException {   

            return "hello, " + someone; 

        } 

    }; 

  /**  向客户端公布接口以供绑定需要  ,以便返回自定义的Stub类实例 */

    @Override 

    public IBinder onBind(Intent intent) {                       //②

        return mBinder;

    }  

此步骤需要注意以下几点(引自AIDL官网文档翻译):

l  调用不一定是在主线程中运行,因此从一开始就要考虑多线程运行,并保证服务是按照线程安全的模式编写的。

l  默认情况下, RPC调用是以同步方式运行的。如果事先知道服务处理完一次请求需要若干毫秒的时间,就不应该在 Activity 的主线程中发起调用。因为这可能会导致应用程序挂起——通常,这时应该在客户端的单独线程中发起调用。

l  所有异常都不会(Exception)发还给调用者。

三、 在客户端调用服务端公布的接口

若客户端要访问服务端接口,其必须有访问接口类的权限,因此,假如客户端和服务端位于不同的应用,客户端的应用必须在其 src/ 目录下拥有一份 .aidl 文件的拷贝(同时要保证包名的一致性,因为系统协助生成的接口中继承了自定义的接口的完整包名,见前面代码标红部分),用于生成供客户端访问 AIDL 方法的 android.os.Binder 接口。这样就间接实现了双方通信协议的一致性。

客户端要成功调用,需要调用bindService()连接到该服务,客户端的 onServiceConnected() 方法将会收到服务的 onBind() 方法返回的 mBinder 实例。此时调用IPerson.Stub. asInterface(service)方法即可返回的IBinder对象转化为对应的自定义接口对象,此后便可对自定义接口对象进行调用,从而达到进程间通信的目的。

/**  在客户端定义的Iperson对象,用于调用自定义的接口方法 */

private IPerson person; 

/**  当activity和服务绑定的时候回调用onServiceConnected方法,第二个参数service即是系统获取到的服务端返回的IBinder对象,再调用此对象的asInterface方法即可得到IPersion接口对象,可与前文的stub内部类对应 */     

    private ServiceConnection conn = new ServiceConnection() { 

        @Override 

        public void onServiceConnected(ComponentName name, IBinder service) {   

            person = IPerson.Stub.asInterface(service); 

        } 

 

        @Override 

        public void onServiceDisconnected(ComponentName name) { 

          

        } 

    }; 

四、 跨进程传递对象

如果要跨进程传递某个类,可以通过 IPC 接口来实现。但是需要确保在 IPC 通道的对端可以识别该类的代码,该类必须支持 Parcelable 接口(序列化)。支持 Parcelable 接口非常重要,因为这使得 Android 系统可将对象分解为能够跨进程组装的原生数据。创建支持 Parcelable 协议的类的步骤如下:

1.必须实现Parcelable 接口。

2.实现 writeToParcel 方法,参数为当前对象的状态,并写入一个 Parcel中。读取Parcel数据的次序要和这里的write次序一致,否则可能会读错数据。

3.在类中添加一个名为 CREATOR 的静态成员变量(这个CREATOR命名是固定的),即为一个实现了 Parcelable.Creator 接口的对象。

4.最后,创建 .aidl 文件,声明该 Parcelable 类。代码如下所示

import android.os.Parcel;

import android.os.Parcelable;

/**  实现的Parcelable接口 */

public final class Rect implements Parcelable {

    public int left;

    public int top;

    public int right;

    public int bottom;

/**  在类中添加的一个名为 CREATOR 的静态成员变量,即为一个实现了 Parcelable.Creator 接口的对象。 */

    public static final Parcelable.Creator<Rect> CREATOR = new

Parcelable.Creator<Rect>() {

        public Rect createFromParcel(Parcel in) {

            return new Rect(in);

        }

        public Rect[] newArray(int size) {

            return new Rect[size];

        }

    };

 

    public Rect() {

    }

    private Rect(Parcel in) {

        readFromParcel(in);

    }

    public void writeToParcel(Parcel out) {

        out.writeInt(left);

        out.writeInt(top);

        out.writeInt(right);

        out.writeInt(bottom);

    }

 

    public void readFromParcel(Parcel in) {

        left = in.readInt();

        top = in.readInt();

        right = in.readInt();

        bottom = in.readInt();

    }

}

Android端AIDL的实现细节和原理

AIDL是一个接口描述文件,用于实现Android平台上面的RPC,在编译的时候系统会自动根据规则生成用于IPC的接口和对象,而作为使用者只需要:1.在服务端Service实现接口;2. 在客户端bindService,onServiceConnected时获取接口对象。这里的接口都是AIDL中描述的接口,其他的细节则在由AIDL生成的同名源码文件中。通过查看gen文件夹下生成的与AIDL文件同名的源码文件,可以了解AIDL的本质,现将源码的主要部分展示如下(说明:此源码文件会在服务端和客户端同时拥有一份,原因见上文):

public interface IPerson extends android.os.IInterface {

    public static abstract class Stub extends android.os.Binder implements com.scott.aidl.IPerson {

    

        public static com.scott.aidl.IPerson asInterface(android.os.IBinder obj) {

            if ((obj == null)) {

                return null;

            }

            android.os.IInterface iin = (android.os.IInterface) obj.queryLocalInterface(DESCRIPTOR);

            if (((iin != null) && (iin instanceof com.scott.aidl.IPerson))) {

                return ((com.scott.aidl.IPerson) iin);

            }

            return new com.scott.aidl.IPerson.Stub.Proxy(obj);

        }

        @Override

        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)

                throws android.os.RemoteException {             //

            switch (code) {

                case TRANSACTION_greet: {

                    data.enforceInterface(DESCRIPTOR);

                    java.lang.String _arg0;

                    _arg0 = data.readString();

                    java.lang.String _result = this.greet(_arg0);

                    reply.writeNoException();

                    reply.writeString(_result);

                    return true;

                }

            }

            return super.onTransact(code, data, reply, flags);

        }

        private static class Proxy implements com.scott.aidl.IPerson {

            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {

                mRemote = remote;

            }

            public java.lang.String greet(java.lang.String someone) throws android.os.RemoteException {

                android.os.Parcel _data = android.os.Parcel.obtain();

                android.os.Parcel _reply = android.os.Parcel.obtain();

                java.lang.String _result;

                try {

                    _data.writeInterfaceToken(DESCRIPTOR);

                    _data.writeString(someone);

                    mRemote.transact(Stub.TRANSACTION_greet, _data, _reply, 0);    //  

                    _reply.readException();

                    _result = _reply.readString();

                } finally {

                    _reply.recycle();

                    _data.recycle();

                }

                return _result;

            }

        }

        static final int TRANSACTION_greet = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);

    }

    public java.lang.String greet(java.lang.String someone) throws android.os.RemoteException;

}

首先在上述代码中的接口里(代码中标红部分),有一个在.aidl文件里自己定义的方法;一个继承自Binder的Stub内部类,这个就是我们要在service里面要实现的基类;还有一个proxy的内部类。从使用者的角度来看这个源码,其大体过程如下:最外层是一个与AIDL同名的接口,里面有自己定义的函数声明。客户端使用IPerson.Stub.asInterface,可以看到这个方法返回了一个IPersion对象。另外就是Server端会让Service实现IPersion.Stub,其实是就是实现了IPersion接口,因为Stub也扩展了IPersion接口(见上述代码)。

通过源码分析可以看出,当通讯的双方在同一个进程中时,onServiceConnected传回的对象是Service.onBind()所返回的对象;但如果是跨进程时,则其返回的是一个BinderProxy对象。所以,可以看到在AIDL生成的代码中有如下判断:

android.os.IInterface iin = (android.os.IInterface) obj.queryLocalInterface(DESCRIPTOR);

            if (((iin != null) && (iin instanceof com.scott.aidl.IPerson))) {

                return ((com.scott.aidl.IPerson) iin);

            }

 return new com.scott.aidl.IPerson.Stub.Proxy(obj);

这实际上就是判断此通讯是在同一进程中,还是跨进程,因为同一进程传回的对象是Service.onBind()所返回的对象,而此对象必然实现了接口。所以,如果仅是在同一个进程之中,就不会走Binder进程的IPC,而是直接返回Service所提供的对象,直接调用其方法。

当在不同的进程中时,客户端调用Stub.asInterface会返回一个Stub.Proxy对象,调用其中自定greet方法,并调用Binder. transact()来进行远程方法调用(如上述标红的①)。而服务端仅会执行Stub.onTransact()方法(如上述标红的②),然后就会调用Service端的自定义greet方法了,从而对客户端所请求的方法做出响应。对于上层使用者来说,用transact()把函数的信息(参数,标识和开关)发送出去,剩下的就是Binder的工作了,内部还有大量的细节,但是最终会调用到服务端Binder的onTransact()方法,然后调用具体的实现,再传回返回值,这样一个IPC的函数调用就完成了。

就本质来看,AIDL的作用就是对Binder的二个方法:Binder.transact()和Binder.onTransact()进行封装,而其中便封装了上文提到的对传输对象进行解析和组合的代码,以供Client端和Server端进行使用。因为实现transact()和onTransact()方法的方式基本上是相同的,所以就可以用模板来生成具体的代码。理论上讲只需要为Client端生成transact()相关代码,为服务端生成onTransact()代码即可,但因为工具无法准确的确定某一个应用到底是Client端还是Server端,所以它就生成所有的代码,放在一个文件中。这就是所看到的自动生成的文件。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1 目标检测的定义 目标检测(Object Detection)的任务是找出图像中所有感兴趣的目标(物体),确定它们的类别和位置,是计算机视觉领域的核心问题之一。由于各类物体有不同的外观、形状和姿态,加上成像时光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具有挑战性的问题。 目标检测任务可分为两个关键的子任务,目标定位和目标分类。首先检测图像中目标的位置(目标定位),然后给出每个目标的具体类别(目标分类)。输出结果是一个边界框(称为Bounding-box,一般形式为(x1,y1,x2,y2),表示框的左上角坐标和右下角坐标),一个置信度分数(Confidence Score),表示边界框中是否包含检测对象的概率和各个类别的概率(首先得到类别概率,经过Softmax可得到类别标签)。 1.1 Two stage方法 目前主流的基于深度学习的目标检测算法主要分为两类:Two stage和One stage。Two stage方法将目标检测过程分为两个阶段。第一个阶段是 Region Proposal 生成阶段,主要用于生成潜在的目标候选框(Bounding-box proposals)。这个阶段通常使用卷积神经网络(CNN)从输入图像中提取特征,然后通过一些技巧(如选择性搜索)来生成候选框。第二个阶段是分类和位置精修阶段,将第一个阶段生成的候选框输入到另一个 CNN 中进行分类,并根据分类结果对候选框的位置进行微调。Two stage 方法的优点是准确度较高,缺点是速度相对较慢。 常见Tow stage目标检测算法有:R-CNN系列、SPPNet等。 1.2 One stage方法 One stage方法直接利用模型提取特征值,并利用这些特征值进行目标的分类和定位,不需要生成Region Proposal。这种方法的优点是速度快,因为省略了Region Proposal生成的过程。One stage方法的缺点是准确度相对较低,因为它没有对潜在的目标进行预先筛选。 常见的One stage目标检测算法有:YOLO系列、SSD系列和RetinaNet等。 2 常见名词解释 2.1 NMS(Non-Maximum Suppression) 目标检测模型一般会给出目标的多个预测边界框,对成百上千的预测边界框都进行调整肯定是不可行的,需要对这些结果先进行一个大体的挑选。NMS称为非极大值抑制,作用是从众多预测边界框中挑选出最具代表性的结果,这样可以加快算法效率,其主要流程如下: 设定一个置信度分数阈值,将置信度分数小于阈值的直接过滤掉 将剩下框的置信度分数从大到小排序,选中值最大的框 遍历其余的框,如果和当前框的重叠面积(IOU)大于设定的阈值(一般为0.7),就将框删除(超过设定阈值,认为两个框的里面的物体属于同一个类别) 从未处理的框中继续选一个置信度分数最大的,重复上述过程,直至所有框处理完毕 2.2 IoU(Intersection over Union) 定义了两个边界框的重叠度,当预测边界框和真实边界框差异很小时,或重叠度很大时,表示模型产生的预测边界框很准确。边界框A、B的IOU计算公式为: 2.3 mAP(mean Average Precision) mAP即均值平均精度,是评估目标检测模型效果的最重要指标,这个值介于0到1之间,且越大越好。mAP是AP(Average Precision)的平均值,那么首先需要了解AP的概念。想要了解AP的概念,还要首先了解目标检测中Precision和Recall的概念。 首先我们设置置信度阈值(Confidence Threshold)和IoU阈值(一般设置为0.5,也会衡量0.75以及0.9的mAP值): 当一个预测边界框被认为是True Positive(TP)时,需要同时满足下面三个条件: Confidence Score > Confidence Threshold 预测类别匹配真实值(Ground truth)的类别 预测边界框的IoU大于设定的IoU阈值 不满足条件2或条件3,则认为是False Positive(FP)。当对应同一个真值有多个预测结果时,只有最高置信度分数的预测结果被认为是True Positive,其余被认为是False Positive。 Precision和Recall的概念如下图所示: Precision表示TP与预测边界框数量的比值 Recall表示TP与真实边界框数量的比值 改变不同的置信度阈值,可以获得多组Precision和Recall,Recall放X轴,Precision放Y轴,可以画出一个Precision-Recall曲线,简称P-R
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值