android中为了能够实现高效率的跨进程通信,实现了一种特殊的IPC(inter-process communication)跨进程通信方式,AIDL(android interface definition language,android接口定义语言),该篇主要以一个示例来讲解aidl在实际开发中如何使用。
这里我们以一个登陆为例,调用远端的登录接口,通过callback返回结果。
对于服务端,开发步骤如下:
- 1.定义aidl文件
在androidstudio中,右键鼠标->New->AIDL->AIDL File,设置aidl文件的名称,如图所示:
创建好aidl后,我们的目录结构如下所示:
当前我们的server端的应用包名为:com.aidl.server。在创建aidl文件时,androidstudio会在main目录下,创建一个aidl文件夹,在该aidl文件夹中会创建和应用相同的包,这里为:com.aidl.server,我们的aidl文件就放在这个包下。此处有3个aidl文件,分别为:
ILoginManager.aidl:声明了登录的接口
LoginCallback.aidl:登录结果的回调
LoginInfo.aidl:登录的信息封装,该文件在model文件夹下。
我们看看各自的aidl的内容。
ILoginManager.aidl
package com.aidl.server;
import com.aidl.server.LoginCallback;
interface ILoginManager {
void login(String name,String pwd,in LoginCallback callback);
}
LoginCallback.aidl
package com.aidl.server;
import com.aidl.server.model.LoginInfo;
interface LoginCallback {
void result(int errcode, in LoginInfo loginInfo);
}
LoginInfo.aidl:
package com.aidl.server.model;
parcelable LoginInfo;
这里以一个调用远程的登录接口为例,通过一个callback返回结果。
1.在远程server端定义aidl
LoginManager.aidl:
package com.aidl.server;
import com.aidl.server.LoginCallback;
interface ILoginManager {
void login(String name,String pwd,in LoginCallback callback);
}
LoginCallback.aidl:
package com.aidl.server;
import com.aidl.server.model.LoginInfo;
interface LoginCallback {
void result(int errcode, in LoginInfo loginInfo);
}
LoginInfo.aidl:
package com.aidl.server.model;
parcelable LoginInfo;
aidl和我们写interface很相似,不过aidl中需要将public这个关键字去掉。
注意一:
AIDL默认支持的数据类型有:
1.Java中八种基本数据类型:byte、short、int、long、char、boolean,float、double
2.String
3.CharSequence
4.List和Map:它们内部中的元素也必须是AIDL支持的数据类型或者是Parcelable,或者是其他aidl自动生成的类
注意二:
在使用aidl跨进程通信的时候,数据的传递是有流向的,从client到server、从server到client,或者两者之间双向传递,分别对应的tag关键字为:in,out,inout。其中基本数据类型、String和CharSequence的默认流向是in,而且不能修改。其他类型必须显示的声明数据的定向tag。这个定向tag只针对参数,不针对方法返回值。在客户端调用远程接口,如果是in,那么server端将能够收到数据,如果是out,server端收不到数据,但是如果在server端修改数据,可以同步到client端,如果是inout则server端和client都可以收到数据。
注意三:
在使用自定义的数据类型的时候,如果使用的是Parcelable,需要申明一个对应的aidl文件,这里如LoginInfo.aidl里面所示。
2.创建实现Parcelable接口的类,对应上一步的LoginInfo.aidl。(可以使用插件一键生成)
public class LoginInfo implements Parcelable{ public String name; public String pwd; public boolean success; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.name); dest.writeString(this.pwd); dest.writeByte(this.success ? (byte) 1 : (byte) 0); } public LoginInfo() { } protected LoginInfo(Parcel in) { this.name = in.readString(); this.pwd = in.readString(); this.success = in.readByte() != 0; } public static final Creator<LoginInfo> CREATOR = new Creator<LoginInfo>() { @Override public LoginInfo createFromParcel(Parcel source) { return new LoginInfo(source); } @Override public LoginInfo[] newArray(int size) { return new LoginInfo[size]; } }; }
3.编译或者同步工程,上一步创建的aidl文件会自动生成对应的类文件,创建继承Service的类,在onBinde方法中返回一个IBinder对象。创建这个IBinder对象就是创建第一步中aidl文件生成的类中的Stub类对象。该IBinder对象就是server端给client提供服务功能的对象。
比如我们这里需要给client提供ILoginManger里面的功能,在编译工程后会生成一个ILoginManger.java类。该类中有一个Stub类,我们返回的IBinder对象实际上就是这个Stub对象。
自动生成的ILoginManger.java类如下所示:
package com.aidl.server;
public interface ILoginManager extends android.os.IInterface {
public static abstract class Stub extends android.os.Binder implements com.aidl.server.ILoginManager {
private static final java.lang.String DESCRIPTOR = "com.aidl.server.ILoginManager";
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
public static com.aidl.server.ILoginManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.aidl.server.ILoginManager))) {
return ((com.aidl.server.ILoginManager) iin);
}
return new com.aidl.server.ILoginManager.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_login: {
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
java.lang.String _arg1;
_arg1 = data.readString();
com.aidl.server.LoginCallback _arg2;
_arg2 = com.aidl.server.LoginCallback.Stub.asInterface(data.readStrongBinder());
this.login(_arg0, _arg1, _arg2);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.aidl.server.ILoginManager {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public void login(java.lang.String name, java.lang.String pwd, com.aidl.server.LoginCallback callback) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(name);
_data.writeString(pwd);
_data.writeStrongBinder((((callback != null)) ? (callback.asBinder()) : (null)));
mRemote.transact(Stub.TRANSACTION_login, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_login = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public void login(java.lang.String name, java.lang.String pwd, com.aidl.server.LoginCallback callback) throws android.os.RemoteException;
}
可以看到Stub类继承自Binder类,实现了ILoginManger接口,即我们自己申明的aidl文件接口。而Bindler类又实现了IBinder接口。所以我们返回的IBinder对象需要实现了Stub类的对象。
我们的ServiceL类如下所示:
public class LoginService extends Service {
@Override
public IBinder onBind(Intent intent) {
return new ILoginManager.Stub() {
@Override
public void login(String name, String pwd, LoginCallback callback) throws RemoteException {
LoginInfo info = new LoginInfo();
info.name = name;
info.pwd = pwd;
if (TextUtils.equals("admin",name) && TextUtils.equals(pwd,"123")) {
info.success = true;
callback.result(0,info);
}else {
info.success = false;
callback.result(-1,info);
}
}
};
}
}
服务端需要实现aidl中声明的方法,这里我们通过callback返回结果。
server的开发步骤就这几个,还是比较简单的。下面讲client的开发流程。
- 1.需要拷贝server端中的所有aidl文件在本地,文件的包名需要和server端一一对应。如果有Parcelable类,client端同样也需要有一个同名的类。
我们的client的aidl的目录结构如下:
aidl的目录结构和server端一致。这里我们的client端的包名为:com.aidl.client。这样client和server端都有aidl声明的类了。
2.在我们的activity中调用bindService来绑定远程服务,返回远程IBinder的代理对象(Client是拿不到远程真正的IBinder对象的,除非它们都在同一个进程)。
public class LoginActivity extends Activity{ private ILoginManager loginManager; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); findViewById(R.id.login).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (loginManager == null) { return; } try { loginManager.login("admin", "123", new LoginCallback.Stub() { @Override public void result(int errcode, LoginInfo loginInfo) throws RemoteException { if (errcode == 0) { Toast.makeText(getApplicationContext(),"登录成功" + loginInfo,Toast.LENGTH_SHORT).show(); }else { Toast.makeText(getApplicationContext(),"登录失败" + loginInfo,Toast.LENGTH_SHORT).show(); } } }); } catch (RemoteException e) { e.printStackTrace(); } } }); Intent intent = new Intent("com.aidl.loginservice"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Intent explicitIntent = createExplicitIntent(getApplicationContext(), intent); if (explicitIntent != null) { bindService(explicitIntent, connection, Context.BIND_AUTO_CREATE); } }else { bindService(intent, connection, Context.BIND_AUTO_CREATE); } } private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { loginManager = ILoginManager.Stub.asInterface(service); Toast.makeText(getApplicationContext(),"绑定成功",Toast.LENGTH_SHORT).show(); } @Override public void onServiceDisconnected(ComponentName name) { Toast.makeText(getApplicationContext(),"绑定失败",Toast.LENGTH_SHORT).show(); } }; public Intent createExplicitIntent(Context context, Intent implicitIntent) { PackageManager pm = context.getPackageManager(); List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0); if (resolveInfo == null || resolveInfo.size() != 1) { return null; } ResolveInfo serviceInfo = resolveInfo.get(0); String packageName = serviceInfo.serviceInfo.packageName; String className = serviceInfo.serviceInfo.name; ComponentName component = new ComponentName(packageName, className); Intent explicitIntent = new Intent(implicitIntent); explicitIntent.setComponent(component); return explicitIntent; } @Override protected void onDestroy() { super.onDestroy(); unbindService(connection); } }
在bindService成功后,IBinder对象在ServiceConnection的onServiceConnected方法中返回,这里我们需要调用ILoginManger.Stub.asInterface()方法将其转换为ILoginManger对象。
注意:
在5.0以上bindService只能显示绑定,我们是通过server端中的intent-filter来识别server端的,如果我们使用如下方式来bingService:
Intent intent = new Intent("com.aidl.loginservice");bindService(context,con,Context.BIND_AUTO_CREATE)
,在21以上版本是会抛出异常的,其中”com.aidl.loginservice”是server提供的intent-filter参数。
在ContextImpl中,bindService的时候会调用如下方法:
private void validateServiceIntent(Intent service) {
if (service.getComponent() == null && service.getPackage() == null) {
if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
IllegalArgumentException ex = new IllegalArgumentException(
"Service Intent must be explicit: " + service);
throw ex;
} else {
Log.w(TAG, "Implicit intents with startService are not safe: " + service
+ " " + Debug.getCallers(2, 3));
}
}
}
我们可以看到,在21版本以上(包含21),如果我们的intent中component和package都为空的话就会抛出异常。我们的解决办法当然就是构造一个component或者package了。我们的构造方式如下,通过packagemanager来找到server端的packageName和className,然后创建一个component给intent就可以了。
public Intent createExplicitIntent(Context context, Intent implicitIntent) {
PackageManager pm = context.getPackageManager();
List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);
if (resolveInfo == null || resolveInfo.size() != 1) {
return null;
}
ResolveInfo serviceInfo = resolveInfo.get(0);
String packageName = serviceInfo.serviceInfo.packageName;
String className = serviceInfo.serviceInfo.name;
ComponentName component = new ComponentName(packageName, className);
Intent explicitIntent = new Intent(implicitIntent);
explicitIntent.setComponent(component);
return explicitIntent;
}