AIDL
编写Aidl文件时,需要注意下面几点:
1.接口名和aidl文件名相同。
2.接口和方法前不用加访问权限修饰符public,private,protected等,也不能用final,static。
3.Aidl默认支持的类型包话java基本类型(int、long、boolean等)和(String、List、Map、 CharSequence),使用这些类型时不需要import声明。对于List和Map中的元素类型必须是Aidl支持的类型。如果使用自定义类型作 为参数或返回值,自定义类型必须实现Parcelable接口。
4.自定义类型和AIDL生成的其它接口类型在aidl描述文件中,应该显式import,即便在该类和定义的包在同一个包中。
5.在aidl文件中所有非Java基本类型参数必须加上in、out、inout标记,以指明参数是输入参数、输出参数还是输入输出参数。
6.Java原始类型默认的标记为in,不能为其它标记。
AIDL的使用步骤
为了方便模拟AIDL,我们假设我们有一个应用需要计算两个数相加之和,那么假设有2个进程(或者2个应用),一个叫服务端(Server),它里面已经实现两个整数相加的方法,那么在客户端中我们不需要再重新做这个方法了,而且调用服务端的方法,通过AIDL可以实现上述的过程,下图是过程的描述。
图1、实例程序图
1,创建aidl文件
图2、AndroidStudio中新建AIDL
首先在Android Studio中创建一个module作为服务端,然后在src的目录下右键–>选择New–>选择Folder–>AIDL Folder;然后会弹出一个选择aidl文件是否放在默认的目录下的对话框,我们直接点击Finish即可,我们会发现AndroidStudio下,AIDL的目录会放在java和res同级的目录下:
图3、AIDL的目录位置
2,定义aidl,并且编译成java
在AIDL目录上右键–>New–>AIDL–>AIDL File;然后在aidl目录会看到多了一个以包名命名的子目录,并且多了一个IMathAidl.aidl的文件,我们可以在这个文件下定义上述所说的两个整数相加的方法:
// IMathAidl.aidl
package com.example.server;
// Declare any non-default types here with import statements
interface IMathAidl {
/**
* 计算两个整数相加之和
*/
int add(int params1,int params2);
/**
* 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);
}
上面的IMathAidl中add方法就是我定义的方法,下面的basicTypes是系统自动生成的,先不管它。在aidl文件定义方法就跟在Java中定义方法差不多一样的,但是很值得注意的是aidl中的方法返回值和参数值的类型有如下规定:
1,在默认情况下,aidl参数只支持基本数据类型,如 int、long、char、boolean 等等,还有String、CharSequence。
2,使用对象时,必须继承Parcelable接口。但是使用时,必须在文件中声明import。
3,使用List和Map时,内部支持的数据类型也必须满足基本数据类型或者其包装类、对象等。
4,在使用非基本数据类型时,如Person、List
图4、aidl生成的java文件
3,创建远程服务,实现aidl的接口
我们已经创建好了aidl文件并且也编译成了Java类,那么我们知道aidl远程调用其实是通过绑定服务的方式做到的,那么我们这里就需要在服务端里建立一个服务类RemoteService ,在这个服务里实现aidl的stub类,完成功能代码的编写,如下所示:
public class RemoteService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private IBinder mBinder = new IMathAidl.Stub() {
@Override
public int add(int params1, int params2) throws RemoteException {
//计算
Log.i("AIDL", "params1 = " + params1 + " , params2 = " + params2);
return params1 + params2;
}
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
};
}
通过上述代码的分析,我们首先new了一个IMathAidl.Stub类的对象,其实它是一个IBinder,此时在onBind的生命回调里返回值也是IBinder,那么将这个mBinder通过绑定的时候返回,这样就能在客户端与服务端建立连接的时候,客户端能得到服务端的IBinder对象,从而能调用aidl中方法。
4,客户端的建立
图5,客户端UI
好,客户端的界面如上所示很简单,不贴代码了。我们知道,两个进程(应用)通信,需要建立一种规则,必须同时遵守才能进行下去,那么上面我们在服务端通过aidl定义好了这种规则,如果客户端需要通信,最好的方式就是直接拿服务端定义好的通信协议,换句话说,我们保证客户端能够调用服务端的方法,就应该新建一个跟服务端一样的aidl文件 ,所以我将服务端中的IMathAidl直接拿到客户端中使用,使用步骤都是一样的,新建AIDL目录,新建.aidl文件,编译等。 注意:客户端新建的AIDL下面的包名一定要跟服务端的包名是一样的,譬如上述的服务端aidl的包名是com.example.server,那么客户端的包名也得是这个,否则会报错:
java.lang.SecurityException: Binder invocation to an incorrect interface...
5,客户端与服务端建立连接
首先我们需要绑定远程的服务,通过Intent,然后通过ServiceConnection对象获取到远程服务的IBinder对象,最后调用IBinder里的方法:
public class MainActivity extends AppCompatActivity {
private EditText mEditText1;
private EditText mEditText2;
private EditText mEditText3;
private Button mButton;
private IMathAidl mService;
private ServiceConnection mConn = new ServiceConnection() {
/**
* 服务连接上了调用
* @param name
* @param service
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//获取远程服务
mService = IMathAidl.Stub.asInterface(service);
}
/**
* 服务断开了调用
* @param name
*/
@Override
public void onServiceDisconnected(ComponentName name) {
//回收资源
mService = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mEditText1 = (EditText) findViewById(R.id.editText1);
mEditText2 = (EditText) findViewById(R.id.editText2);
mEditText3 = (EditText) findViewById(R.id.editText3);
mButton = (Button) findViewById(R.id.button);
//进入客户端就绑定远程服务
bindService();
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int params1 = Integer.parseInt(mEditText1.getText().toString().trim());
int params2 = Integer.parseInt(mEditText2.getText().toString().trim());
try {
//调用远程服务的add方法
int result = mService.add(params1, params2);
mEditText3.setText(result + "");
} catch (RemoteException e) {
e.printStackTrace();
mEditText3.setText("出错了");
}
}
});
}
/**
* 绑定远程服务
*/
private void bindService() {
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.example.server", "com.example.server.RemoteService"));
bindService(intent, mConn, Context.BIND_AUTO_CREATE);
}
}
值得注意的是,我们必须要在服务端的AndroidMainifest.xml里为服务设置如下属性:
<service
android:name=".RemoteService"
android:exported="true"
android:process=":remote" />
否则在客户端绑定远程服务建立连接时,获取到的远程AIDL的binder为空,就无法调用远程服务中的方法了,报错信息如下:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.aidlapplication/com.example.server.MainActivity}: java.lang.SecurityException: Not allowed to bind to service Intent { cmp=com.example.server/.RemoteService }
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2416)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476)
at android.app.ActivityThread.-wrap11(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Caused by: java.lang.SecurityException: Not allowed to bind to service Intent { cmp=com.example.server/.RemoteService }
at android.app.ContextImpl.bindServiceCommon(ContextImpl.java:1322)
at android.app.ContextImpl.bindService(ContextImpl.java:1286)
at android.content.ContextWrapper.bindService(ContextWrapper.java:604)
at com.example.server.MainActivity.bindService(MainActivity.java:84)
at com.example.server.MainActivity.onCreate(MainActivity.java:58)
at android.app.Activity.performCreate(Activity.java:6237)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1107)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2369)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476)
at android.app.ActivityThread.-wrap11(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
下面是实例程序的结果:
图6、计算结果
并且在服务端的服务日志里打印如下:
图7、服务日志
说明该计算过程确实是调用了服务端的add方法完成的。
三、优缺点
优点 1.AIDL有自己的独立进程,不会受到其它进程的影响; 2.可以被其它进程复用,提供公共服务; 3.具有很高的灵活性。
缺点 相对普通服务,占用系统资源较多,使用AIDL进行IPC也相对麻烦。