Android中AIDL的使用
AIDL,即Android 接口定义语言,平时代码中几乎没用到,有点忘了,今天拿出来复习一下使用方法。
我们知道Android中要实现IPC,有好多种方式:
1.在Intent中附加extras来传递信息。
2.共享文件。
3.SharedPreferences。
4.基于Binder的AIDL。
5.基于Binder的Messenger(其实Messenger本质上也是AIDL,只不过系统做了封装以方便上层调用)。
6.Socket。
7.ContentProvider。
先讲讲怎么使用AIDL,首先创建一个aidl文件夹,接着创建aidl文件,文件名和接口名称需一致,否则会报错(AS的rename功能有坑。。找了半天才发现问题)。以IPerson接口为例,定义接口如图。
接着Rebuild Project,IDE会帮我们实现相关的类(app/build/generated/source/aidl文件夹内,后续再分析),实现Person类,集成IPerson.Stub:
public class MainActivity extends AppCompatActivity {
private IPerson iPerson;
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
iPerson = IPerson.Stub.asInterface(iBinder);
if (iPerson != null) {
try {
iPerson.setName("SJC");
Toast.makeText(MainActivity.this, "赋值成功!", Toast.LENGTH_LONG).show();
} catch (RemoteException e) {
e.printStackTrace();
Toast.makeText(MainActivity.this, "赋值失败!", Toast.LENGTH_LONG).show();
}
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent();
intent.setAction("forServiceAidl");
bindService(getExplicitIntent(this, intent), serviceConnection, Service.BIND_AUTO_CREATE);
btn = findViewById(R.id.jump);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (iPerson != null) {
try {
iPerson.setName("SJC" + Math.random());
Toast.makeText(MainActivity.this, "赋值成功!", Toast.LENGTH_LONG).show();
} catch (RemoteException e) {
e.printStackTrace();
Toast.makeText(MainActivity.this, "赋值失败!", Toast.LENGTH_LONG).show();
}
}
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(serviceConnection);
}
public static Intent getExplicitIntent(Context context, Intent implicitIntent) {
// Retrieve all services that can match the given intent
PackageManager pm = context.getPackageManager();
List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);
// Make sure only one match was found
if (resolveInfo == null || resolveInfo.size() != 1) {
return null;
}
// Get component info and create ComponentName
ResolveInfo serviceInfo = resolveInfo.get(0);
String packageName = serviceInfo.serviceInfo.packageName;
String className = serviceInfo.serviceInfo.name;
ComponentName component = new ComponentName(packageName, className);
// Create a new intent. Use the old one for extras and such reuse
Intent explicitIntent = new Intent(implicitIntent);
// Set the component to be explicit
explicitIntent.setComponent(component);
return explicitIntent;
}
}
MainActivity代码贴出来了,这里因为Android5.0以上不允许隐式调用Service,调用了getExplicitIntent方法,将隐式调用改为显示调用(也可以直接指定包名),记得在Manifest中注册Service。MainActivity主要作用就是绑定Service。接下来可以创建客户端ApplicationB了,将aidl文件夹拷贝到ApplicationB中,如图:

public class MainActivityB extends AppCompatActivity {
IPerson person;
Button btn;
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName arg0) {
}
//因为有可能有多个应用同时进行RPC操作,所以同步该方法
@Override
public synchronized void onServiceConnected(ComponentName arg0, IBinder binder) {
//获得IPerson接口
person = IPerson.Stub.asInterface(binder);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = findViewById(R.id.btn);
Intent intent = new Intent();
intent.setAction("forServiceAidl");
bindService(getExplicitIntent(this, intent), conn, Service.BIND_AUTO_CREATE);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
if(person != null){
try {
//RPC方法调用
String name = person.getName();
Toast.makeText(MainActivityB.this, "远程进程调用成功!值为 : "+ name, Toast.LENGTH_LONG).show();
} catch (RemoteException e) {
e.printStackTrace();
Toast.makeText(MainActivityB.this, "远程进程调用失败! ", Toast.LENGTH_LONG).show();
}
}
}
});
}
public static Intent getExplicitIntent(Context context, Intent implicitIntent) {
// Retrieve all services that can match the given intent
PackageManager pm = context.getPackageManager();
List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);
// Make sure only one match was found
if (resolveInfo == null || resolveInfo.size() != 1) {
return null;
}
// Get component info and create ComponentName
ResolveInfo serviceInfo = resolveInfo.get(0);
String packageName = serviceInfo.serviceInfo.packageName;
String className = serviceInfo.serviceInfo.name;
ComponentName component = new ComponentName(packageName, className);
// Create a new intent. Use the old one for extras and such reuse
Intent explicitIntent = new Intent(implicitIntent);
// Set the component to be explicit
explicitIntent.setComponent(component);
return explicitIntent;
}
}
MainActivityB 代码也贴出来,然后就可以愉快地调用远程服务了。
会用了,接下来分析一下原理吧。
废话不多说,直接把AS帮我们生成的代码贴出来再说
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: F:\\project\\MyApplication\\app\\src\\main\\aidl\\sjc\\zailingtech\\com\\myapplication\\IPerson.aidl
*/
package sjc.zailingtech.com.myapplication;
public interface IPerson extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements sjc.zailingtech.com.myapplication.IPerson {
private static final java.lang.String DESCRIPTOR = "sjc.zailingtech.com.myapplication.IPerson";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an sjc.zailingtech.com.myapplication.IPerson interface,
* generating a proxy if needed.
*/
public static sjc.zailingtech.com.myapplication.IPerson asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof sjc.zailingtech.com.myapplication.IPerson))) {
return ((sjc.zailingtech.com.myapplication.IPerson) iin);
}
return new sjc.zailingtech.com.myapplication.IPerson.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_setName: {
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
this.setName(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_getName: {
data.enforceInterface(DESCRIPTOR);
java.lang.String _result = this.getName();
reply.writeNoException();
reply.writeString(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements sjc.zailingtech.com.myapplication.IPerson {
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 setName(java.lang.String name) 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);
mRemote.transact(Stub.TRANSACTION_setName, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public java.lang.String getName() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_setName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public void setName(java.lang.String name) throws android.os.RemoteException;
public java.lang.String getName() throws android.os.RemoteException;
}
emmmmm......不知道说什么。。重要的地方编译器都已经给了注释了。。
因此,简单的说,Android中的Stub是一个类,实现了远程服务的接口,以便你能使用它,就好像此服务是在本地一样。好比在本地打了一个远程服务的“桩”,你就可以用来造房子什么的。"
Stub继承了android.os.Binder并实现IPerson接口,asInterface 方法用于将服务端的Binder对象转换为客户端所需要的接口对象,根据obj.queryLocalInterface(DESCRIPTOR)方法区分进程,如果是在同一个进程中调用的,就返回服务端Stub对象本身,否则就返回封装后的Stub.Proxy对象供客户端使用。
onTransact方法是运行在服务端的Binder线程中的,_data即调用接口传入的参数,_reply为调用方法得到的返回值,mRemote.transact(Stub.TRANSACTION_download, _data, _reply, 0);为调用过程。当客户端发起远程请求后,在底层封装后会交由此方法来处理。通过code来区分客户端请求的方法。
先讲这么多,写bug去了zzz。。。