AIDL

来源:互联网 发布:oracle数据库免费下载 编辑:程序博客网 时间:2024/05/14 17:58
Android翻译:Android interface definition language(aidl)

原文:http://www.cnblogs.com/hibraincol/archive/2011/09/06/2169325.html

AIDL(Android Interface Definition Language)  IPC机制是面向对象的,轻量级的。通过AIDL定义的接口可以实现服务器端与客户端的IPC通信。在Android上,一个进程不能简单的像访问本进程内存一样访问其他进程的内存。所以,进程间想要对话,需要将对象拆解为操作系统可以理解的基本数据单元,并且有序的通过进程边界。通过代码来实现这个数据传输过程是冗长乏味的,所幸的是android提供了AIDL工具来帮我们完成了此项工作。

注意:仅仅在你需要A应用程序的客户端访问B应用程序的服务器端来实现IPC通信,并且在服务器端需要处理多线程(客户端)访问的情况下使用AIDL。如果不需要使用到进程间的IPC通信,那么通过Binder接口实现将更为合适,如果需要实现进程间的IPC通信,但不需要处理多线程(多客户端),通过Messager接口来实现将更为合适。不管怎样,在使用AIDL之前,应先确保已理解了Bound Service。

AIDL接口的调用采用的是直接的函数调用方式,但你无法预知哪个进程(或线程)将调用该接口。同进程的线程调用和其他进程调用该接口之间是有所区别的:

  • 在同进程中调用AIDL接口,AIDL接口代码的执行将在调用该AIDL接口的线程中完成,如果在主UI线程中调用AIDL接口,那么AIDL接口代码的执行将会在这个主UI线程中完成。如果是其他线程,AIDL接口代码的执行将在service中完成。因此,如果仅仅是本进程中的线程访问该服务,你完全可以控制哪些线程将访问这个服务(但是如果是这样,那就完全没必要使用AIDL了,而采取Binder接口的方式更为合适)。
  • 远程进程(其他线程)调用AIDL接口时,将会在AIDL所属的进程的线程池中分派一个线程来执行该AIDL代码,所以编写AIDL时,你必须准备好可能有未知线程访问、同一时间可能有多个调用发生(多个线程的访问),所以ADIL接口的实现必须是线程安全的。
  • 可以用关键字oneway来标明远程调用的行为属性,如果使用了该关键字,那么远程调用将仅仅是调用所需的数据传输过来并立即返回,而不会等待结果的返回,也即是说不会阻塞远程线程的运行。AIDL接口将最终将获得一个从Binder线程池中产生的调用(和普通的远程调用类似)。如果关键字oneway在本地调用中被使用,将不会对函数调用有任何影响。

定义AIDL接口

AIDL接口使用后缀名位.aidl的文件来定义,.aidl文件使用java语法编写,并且将该.aidl文件保存在 src/目录下(无论是服务端还是客户端都得保存同样的一份拷贝,也就是说只要是需要使用到该AIDL接口的应用程序都得在其src目录下拥有一份.aidl文件的拷贝)。

编译时,Android sdk 工具将会为 src/目录下的.aidl文件在 gen/ 目录下产生一个IBinder接口。服务端必须相应的实现该IBinder接口。客户端可以绑定该服务、调用其中的方法实现IPC通信。

 

创建一个用AIDL实现的服务端,需要以下几个步骤: 
    1. 创建.aidl文件:

        该文件(YourInterface.aidl)定义了客户端可用的方法和数据的接口

    2. 实现这个接口:

        Android SDK将会根据你的.aidl文件产生AIDL接口。生成的接口包含一个名为Stub的抽象内部类,该类声明了所有.aidl中描述的方法,你必须在代码里继承该Stub类并且实现.aidl中定义的方法。

    3.向客户端公开服务端的接口:

        实现一个Service,并且在onBinder方法中返回第2步中实现的那个Stub类的子类(实现类)。

注意:

服务端AIDL的任何修改都必须的同步到所有的客户端,否则客户端调用服务端得接口可能会导致程序异常(因为此时客户端此时可能会调用到服务端已不再支持的接口)。

 

1. 创建.aidl文件

AIDL使用简单的语法来声明接口,描述其方法以及方法的参数和返回值。这些参数和返回值可以是任何类型,甚至是其他AIDL生成的接口。重要的是必须导入所有非内置类型,哪怕是这些类型是在与接口相同的包中。

默认的AIDL支持一下的数据类型(这些类型不需要通过import导入):

  • java语言的原始数据类型(包括 int, long, char, boolen 等等)
  • String
  • CharSequence:该类是被TextView和其他控件对象使用的字符序列
  • List:列表中的所有元素必须是在此列出的类型,包括其他AIDL生成的接口和可打包类型。List可以像一般的类(例如List<String>)那样使用,另一边接收的具体类一般是一个ArrayList,这些方法会使用List接口
  • Map:Map中的所有元素必须是在此列出的类型,包括其他AIDL生成的接口和可打包类型。一般的maps(例如Map<String,Integer>)不被支持,另一边接收的具体类一般是一个HashMap,这些方法会使用Map接口。

对于其他的类型,在aidl中必须使用import导入,即使该类型和aidl处于同一包内。

定义一个服务端接口时,注意一下几点:

  • 方法可以有0个或多个参数,可以使空返回值也可以返回所需的数据。
  • 所有非原始数据类型的参数必须指定参数方向(是传入参数,还是传出参数),传入参数使用in关键字标记,传出参数使用out,传入传出参数使用inout。如果没有显示的指定,那么将缺省使用in。
  • 在aidl文件中所有的注释都将会包含在生成的IBinder接口中(在Import和pacakge语句之上的注释除外)。
  • aidl中只支持成员方法,不支持成员变量。

提示:

限定参数的传输方向非常有必要,因为编组(序列化)参数的代价非常昂贵。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 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/目录下,当编译生成apk时,sdk 工具将会在 gen/ 目录下生成一个对应的IBiner接口的.java文件。

如果使用eclipse编写app,那么这个IBinder接口文件将会瞬间生成。如果不是使用eclipse,Ant 工具将会在下次编译你的app时,生成这个IBinder接口文件——所以当你编写.aidl文件之后应该立即使用 ant debug 编译你的app,这样你后续的代码就可以使用这个已生成的IBinder接口类了。

 

2. 实现接口

生成的接口包含一个名为Stub的抽象的内部类,该类声明了所有.aidl中描述的方法,

注意:

Stub还定义了少量的辅助方法,尤其是asInterface(),通过它或以获得IBinder(当applicationContext.bindService()成功调用时传递到客户端的onServiceConnected())并且返回用于调用IPC方法的接口实例,更多细节参见Calling an IPC Method

要实现自己的接口,就从YourInterface.Stub类继承,然后实现相关的方法(可以创建.aidl文件然后实现stub方法而不用在中间编译,Android编译过程会在.java文件之前处理.aidl文件)。

下面的例子实现了对IRemoteService接口的调用,这里使用了匿名对象。

1
2
3
4
5
6
7
8
9
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接口。下一步中该对象将会向客户端公开,这样客户端就可以通过该对象与该service进行交互了。

实现ADIL接口时需要注意一下几点:

  • 不能保证所有对aidl接口的调用都在主线程中执行,所以必须考虑多线程调用的情况,也就是必须考虑线程安全。
  • 默认IPC调用是同步的。如果已知IPC服务端会花费很多毫秒才能完成,那就不要在Activity或View线程中调用,否则会引起应用程序挂起(Android可能会显示“应用程序未响应”对话框),可以试着在独立的线程中调用。
  • 不会将异常返回给调用方

3. 向客户端暴露接口

在完成了接口的实现后需要向客户端暴露接口了,也就是发布服务,实现的方法是继承 Service,然后实现以Service.onBind(Intent)返回一个实现了接口的类对象。下面的代码表示了暴露IRemoteService接口给客户端的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class RemoteServiceextends 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
        }
    };
}

现在,如果客户端(比如一个Activity)调用bindService()来连接该服务端(RemoteService) ,客户端的onServiceConnected()回调函数将会获得从服务端(RemoteService )的onBind()返回的mBinder对象。

客户端同样得访问该接口类(这里指IRemoteService),所以,如果服务端和客户端不在同一进程(应用程序)中,那么客户端也必须在 src/ 目录下拥有和服务端同样的一份.aidl文件的拷贝(同样是指,包名、类名、内容完全一模一样),客户端将会通过这个.aidl文件生成android.os.Binder接口——以此来实现客户端访问AIDL中的方法。

当客户端在onServiceConnected()回调方法中获得IBinder对象后,必须通过调用YourServiceInterface.Stub.asInterface(service)将其转化成为YourServiceInterface类型。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
    }
};

 

更多的示例代码, 请看ApiDemos示例中的RemoteService.java.

 

4. 通过IPC传递对象

如果想要通过IPC接口将一个类从一个进程传递给另外一个进程,这个可以实现,但是必须得保证这个类在IPC两端的有效性,并且该类必须支持Parcelable接口。支持Parcelable接口非常重要,因为这允许Android系统将Object拆解为原始数据类型,这样才能达到跨进程封送(序列化发送)。

创建一个支持Parcelable协议的类,需要如下几个步骤:

    1. 使该类实现Parcelabel接口。

    2. 实现public void writeToParcel(Parcel out) 方法,以便可以将对象的当前状态写入包装对象中(Parcel)。

    3. 在类中增加一个Parcelable.Creator接口的静态对象CREATOR

    4. 最后,创建AIDL文件声明这个可打包的类(见下文的Rect.aidl),如果使用的是自定义的编译过程,那么不要编译此AIDL文件,它像C语言的头文件一样不需要编译。

 

AIDL会使用这些方法的成员序列化和反序列化对象。下面的代码演示如何使Rect类支持序列化(parcelable)

1
2
3
4
5
package android.graphics;
 
// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;

下面的例子演示了如何让Rect类实现Parcelable协议。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import android.os.Parcel;
import android.os.Parcelable;
 
public final class Rectimplements 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();
    }
}

这里Rect类序列化工作相当简单,对可打包的其他类型的数据可以参见Parcel类中的其他方法。

警告:不要忘了对从其他进程接收到的数据进行安全检查。在上面的例子中,rect要从数据包中读取4个数值,需要确认无论调用方想要做什么,这些数值都是在可接受的范围之内。想要了解更多的关于保持应用程序安全的内容,可参见 Security and Permissions

 

5. 调用ICP方法

这里给出调用远端AIDL接口的步骤:

    1. 在 src/ 目录下包含.adil文件。

    2. 声明一个IBinder接口(通过.aidl文件生成的)的实例。

    3. 实现ServiceConnection.

    4. 调用Context.bindService()绑定你的ServiceConnection实现类的对象(也就是远程服务端)。

    5. 在onServiceConnected()方法中会接收到IBinder对象(也就是服务端),调用YourInterfaceName.Stub.asInterface((IBinder)service)将返回值转换为YourInterface类型。

    6. 调用接口中定义的方法,并且应该总是捕获连接被打断时抛出的DeadObjectException异常,这是远端方法可能会抛出唯一异常。

    7. 调用Context.unbindService()方法断开连接。

这里有几个关于调用IPC服务的提示:

  • 对象是在进程间会进行引用计数
  • 可以发送匿名对象作为方法的参数

更多关于服务绑定的内容请看Bound Services相关文档。

下面是AIDL-created服务的演示代码,该代码是从ApiDemos工程中的Remote Service中提取的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
public static class Bindingextends 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.
            bindService(new Intent(IRemoteService.class.getName()),
                    mConnection, Context.BIND_AUTO_CREATE);
            bindService(new Intent(ISecondary.class.getName()),
                    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);
            }
        }
 
    };
}

 

注:由于译者水平有限,若发现有何处不妥,还望指出,不甚感激,共同进步!

 

原文地址:http://developer.android.com/guide/developing/tools/aidl.html


0 0
原创粉丝点击