解读Android之Service(3)AIDL

本文翻译自android官方文档,结合自己测试,整理如下。

Android Interface Definition Language(AIDL)能够让我们定义自己的编程接口,该接口可以使得客户端和service之间进行跨进程通信(interprocess communication,IPC)。通常,在android中无法直接跨进程通信。因此,需要把传递的对象分解成系统可以识别的原始状态(数据),并将它们跨进程序列化marshalling。由于marshalling过程繁琐,因此android通过AIDL处理。

注意:只有当我们允许不同应用程序的客户端获取service来进行IPC,并且在service中需要处理多线程时,AIDL才是必须的。绝大多数应用程序都不应该用AIDL来创建Bound Service,因为这可能需要多线程处理能力并且会让代码变得更为复杂。因此,AIDL对绝大多数应用程序都不适用。如果只是在应用程序内部使用,并且不需要跨进程,我们可以通过继承Binder类直接进行交互,这种是最常见的方式。若跨进程IPC且不需要处理多线程问题可以通过使用Messenger方法,因为Messenger把所有请求都放在一个线程中,因此不必担心线程安全问题。

(下面的理解上还有些问题)在开始设计AIDL接口之前,需要注意的是调用AIDL接口是直接的调用方法,我们不应该假设调用方法发生在子线程。从本地进程和远程进程中的线程调用是不同的:

  • 在本地进程中调用。若在主线程中调用,则AIDL接口会在主线程中执行。若是另外的子线程,则该线程执行service中的代码。因此 若只有本地线程获取service,我们能够完全控制。这种情况不应该使用AIDL,而是继承Binder实现。
  • 若是远程进程调用,则AIDL的实现必须要保证完全地线程安全。这是因为若是支持远程调用的话可能需要同时处理多个调用(并发处理)。
  • 单向改变远程调用行为。当使用这种方式时,远程调用不会阻塞;它简单的发送数据,并立刻返回。最终,接口实现接收向常规的通过Binder线程池调用一样处理远程调用。若单向用在本地调用,则不会影响并且调用仍是异步的。

上述内容理解有的问题,须再查阅资料并验证,同时请各位不吝赐教。

定义AIDL接口

AIDL接口必须定义在.aidl文件中(命名满足java语法),并同时保存在service所在的应用程序和其它绑定该service的应用程序(需要通过AIDL进行IPC的service)中,保存位置为源代码中src/目录下。

当我们新建一个.aidl文件时,android SDK工具就会根据该文件自动生成一个IBinder接口,并且保存在gen/目录下。service必须实现IBinder接口,客户端才能绑定service并调用方法获得该对象进行IPC。

上面的目录是在eclipse中的,在android studio中则在:

为了能够创建使用AIDL的service,必须要实现以下步骤:

  1. 创建.aidl文件

    该文件定义了带有方法声明的编程接口

  2. 实现接口

    android SDK工具使用java生成一个接口,依据是根据.aidl文件。这个接口中有一个内部抽象类Stub,该类继承了Binder类,并且实现了AIDL接口中的方法,我们必须继承Stub类和实现其方法。

  3. 将接口暴露给客户端

    实现Service类,覆盖onBind()方法,并且返回Stub的实现。

注意:在我们第一次发布之后的改变AIDL接口都要保证对原来的版本的兼容性,避免其它应用无法访问我们的service(其它客户端可能拷贝的还是原来的接口版本)。

下面详细介绍以上几步:

1. 创建.aidl文件

AIDL需要满足一些简单的语法:能够使我们声明一个接口,该接口可以包含一个或多个方法,且能够带有参数和返回值。参数和返回值可以是任何类型的甚至是其它AIDL生成的接口。

每个.aidl文件必须定义一个接口,并且只需要接口声明和方法声明。

默认情况下,AIDL支持以下数据类型:

  • java中八大基本数据类型
  • String
  • CharSequence
  • List

    当然,List中的类型也需要是AIDL支持的类型。通常使用List声明变量,而具体的类型(例如ArrayList)在定义时确定。

  • Map

    和List用法一样。

若是使用上述以外的类型(例如自定义类)必须导入相关声明,即使在同一个包中定义的。

在定义AIDL接口时,需要注意以下几点:

  • 方法可以带有0个或多个参数,可以选择有无返回值。
  • 所有的非基本数据类型都需要指定一个方向来标记数据的流向(进,出,进出同时)。基本数据类型默认是进,且不能改变。我们有必要限制数据方向(真正需要的方向),原因在于marshalling参数的代价消耗大。
  • 文件中的所有代码注释都被包含在生成的IBinder接口中,除非是import和package之前注释的。
  • 只支持方法,不支持静态成员变量。且方法不能有修饰符。
  • 需要手动输入包名(android studio不需要手动)

下面有一个例子:


package com.sywyg.servicetest;

import com.sywyg.servicetest.Man;
// Declare any non-default types here with import statements
// 需要导入自定义类型的位置
interface IRemoteService{
   /** Request the process ID of this service, to do evil things with it. */
    int getPid();

    /** 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);
     // Man getMan();
}

然后系统就会自动生成一个IRemoteService.java(对应IRemoteService.aidl)文件。

有的编译器是立刻生成,有的则在编译应用程序时生成,这点注意。

2. 实现接口

自动生成文件包含一个内部抽象类Stub,继承了Binder并是父类接口的一个抽象实现,实现了.aidl文件中的所有方法。Stub同时定义了一些其他有用的方法,尤其是asInterface()方法,该方法接收一个IBinder对象,返回Stub接口的实现。Stub英文表示存根的意思,该类在服务端进程,我们必须继承该类并实现aidl接口中的方法。

下面用一个匿名类实现简单的接口实例:

    /**
     * 定义一个匿名内部类实现aidl接口,需要继承IRemoteService.Stub类
     * 在.aidl文件中声明的方法需要在这里实现具体功能
     */
    private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
        public int getPid(){
            Log.d(TAG,"getPid is executed ...");
            return android.os.Process.myPid();
        }
        public void basicTypes(int anInt, long aLong, boolean aBoolean,
                                   float aFloat, double aDouble, String aString) {
            Log.d(TAG,"basicTypes is executed ...");
        }
    };

Stub实现了Binder类(定义了远程过程调用协议Remote Procedure Call Protocol RPC),因此mBinder可以传输给客户端。

在实现AIDL时需要注意一下几点:

  • 调用不能保证在主线程中执行,我们应该考虑多线程问题,并保证service是线程安全的。
  • 默认情况,RPC调用是异步的。若service需要长时间的操作要保证调用不能发生在主线程中,因为这个可能出现应用程序无法响应问题Application Not Responding ANR。因此我们应该保证调用发生在另外的子线程中。
  • 不会给调用者抛出异常。

3. 将接口暴露给客户端

为service实现了AIDL接口,我们应该把接口暴露给客户端,使得他们能够绑定它。下面给出完整的代码,说明如何实现:


/**
 * 通过AIDL实现IPC
 * @author sywyg
 * @since 2015.7.16
 */
public class AIDLService extends Service {
    private final String TAG = "result";
    public AIDLService() {
    }

    /**
     * 定义一个匿名内部类实现aidl接口,需要继承IRemoteService.Stub类
     * 在.aidl文件中声明的方法需要在这里实现具体功能
     */
    private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
        public int getPid(){
            Log.d(TAG,"getPid is executed ...");
            return android.os.Process.myPid();
        }
        public void basicTypes(int anInt, long aLong, boolean aBoolean,
                                   float aFloat, double aDouble, String aString) {
            Log.d(TAG,"basicTypes is executed ...");
        }
    };

    @Override
    public IBinder onBind(Intent intent) {

        return mBinder;
    }
}

那么,当客户端调用bindService()连接service时,客户端回调onServiceConnected()方法接收mBinder实例(service的onBinder()方法返回的)。

客户端必须也能够获得该接口类,因此当客户端和service在不同的应用程序时,客户端应用程序必须复制一份.aidl文件,这样才能获得AIDL中的方法。

当客户在onServiceConnected()方法中接收IBinder对象时,必须通过调用YourServiceInterface.Stub.asInterface(service)转换为YourServiceInterface类型。如下:


IRemoteService mIRemoteService;
  private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG,"绑定成功");
            // Following the example above for an AIDL interface,
            // this gets an instance of the IRemoteService, which we can use to call on the service
            // 还是接着上面的例子,通过这种方式获得IRemoteService的一个实例,
            // 这样,我们可以在客户端进行处理了。
            mIRemoteService = IRemoteService.Stub.asInterface(service);

            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mBound = false;
        }
    };

通过IPC传递对象

我们可以实现通过IPC把对象从一个进程传递到另一个进程中。但是,我们必须要确保在另一个进程中可以获得该对象(即需要该类的代码),并且该类需要支持Parcelable接口。必须要支持Parcelable,这样系统才能将对象分解为基本数据类型(能够跨进程marshalled)。

注意:Parcelable是一个接口,实现该接口的类实例能够保存在Parcel中并从中恢复。该类中必须有一个名叫CREATOR的静态成员变量,该成员是Parcelable.Creator的一个实现实例。

为了创建支持Parcelable协议的类,必须完成以下几点:

  1. 该类必须实现Parcelable接口;
  2. 实现writeToParcel()和方法,记录当前对象的状态(成员变量等),并用Parcel保存。还要实现describeContents(),一般返回0;
  3. 添加静态成员变量CREAROR,该成员是Parcelable.Creator的一个实现实例;
  4. 最后创建一个.aidl文件声明该parcelable类(例如下面的Rect.aidl文件)。若你正在进行自定义生成过程,不要添加.aidl文件,这是因为,它类似C语言的头文件,不会进行编译的。???茫然

AIDL通过上述办法产生marshall和unmarshall对象。

下面是一个实现Parcelable接口的类Rect,首先要有Rect.aidl文件:


package android.graphics;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
// 声明Rect,AIDL好找到并确认它实现了parcelable协议
parcelable Rect;

下面是Rect类:


import android.os.Parcel;
import android.os.Parcelable;

public final class Rect implements Parcelable {
    public int left;
    public int top;
    public int right;
    public int bottom;

    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();
    }
}

Parcel同样可以写其它类型的数据。

警告:不要忘记从另一个进程中获取数据时的安全问题。上面的例子,Rect获取四个数,但是这取决于你获得多少数据(读写顺序要一致)。

调用IPC方法

客户端必须完成以下步骤才能实现调用远程接口:

  1. 在项目中包含.aidl文件。
  2. 声明一个IBinder对象实例(基于AIDL产生的)。
  3. 实现ServiceConnection。
  4. 调用Context.bindService(),并传递ServiceConnection的实现。
  5. onServiceConnected()实现中,我们可以接受IBinder的一个实例(名为service)。调用asInterface()转换成接口实例。
  6. 调用在接口中定义的方法。必须要捕获DeadObjectionException异常(当连接断开时),这是调用远程方法的唯一异常。
  7. 调用Context.unBindService()解除连接。

调用IPC service注意事项:

  • 对象是跨进程计数的引用类型,上一章讲的一样,可能会造成内存泄漏。
  • 能够发送匿名对象作为方法参数。
时间: 2024-11-03 05:43:57

解读Android之Service(3)AIDL的相关文章

android 远程Service以及AIDL的跨进程通信

在Android中,Service是运行在主线程中的,如果在Service中处理一些耗时的操作,就会导致程序出现ANR. 但如果将本地的Service转换成一个远程的Service,就不会出现这样的问题了. 转换成远程Service非常简单,只需要在注册Service的时候将他的android:process的属性制定成 :remote就可以了. 重新运行项目,你会发现,不会出现ANR了. 为什么将MyService转换成远程Service后就不会导致程序ANR了呢?这是由于,使用了远程Serv

解读Android之Service(2)Bound Service

翻译自android官方文档,并根据自己测试形成下面的内容. 这是service的第二部分bound service.若第一部分没看的,请参考:上一篇. bound service 相当于客户-服务器接口中的服务器.bound service 允许其它组件(除了broadcast receiver)绑定该service,然后进一步操作:发送请求,接收响应,甚至IPC.bound service 只有在其他组件绑定它时才处于存活状态,且会受到绑定它的组件影响. 下面将具体介绍如何创建bound s

Android service通过aidl 回调客户端 daemon

前端时间项目运用到AIDL,关于AIDL客户端以及AIDL服务端网络上没有一个比较完备的Demo. 而参考Demo无疑是一个比较快速的学习方法.因此,我写了一个Demo. 供大家参考,也非常欢迎大家对其中写的不好的地方进行指正. 好了,首先简述下基本功能: 在AIDL 客户端三个EditText中输入三个值,点击提交按钮,将这三个值传入到AIDL服务端进行处理. 服务端处理后会执行客户端的回调函数:在AIDL客户端界面进行刷新,并显示一个toast. 接下来看看代码结构: 需要注意的是,两个工程

android 四大组件之Service(10) AIDL android interface definition language

Android Interface Definition Language (AIDL) 使用AIDL建立一个邻接的service需要遵循下面的步骤 1.建立.aidl文件 这个文件使用方法签名定义了语言接口 2.实现这个接口 Android SDk工具基于你的.aidl文件使用java语言生成一个接口 这个接口有一个内部抽象类,叫做Stub,它是继承Binder并且实现你AIDL接口的 你必须继承这个Stub类并且实现这些方法 3.暴露这个接口给客户端 实现一个service并且覆盖onBin

android学习之remote service 的aidl详解

写在前面的话: 关于remote service中使用aidl来实现跨进程,多线程通信,我是参考了三篇文章,大概把这个弄明白了. (1)android 官方关于aidl的说明文档 docs/guide/components/aidl.html (2)Android学习笔记23服务Service之AIDL和远程服务实现进程通信以及进程间传递自定义类型参数 http://blog.csdn.net/honeybaby201314/article/details/19989455 (3) Androi

Android Bound Service(二) ----- Using AIDL

refs: http://developer.android.com/guide/components/aidl.html AIDL(Android Interface Definition Language) 就像其它接口定义语言一样.它使你可以定义服务端及客户端程序的接口,以达到跨进程沟通( IPC )的目的. 注意: 1.在多进程多线程的情况下,我们才使用 AIDL 2.单一进程时,使用实现 Binder 类的方式定义接口 3.如果只有跨进程,但不需処理多线程的情况,请使用 Messeng

Android service binder aidl 关系

/********************************************************************************** * Android service binder aidl 关系 * 声明: * 最近一直被Android中的service.binder.aidl这三者之间的关系给搞得有点难受, * 于是就自己花了点时间,将他们之间的关系给画出来,这样思维上就清晰多了,也方便 * 和朋友沟通,减少沟通成本. * * 2016-1-10 晴 深圳

Android studio 中创建AIDL Service

  1.概述  AIDL在android系统中的作用 AIDL,Android Interface definition language的缩写,它是一种android内部进程通信接口的描述语言,通过它我们可以定义进程间的通信接口.最近看了下AIDL在Android系统中的用法,在网上看到很多初学的朋友不太明白AIDL的实际作用,android提供了很多进程间通信的组件,像action.broadcast.contentprovide都可以实现进程间的通信,为什么还要用AIDL这个东西呢?我在a

【翻译】Android Interface Definition Language (AIDL)

参考地址:https://developer.android.com/guide/components/aidl.html Android Interface Definition Language (AIDL) AIDL (Android Interface Definition Language) is similar to other IDLs you might have worked with. It allows you to define the programming inter