Android官方文档笔记:AIDL

AIDL(Android接口定义语言)与你可能用过的其他IDL类似。它允许你定义客户端和服务端通过使用进程间通信(IPC)进行通信的编程接口。在Android里,一个进程是无法正常访问另一个进程的内存的。所以说,它们需要被分解成能被操作系统理解的原语,并且让他们为你跨越边界。编写这些代码很繁琐,所以Android使用AIDL为你处理这些。

使用AIDL的唯一原因是你想允许从不同的客户端的client都能访问你的service来进行IPC且你的service能处理多线程。如果你不用处理多个应用间的IPC,那么你应该通过继承Binder来创建你的接口,如果你想要IPC但是不用处理多线程,你可以用Meseenger来实现你的接口。

在开始设计您的AIDL接口之前,请注意对AIDL接口的调用是直接函数调用。 你不应该对发生调用的线程做出假设。根据调用是来自本地进程中的线程还是远程进程,发生的情况会有所不同。

从本地进程中发起的调用是在调用发起的线程中执行的。如果是UI线程,该线程继续在AIDL接口中执行。如果是其他线程,该线程在service中执行你的代码。如果只有本地线程能访问到该service,那么你完全可以控制哪个线程来执行它(但是在这种情况下,你完全可以不用AIDL,而是使用实现Binder来创建接口)。

来自远程进程的调用将从你自己的进程中维护的线程池分发。你必须为从未知线程中同一时间传入的大量请求做好准备。换句话说,你的AIDL接口的实现必须是完全线程安全的。

oneway关键字改变了远程调用的行为。当使用这个的时候,一个远程的调用不会阻塞,而是简单的发送运输数据后立刻返回。接口的实现最终将接受到远程调用认为是一个来自Binder线程池的常规调用。如果oneway被用在了本地调用,那么不会造成什么影响,调用仍然是异步的。

定义一个AIDL接口

你必须通过一个.aidl文件、使用java编程语法来定义你的AIDL接口,然后将它保存在持有对应service和其他与该service绑定的应用的源码中(在src/目录下)。

当你编译一个含有.aidl文件的应用的时候,Android SDK工具基于.aidl文件生成IBinder接口,然后将其保存在工程的gen/目录下面。service必须要正确的实现IBinder接口。然后client应用就可以绑定到这个service并调用IBinder中的方法来进行IPC。

按照以下步骤来创建使用AIDL的bound service

  1. 创建.aidl文件
    该文件使用方法签名来定义编程接口。
  2. 继承接口
    Android SDK工具会基于你的.aidl文件生成用java编程语言编写的接口。该接口有一个名叫Stub的内部抽象类,该类继承了IBinder且实现了你AIDL接口中的方法。你必须继承Stub来实现这些方法。
  3. 暴露接口给客户端
    实现一个service并重写onBind方法来返回Stub类的实现。
    注意:在你发布之后,你对你的AIDL接口做的任何变动都必须要向后兼容,以此来避免其他使用你的service的应用的崩溃。这是因为你的.aidl文件必须要拷贝到其他的应用中来让它们可以访问你的service的接口,你必须要给原始的接口提供支持。

1.创建.aidl文件

AIDL使用简单的语法来让你声明一个接口,该接口中含有一个或多个可以携带参数且有返回值的方法。参数和返回值可以有很多类型,甚至是AIDL生成的接口。
你必须使用java编程语言来构建一个.aidl文件,每个.aidl文件必须定义单个的接口,只需要接口的声明和方法签名。

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

  • 所有的java语言中的基本类型(int ,long,cahr,boolean等)
  • String
  • CharSequence
  • List
    List中所有元素的类型都必须是当前这个列表支持的类型,或者其余AIDL生成的接口以及你自己声明的parcelables。一个List可以随意使用泛型(例如List<String>)。另外一 端接收到的真正类型是ArrayList,尽管方法使用List接口生成的。
  • Map
    和List的说明差不多。另外一端接收到的真正类型是HashMap。

你必须使用import语句来导入所有没有出现在上面列表中的类型,即使它和你的接口在同一个包里。

在定义你的接口的时候,需要注意:

  • 方法可以有0或多个参数,可以有返回值或者void
  • 所有非基本参数都需要一个方向标签来指示数据传送的方式。in,out或者inout中的一个
  • 基本参数默认是in且不能是其他。
    注意:您应该将方向限制在真正需要的地方,因为编组参数很昂贵。
  • 包含在.aidl文件中的所有代码注释都包含在生成的IBinder接口中(import和package之前的注释除外)。
  • 仅支持方法,你不能在AIDL中公开字段。

下面是一个.aidl文件的例子

// IRemoteService.aidl
package com.example.android;
// Declare any non-default types here with import statements
/** Example service interface */
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);
}

将.aidl文件保存在你项目的src/目录下,当你构建你的应用的时候,SDK工具会在你工程的gen/目录下面生成IBinder接口。生成的文件的名字和.aidl文件的名字一样,不过是.java结尾的。(例如,IRemoteService.aidl生成IRemoteService.java

2.实现接口

当你编译你的应用的时候,Android SDK工具会生成一个和.aidl同名的.java接口。该生成出来的接口含有一个名为Stub的抽象内部类,实现了父类接口且声明了.aidl文件中的所有接口。

说明:Stub也定义了一些有用的方法,尤其是asInterface,该方法接受一个IBinder(一般是传给client的onServiceConneted回调方法的那一个)并返回一个根接口的实例。

要实现.aidl生成的接口,请扩展生成的Binder接口(例如YourInterface.Stub)并实现从.aidl文件继承的方法。
下面是一个用匿名实例的方式实现了一个名为IRemoteService(上面的例子中.aidl定义的)接口。

private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
    public int getPid(){
        return Process.myPid();
    }
    public void basicTypes(int anInt, long aLong, boolean aBoolean,
        float aFloat, double aDouble, String aString) {
        // Does nothing
    }
};

现在,mBinder是Stub的一个实例,定义了Service的RPC接口。下一步,将这个实例暴露给client以便它们能和service交互。

下面是你在实现你的AIDL接口的时候应该注意的几条规则:

  • 接收到的调用不能保证在主线程中执行,所以在一开始你就要考虑多线程,将你的service构建成线程安全的。
  • 默认情况下,RPC的调用是同步的。如果你知道你的service需要花一些时间去完成一个请求,那么你不应该在主线程中调用它,这会挂起当前应用(Android可能会显示一个ANR弹窗),你应该从另外一个线程中调用它。
  • 你抛出的任何异常都不会返回给调用者

3.暴露接口给client

如果你已经为你的service实现了相关的接口,那么你需要将它暴露给client这样它们就可以与之绑定了。要暴露你的service的接口,要继承service并实现onBind方法来返回一个实例,该实例是对生成的Stub的实现(上面讨论过的)。下面是一个service暴露IRmoteService的例子:

public class RemoteService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
    }
    @Override
    public IBinder onBind(Intent intent) {
        // Return the interface
        return mBinder;
    }
    private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
        public int getPid(){
            return Process.myPid();
        }
        public void basicTypes(int anInt, long aLong, boolean aBoolean,
            float aFloat, double aDouble, String aString) {
            // Does nothing
        }
    };
}

现在,一个client(例如activity)就可以调用bindService来与这个service绑定。client的onServiceConnected回调方法将会受到从service的onBind方法返回的mBinder实例。
client必须可以访问对应的接口,所以如果client和service在不同的应用中,那么client应用也要将一份.adil文件的拷贝放到它的src/目录下面(这会生成android.os.Binder-为客户提供对AIDL方法的访问)

当客户端在onServiceConnected()回调中接收到IBinder时,它必须调用YourServiceInterface.Stub.asInterface(service)将返回的参数转换为YourServiceInterface类型。例如:

IRemoteService mIRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Following the example above for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service
        mIRemoteService = IRemoteService.Stub.asInterface(service);
    }
    // Called when the connection with the service disconnects unexpectedly
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "Service has unexpectedly disconnected");
        mIRemoteService = null;
    }
};

通过IPC传递对象

如果你需要将一个对象通过IPC接口从一个进程传送到另外一个进程,你是可以做到的。然而,你必须确保你类的代码能用于IPC通道的另一端且你的类必须支持Parcelable接口。支持该接口是非常重要的,因为这样Android系统就可以将对象分解为可以跨进程整理的基本数据。

要创建支持Parcelable协议的类,您必须执行以下操作:

  • 1.让你的类实现Parcelable
  • 2.实现writeToParcel,可以将当前对象的状态写进Parcel。
  • 3.为你的类添加一个名为CREATOR的静态字段,该字段是一个实现了Parcelable.Creator接口的对象。
  • 4.最后,创建一个.adil文件来声明你的parcelable类(就像下面展示的Rect.aidl文件)。如果您正在使用自定义构建过程,请不要将.aidl文件添加到您的构建中。与C语言中的头文件类似,该.aidl文件未被编译。

AIDL在它生成的代码中使用这些方法和字段来编组和解组对象。
例如,下面是一个Rect.aidl文件,用于创建可以打包的Rect类:

package android.graphics;
// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;
下面的例子是Rect类如何实现Parcelabel协议:
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();
    }
}

调用一个IPC方法

要调用AIDL定义的远程接口,调用类要做到以下几步:

1.在src/目录下包含相应的.aidl文件
2.声明一个IBinder接口的实例(基于AIDL生成的)
3.实现ServiceConnection
4.调用Context.bindService,传入你的ServiceConnection的实现。
5.在你实现的onServiceConnected中,你会收到一个IBinder实例。调用YourInterfaceName.Stub.asInterface((IBinder)service)将返回的参数转型为YourInterface类型。
6.调用你在你的接口上定义的方法。你应该捕获DeadObjectException异常,它会在连接断开的时候抛出,这也是远程方法唯一抛出的异常。
7.要断开连接,请使用接口实例调用Context.unbindService()

关于调用IPC服务的几点提示:

  • 对象是跨进程引用计数的
  • 您可以将匿名对象作为方法参数发送。

下面是一个例子:

public static class Binding extends Activity {
    /** The primary interface we will be calling on the service. */
    IRemoteService mService = null;
    /** Another interface we use on the service. */
    ISecondary mSecondaryService = null;
    Button mKillButton;
    TextView mCallbackText;
    private boolean mIsBound;
    /**
     * Standard initialization of this activity.  Set up the UI, then wait
     * for the user to poke it before doing anything.
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.remote_service_binding);
        // Watch for button clicks.
        Button button = (Button)findViewById(R.id.bind);
        button.setOnClickListener(mBindListener);
        button = (Button)findViewById(R.id.unbind);
        button.setOnClickListener(mUnbindListener);
        mKillButton = (Button)findViewById(R.id.kill);
        mKillButton.setOnClickListener(mKillListener);
        mKillButton.setEnabled(false);
        mCallbackText = (TextView)findViewById(R.id.callback);
        mCallbackText.setText("Not attached.");
    }
    /**
     * Class for interacting with the main interface of the service.
     */
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // This is called when the connection with the service has been
            // established, giving us the service object we can use to
            // interact with the service.  We are communicating with our
            // service through an IDL interface, so get a client-side
            // representation of that from the raw service object.
            mService = IRemoteService.Stub.asInterface(service);
            mKillButton.setEnabled(true);
            mCallbackText.setText("Attached.");
            // We want to monitor the service for as long as we are
            // connected to it.
            try {
                mService.registerCallback(mCallback);
            } catch (RemoteException e) {
                // In this case the service has crashed before we could even
                // do anything with it; we can count on soon being
                // disconnected (and then reconnected if it can be restarted)
                // so there is no need to do anything here.
            }
            // As part of the sample, tell the user what happened.
            Toast.makeText(Binding.this, R.string.remote_service_connected,
                    Toast.LENGTH_SHORT).show();
        }
        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service has been
            // unexpectedly disconnected -- that is, its process crashed.
            mService = null;
            mKillButton.setEnabled(false);
            mCallbackText.setText("Disconnected.");
            // As part of the sample, tell the user what happened.
            Toast.makeText(Binding.this, R.string.remote_service_disconnected,
                    Toast.LENGTH_SHORT).show();
        }
    };
    /**
     * Class for interacting with the secondary interface of the service.
     */
    private ServiceConnection mSecondaryConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // Connecting to a secondary interface is the same as any
            // other interface.
            mSecondaryService = ISecondary.Stub.asInterface(service);
            mKillButton.setEnabled(true);
        }
        public void onServiceDisconnected(ComponentName className) {
            mSecondaryService = null;
            mKillButton.setEnabled(false);
        }
    };
    private OnClickListener mBindListener = new OnClickListener() {
        public void onClick(View v) {
            // Establish a couple connections with the service, binding
            // by interface names.  This allows other applications to be
            // installed that replace the remote service by implementing
            // the same interface.
            Intent intent = new Intent(Binding.this, RemoteService.class);
            intent.setAction(IRemoteService.class.getName());
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
            intent.setAction(ISecondary.class.getName());
            bindService(intent, mSecondaryConnection, Context.BIND_AUTO_CREATE);
            mIsBound = true;
            mCallbackText.setText("Binding.");
        }
    };
    private OnClickListener mUnbindListener = new OnClickListener() {
        public void onClick(View v) {
            if (mIsBound) {
                // If we have received the service, and hence registered with
                // it, then now is the time to unregister.
                if (mService != null) {
                    try {
                        mService.unregisterCallback(mCallback);
                    } catch (RemoteException e) {
                        // There is nothing special we need to do if the service
                        // has crashed.
                    }
                }
                // Detach our existing connection.
                unbindService(mConnection);
                unbindService(mSecondaryConnection);
                mKillButton.setEnabled(false);
                mIsBound = false;
                mCallbackText.setText("Unbinding.");
            }
        }
    };
    private OnClickListener mKillListener = new OnClickListener() {
        public void onClick(View v) {
            // To kill the process hosting our service, we need to know its
            // PID.  Conveniently our service has a call that will return
            // to us that information.
            if (mSecondaryService != null) {
                try {
                    int pid = mSecondaryService.getPid();
                    // Note that, though this API allows us to request to
                    // kill any process based on its PID, the kernel will
                    // still impose standard restrictions on which PIDs you
                    // are actually able to kill.  Typically this means only
                    // the process running your application and any additional
                    // processes created by that app as shown here; packages
                    // sharing a common UID will also be able to kill each
                    // other's processes.
                    Process.killProcess(pid);
                    mCallbackText.setText("Killed service process.");
                } catch (RemoteException ex) {
                    // Recover gracefully from the process hosting the
                    // server dying.
                    // Just for purposes of the sample, put up a notification.
                    Toast.makeText(Binding.this,
                            R.string.remote_call_failed,
                            Toast.LENGTH_SHORT).show();
                }
            }
        }
    };
    // ----------------------------------------------------------------------
    // Code showing how to deal with callbacks.
    // ----------------------------------------------------------------------
    /**
     * This implementation is used to receive callbacks from the remote
     * service.
     */
    private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
        /**
         * This is called by the remote service regularly to tell us about
         * new values.  Note that IPC calls are dispatched through a thread
         * pool running in each process, so the code executing here will
         * NOT be running in our main thread like most other things -- so,
         * to update the UI, we need to use a Handler to hop over there.
         */
        public void valueChanged(int value) {
            mHandler.sendMessage(mHandler.obtainMessage(BUMP_MSG, value, 0));
        }
    };
    private static final int BUMP_MSG = 1;
    private Handler mHandler = new Handler() {
        @Override public void handleMessage(Message msg) {
            switch (msg.what) {
                case BUMP_MSG:
                    mCallbackText.setText("Received from service: " + msg.arg1);
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    };
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352