android的aidl机制案例
这段时间在学习android,结果碰到了教学视频中经常提到和使用aidl,随后在网上找了一些资料,写了一些案例来分析分析,假如我写的和解释的有些偏差,希望能被大家指点出来纠正自己的拙见。<p><span style="font-size:12px;">AIDL是一个接口描述文件,用于实现Android平台上面的RPC,aapt在编译的时候会自动根据规则生成用于IPC的接口和对象,而作为使用者只需要:1.在服务端Service实现接口;2. 在客户端bindService,onServiceConnected时获取接口对象。这里的接口都是AIDL中描述的接口,其他的细节则在由AIDL生成的同名源码文件中</span></p><p><span style="font-size:12px;">我所实现的案例是从服务端获取一个书本的信息:</span></p>
package com.edify.bluenfcserver.myservice; import java.util.Date; import android.annotation.SuppressLint; import android.os.Parcel; import android.os.Parcelable; /** * @Description 写一个实体类步骤: * 1. 实现Pracelable接口 * 2. 需要定义一个Parcelable.Creator CREATOR对象 * 3. 同时要建立一个aidl文件 * @Author monkey * @Date 2014年9月4日下午5:22:02 */ @SuppressLint("ParcelCreator") public class BookInfo implements Parcelable { private int id; private String name; private String author; private float price; private Date product; public BookInfo(Parcel in) { } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getProduct() { return product; } public void setProduct(Date product) { this.product = product; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public float getPrice() { return price; } public void setPrice(float price) { this.price = price; } public BookInfo(int id, String name, String author, float price, Date product) { super(); this.id = id; this.name = name; this.author = author; this.price = price; this.product = product; } @Override public int describeContents() { return 0; } /** * 将对象序列号 * dest 就是对象即将写入的目的对象 * flags 有关对象序列号的方式的标识 * 这里要注意,写入的顺序要和在createFromParcel方法中读出的顺序完全相同。例如这里先写入的为name, * 那么在createFromParcel就要先读name */ @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(id); dest.writeString(name); dest.writeString(author); dest.writeFloat(price); dest.writeValue(product);// 写一个date类型的数据 } /** * 在想要进行序列号传递的实体类内部一定要声明该常量。常量名只能是CREATOR,类型也必须是 * Parcelable.Creator<T> */ public static final Parcelable.Creator<BookInfo> CREATOR = new Parcelable.Creator<BookInfo>() { /*** * 根据序列号的Parcel对象,反序列号为原本的实体对象 * 读出顺序要和writeToParcel的写入顺序相同 */ public BookInfo createFromParcel(Parcel in) { int id = in.readInt(); String name = in.readString(); String author = in.readString(); float price = in.readFloat(); Date product = (Date) in.readValue(this.getClass().getClassLoader()); return new BookInfo(id, name, author, price, product); } public BookInfo[] newArray(int size) { return new BookInfo[size]; } }; }
客户端的代码:
package com.edify.bluenfc; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; import com.edify.bluenfcserver.Beauty; import com.edify.bluenfcserver.RemoteBeauty; import com.edify.bluenfcserver.myservice.BookInfo; import com.edify.bluenfcserver.myservice.MyRemoteBookInfo; import com.edify.bluenfcserver.myservice.MyRemoteBookInfo.Stub; public class MainActivity extends Activity implements OnClickListener { TextView textView; Button button; String actionName = "com.edify.bluenfcserver.remoteservice"; String actionName2 = "com.edify.bluenfcserver.myservice.myRemoteService"; RemoteBeauty remoteBeauty; private MyRemoteBookInfo myRemoteBookInfo; private TextView textView2; private View button2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView) findViewById(R.id.textView1); textView2 = (TextView) findViewById(R.id.textView2); button = (Button) findViewById(R.id.button1); button2 = (Button) findViewById(R.id.button2); button.setOnClickListener(this); button2.setOnClickListener(this); } // service.queryLocalInterface("com.edify.bluenfcserver.RemoteBeauty"); // getPackageManager():提供了方法可以对清单文件中的配置进行查询 private class MyServiceConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.i("通知", "链接成功!"); remoteBeauty = RemoteBeauty.Stub.asInterface(service); try { Beauty beauty = remoteBeauty.getBeauty(); textView.setText("美女 姓名:" + beauty.getName() + " 年龄:" + beauty.getAge() + " 性别:" + beauty.getSex()); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } } MyServiceConnection connection = new MyServiceConnection(); @Override public void onClick(View v) { switch (v.getId()) { case R.id.button1: Intent intent = new Intent(actionName); bindService(intent, connection, Context.BIND_AUTO_CREATE); break; case R.id.button2: Intent intent2 = new Intent(actionName2); // 建立连接:第一步,向服务端发请求 bindService(intent2, new MyBookInfoServiceConnection(), Context.BIND_AUTO_CREATE); break; } } // 建立与服务器链接对象,桥梁 private class MyBookInfoServiceConnection implements ServiceConnection { // 建立连接:第三步 @Override public void onServiceConnected(ComponentName name, IBinder service) { // 判断是否是同一个进程内的进行通信,假如是那么返回的是同一个对象,如果不是那么返回的是代理对象 /** * 假如是一个进程内通信,就没有必要进行aidl的机制来处理了 * 这实际上就是判断此通讯是在同一进程中,还是跨进程,因为同一进程传回的对象是Service * .onBind()所返回的对象,而此对象必然实现了接口 * (要不然搞毛啊!)。所以,如果仅是在同一个进程之中,不会走Binder进程IPC * ,而是直接返回Service所提供的对象,直接调用其方法,因此也就不会有对象必须Parcelable的限制! * 也就是说,当在同一个进程中时AIDL实际上变成了这样的: * 也就是说它是直接返回了Service.onBind()的对象,这其实跟前面提到的第一种方式 * :直接实现Binder对象的方式是一样一样的 * ,其他的代码全是多余的。因此,如前面建议的,如果仅是在同一个进程中,就直接使用Binder就好了,没有必要创建AIDL文件。 * 当在不同的进程中时 * ,客户端Stub.asInterface会返回一个Stub.Proxy对象,调用其上的print方法。而服务端仅会执行Stub * .onTransact()方法,然后就调到Service的print方法了。 * 当跨进程的时候,就要使用Binder对象的IPC相关的方法和机制 * 。客户端需要实现Binder.transact()方法来执行远程的一个方法 * ,这是给客户端来使用;而服务端则需要实现Binder.onTransact * ()来响应客户端所请求的方法。对于上层使用者来说,用transact * ()把函数的信息(参数,标识和开关)发送出去,剩下的就是Binder的工作了 * ,内部还有大量的细节,但是最终会调用到服务端Binder的onTransact * ()方法,这里识别出函数的标识,然后调用具体的实现,再传回返回值,这样一个IPC的函数调用就完成了。 * 当跨进程时,仅以下代码是各自所必须的,去掉了无关代码: * * 其实AIDL的作用就是对Binder的二个方法:Binder.transact()和Binder.onTransact * ()进行封装, * 以供Client端和Server端进行使用。因为实现transact()和onTransact()方法的方式基本上是相同的 * ,所以就可以用模板来生成具体的代码 * 。理论上讲只需要为Client端生成transact()相关代码,为服务端生成onTransact * ()代码即可,但因为工具无法准确的确定某一个应用到底是Client端还是Server端 * ,所以它就生成所有的代码,放在一个文件中。这就是你看到的自动生成的文件。 * 还需要注意的一点是Client端的Proxy是组合Binder对象 * ,调用其transact()方法;而服务端必须继承Binder对象 * ,覆写onTransact()方法。为虾米呢?因为Client是主动发起IPC函数Call * ,所以它可以直接调用Binder的方法来进行IPC。而Server是被动的,是要接收进来的IPC * call,但Service自己无法得知啥时候Call会来 * ,因此必须实现回调(onTransact())给Binder,以让Binder在有IPC Call进来的时候告诉Service。 * 原理和内幕 AIDL的角色是实现Android平台上面的RPC(Remote Procedure * Call)也即远程例程调用。RPC是IPC中的一种,但是它是以调用在本地或者另一个进程,甚至是另一个主机上的方法的机制。 * RPC的目的就是可以让程序不用担心方法具体是在哪个进程里面或者哪以机器上面 * ,就像正常的本地方法那样去调用即可,RPC机制会处理所有的具体细节。RPC一般用IDL(Interface Definition * Language)来描述,实现则要看具体的平台和语言。可以参考Wikipedia来看RPC 和IDL 的更多信息。 * AIDL提供Android平台的RPC的支持:开发者仅需要要定义AIDL,做一些相关的适配工作,然后就可以使用这些方法了, * 不用具体关心接口描述的方法空究竟是在同一个进程中还是在其他的进程中。这些RPC实现的细节由Binder和系统来处理。 */ myRemoteBookInfo = MyRemoteBookInfo.Stub.asInterface(service); try { // 建立连接:第四步到服务端请求数据,@Override public BookInfo // getBookInfo(java.lang.String tagName) // 调用了这个方法去请求数据:mRemote.transact(Stub.TRANSACTION_getBookInfo, // _data, _reply, 0); BookInfo bookInfo = myRemoteBookInfo.getBookInfo("我是一个tagName"); textView2.setText(bookInfo.toString()); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } } }
服务端代码:
package com.edify.bluenfcserver.myservice; import java.util.Date; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; public class MyRemoteService extends Service { //建立连接:第二步,返回一个该提供给客户端操作服务端的对象 @Override public IBinder onBind(Intent intent) { return stub; } /** * 实例化一个对象,该对象就是提供给客户端的对象 */ private MyRemoteBookInfo.Stub stub = new MyRemoteBookInfo.Stub() { /** *实现接口未实现的方法 */ //客户端请求是通过这个方法来判断具体请求那个操作@Override public boolean onTransact(int code, android.os.Parcel data, an //建立连接:第五步;通过调用该方法回去服务器端的数据信息,操作 @Override public BookInfo getBookInfo(String tagName) throws RemoteException { return getBookInfoByTagName(tagName); } }; /** * @Description 服务的自身的方法 * @Author monkey * @param tagName * @return * @Return BookInfo */ public BookInfo getBookInfoByTagName(String tagName){ Log.i("getBookInfoByTagName--------------", tagName); if(tagName != null){ return new BookInfo(1, "尽忠报国", "金庸", 100f, new Date()); }else{ return new BookInfo(1, "水浒传", "未知", 200f, new Date()); } } }
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。