前言
在Android四大组件之Service中我已经介绍了如何使用Service以及使用Binder和Service进行交互,下面我来介绍下使用AIDL机制与远程Service进行交互。
AIDL
AIDL (Android Interface Definition Language) 是一种IDL 语言,用于生成可以在Android设备上两个进程之间进行进程间通信(interprocess communication, IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数。
AIDL的作用
由于每个应用程序都运行在自己的进程空间,并且可以从应用程序UI运行另一个服务进程,而且经常会在不同的进程间传递对象。在Android平台,一个进程通常不能访问另一个进程的内存空间,所以要想对话,需要将对象分解成操作系统可以理解的基本单元,并且有序的通过进程边界。通过代码来实现这个数据传输过程是冗长乏味的,所以Android提供了AIDL工具来处理这项工作。
选择AIDL的使用场合
官方文档特别提醒我们何时使用AIDL是必要的:只有你允许客户端从不同的应用程序为了进程间的通信而去访问你的service,以及想在你的service处理多线程。
如果不需要进行不同应用程序间的并发通信(IPC),you should create your interface by implementing a Binder;或者你想进行IPC,但不需要处理多线程的,则implement your interface using a Messenger。无论如何,在使用AIDL前,必须要理解如何绑定service——bindService,可以查看Android四大组件之Service。
在设计AIDL接口前,要提醒的是,调用AIDL接口是直接的方法调用的,不是我们所想象的调用是发生在线程里。而调用(call)来自local进程或者remote进程。
AIDL的使用步骤
一、创建.aidl文件
在Android Studio中,在项目名上面右键New->Folder->AIDL Folder,创建存放aidl文件的文件夹,然后在文件夹上右键New->AIDL->AIDL File,这样我们就创建了一个.aidl文件(aidl文件文件名后缀为aidl)。
AIDL使用定义接口语法,描述其方法以及方法的参数和返回值。这些参数和返回值可以是任何类型,甚至是其他AIDL生成的接口。其中对于Java编程语言的基本数据类型 (int, long, char, boolean等),String和CharSequence,集合接口类型List和Map,不需要import 语句。而如果需要在AIDL中使用其他AIDL接口类型,需要import,即使是在相同包结构下。AIDL允许传递实现Parcelable接口的类,需要import。需要特别注意的是,对于非基本数据类型,也不是String和CharSequence类型的,需要有方向指示,包括in、out和inout,in表示由客户端设置,out表示由服务端设置,inout是两者均可设置。AIDL只支持接口方法,不能公开static变量。
例如下面这段代码就是IMyAidlInterface.aidl的内容,可以看到,类似于定义接口的语法,我们在里面声明一个add方法给其他进程调用。
package com.enjoywatch.taidl; interface IMyAidlInterface { void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString); int add(int num1,int num2); }
二、实现aidl接口
创建一个Service实现刚才定义的aidl接口,如下:
public class MyService extends Service { public MyService() { } /** * 当使用bindService绑定服务的时候,返回实现了AIDL接口的Binder对象 */ @Override public IBinder onBind(Intent intent) { return mBinder; } /** * 实现AIDL接口,IMyAidlInterface.Stub已经实现了android.os.Binder接口 * AIDL其实是基于Binder的 */ private IMyAidlInterface.Stub mBinder = new IMyAidlInterface.Stub() { @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { } @Override public int add(int num1, int num2) throws RemoteException { return num1 + num2; } }; }
Service的注册:
android:exported="true"属性代表允许其他进程访问此Service,必须为true,默认为false,如果为false,当其他进程绑定Service的时候,会报错。
<service android:name=".MyService" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="remote_service" /> </intent-filter> </service>
三、客户端调用服务端提供的Service方法
这里我新建了一个Module,名为aidlclient,将上面定义的AIDL文件夹以及里面的aidl文件一同拷贝过来,然后在需要调用远程Service的地方使用bindService绑定远程Service,获取返回的IMyAidlInterface对象,就可以调用其中的方法了。关于bindService的用法可以查看这篇博客的介绍Android四大组件之Service,当调用bindService以后,在ServiceConnection的onServiceConnected方法中即可获取绑定的Service的onBind方法返回的IBinder对象。
public class MainActivity extends Activity implements View.OnClickListener { private Button mAddBtn; private IMyAidlInterface mMyAidlInterface; private ServiceConnection mServiceConn = new ServiceConnection() { /** * 当绑定成功以后调用,其中service参数为上面定义的Service中的onBind方法返回的。 */ @Override public void onServiceConnected(ComponentName name, IBinder service) { mMyAidlInterface = IMyAidlInterface.Stub.asInterface(service); } /** * 断开连接的时候调用 */ @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); bindRemoteService(); } /** * 绑定远程Service,其中Service定义Action为remote_service */ private void bindRemoteService() { Intent intent = new Intent(); intent.setAction("remote_service"); bindService(intent, mServiceConn, Context.BIND_AUTO_CREATE); } private void initView() { mAddBtn = (Button) findViewById(R.id.add); mAddBtn.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.add: int result = 0; try { result = mMyAidlInterface.add(1, 1); Toast.makeText(MainActivity.this, "AIDL返回结果为:1+1=" + result, Toast.LENGTH_SHORT).show(); } catch (RemoteException e) { Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show(); } break; } } //当Activity销毁的时候解除绑定 @Override protected void onDestroy() { super.onDestroy(); unbindService(mServiceConn); } }
四、传递Parcelable对象
首先定义一个Person实现Parcelable接口
public class Person implements Parcelable { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.name); dest.writeInt(this.age); } protected Person(Parcel in) { this.name = in.readString(); this.age = in.readInt(); } public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() { @Override public Person createFromParcel(Parcel source) { return new Person(source); } @Override public Person[] newArray(int size) { return new Person[size]; } }; @Override public String toString() { return "{name:" + name + ",age:" + age + "}"; } }
然后修改AIDL文件,添加函数addPerson,注意,即使是在相同包结构下。AIDL允许传递实现Parcelable接口的类,需要import。
import的方法为:在AIDL文件夹下创建Person.aidl文件,里面的内容为:
// bean.aidl package com.enjoywatch.taidl; parcelable Person;
然后在AIDL文件中引入这个Person.aidl文件,注意:需要使用in out标记参数,原因请看最上面关于AIDL的介绍。
import com.enjoywatch.taidl.Person; interface IMyAidlInterface { void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString); /** * 两个数相加 */ int add(int num1,int num2); /** * 传递Parcelable对象,需要使用in out标记出入 */ List<Person> addPerson(in Person person); }
调用方法重复一二三步即可。
项目地址:https://github.com/CB2Git/AIDL
参考博客:51CTO推荐博客
参考视频 : 慕课网