概述
AIDL
的全称是 Android Interface Definition Language
,即Android 接口定义语言,其主要用于APP之间通讯,即一个APP与其他APP里的Service进行通讯,因为在Android里面一个独立的APP就是一个独立的进程,所以这也是传说中的Android跨进程通讯(即IPC: Inter-Process Communication)方式之一。
工作原理
AIDL定义了一套基于Server/Client模型的自定义语法的多进程通讯方式,通过该语法最终编译器自动生成基于Binder机制的通讯方式。
使用步骤
AIDL是工作于C/S模式下的,因此在多进程中至少存在两个角色——服务端和客户端,以下是这个两个角色使用的大概步骤:
-
服务端(Server):
第1步:新建AIDL文件,并声明可以向客户端提供的接口方法;
第2步:新建一个Service,实现AIDL中定义的接口方法,通过Binder与该Service进行绑定;
第3步:在manifest文件中注册该服务并声明为远程服务。 客户端(Client):
第1步:拷贝服务器端的AIDL文件到main目录下;
第2步:通过AIDL定义的接口的Stub.asInterface(service)
方法获取服务器端的AIDL类型实例,然后通过ServiceConnection与Activity进行关联,绑定服务后便可以通过该实例根据需要调用服务器端提供的接口方法了;
第3步:通过Intent指定服务端的服务器名称和包名,与远程Service进行绑定。
实例
该实例展示了客户端如何通过AIDL与远程Service进行通讯的,首先展示的是基本的数据类型的传递,最后使用自定义数据类型的传递。
因为是跨进程通讯,所以需要新建两个APP,一个作为服务端,一个作为客户端,该实例中服务端APP名称:
AidlServer
,包名:com.example.study.aidl.aidlserver
;客户端APP名称:AidlClient
,包名:com.example.study.aidl.aidlclient
。
基本数据类型
- 服务端(Server):
第1步:新建AIDL文件,右键新建文件,选择AIDL,文件名称为IMyAidlInterface
,Android Studio自动生产文件如下:
// IMyAidlInterface.aidl
package com.example.study.aidl.aidlserver;
// Declare any non-default types here with import statements
interface IMyAidlInterface {
/**
* 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);
}
通过注释可以看出,默认情况下,AIDL只支持6种基本数据类型,即int、long、boolean、float、double和String。
第2步:在该AIDL文件中定义自己的可以向客户端提供的接口方法;
// IMyAidlInterface.aidl
package com.example.study.aidl.aidlserver;
// Declare any non-default types here with import statements
interface IMyAidlInterface {
String getName();
}
第3步:Sync
整个工程(Build --> make project),然后在java目录下新建一个Service,取名MyService
;
package com.example.study.aidl.aidlserver;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class MyService extends Service {
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
}
第4步:定义一个AIDL接口类型的Stub实例,并实现接口方法;
package com.example.study.aidl.aidlserver;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
public class MyService extends Service {
private IMyAidlInterface.Stub myBinder = new IMyAidlInterface.Stub() {
@Override
public String getName() throws RemoteException {
return "HSG --- AIDL --- Okay!";
}
};
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
}
第5步:将上述AIDL接口类型的Stub实例通过onBinder()方法返回,即与当前Service建立绑定关系,并根据需要重写其他生命周期方法;
package com.example.study.aidl.aidlserver;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
public class MyService extends Service {
private static final String TAG = "MyService";
private IMyAidlInterface.Stub myBinder = new IMyAidlInterface.Stub() {
@Override
public String getName() throws RemoteException {
return "HSG --- AIDL --- Okay!";
}
};
public MyService() {
}
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate() called");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], flags = [" + flags + "], startId = [" + startId + "]");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy() called");
}
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind() called with: intent = [" + intent + "]");
return this.myBinder;
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "onUnbind() called with: intent = [" + intent + "]");
return super.onUnbind(intent);
}
}
第5步:在Manifest.xml文件中,注册并声明为远程服务。
说明:
android:process=":remote"
将该服务设置成远程服务;android:exported="true"
将该服务设置成可被其他进程调用;intent-filter
标签中的这个后面会通过Intent与该服务进行绑定会用到。
- 客户端(Client):
第1步:将服务端aidl目录下的文件全部拷贝到客户端app/src/main
目录下;
注意:记得是原封不动地拷贝,什么都不要改,切记!切记!切记!
第2步:Sync
整个工程(Build --> make project),然后在MainActivity中定义ServiceConnection
实例,以便与该Activity进行关联,在onServiceConnected()
方法中,通过Stub.asInterface(service)
接口获取服务端的AIDL接口类型实例;
package com.example.study.aidl.aidlclient;
import android.content.ComponentName;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import com.example.study.aidl.aidlserver.IMyAidlInterface;
public class MainActivity extends AppCompatActivity {
private IMyAidlInterface myAidlInterface;
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myAidlInterface = IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
第3步:通过Intent指定服务端的服务名称和服务所在的包名,并通过bindService()
方法进行Service绑定。
package com.example.study.aidl.aidlclient;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import com.example.study.aidl.aidlserver.IMyAidlInterface;
public class MainActivity extends AppCompatActivity {
private IMyAidlInterface myAidlInterface;
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myAidlInterface = IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent("com.example.study.aidl.MyServer");
intent.setPackage("com.example.study.aidl.aidlserver");
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
}
}
注意:
- 这里Intent构造方法里的action字符串一定要与服务端在manifest文件中通过intent-filter标签声明的action name的值保持一直;
- Android5.0后无法只通过隐式Intent绑定远程Service,还需要通过
setPackage()
方法指定远程服务所在的包名,本人当时就这里报错,说找不到对应的Service,折腾了好久。
第4步:根据需要通过AIDL接口实例调用AIDL提供的接口方法,此处通过在客户端布局文件中添加一个按钮,并添加点击事件监听,在点击事件中调用getName()
方法,从而获取服务端返回信息。
package com.example.study.aidl.aidlclient;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;
import com.example.study.aidl.aidlserver.IMyAidlInterface;
public class MainActivity extends AppCompatActivity {
private IMyAidlInterface myAidlInterface;
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myAidlInterface = IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent("com.example.study.aidl.MyServer");
intent.setPackage("com.example.study.aidl.aidlserver");
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
String name = myAidlInterface.getName();
Toast.makeText(MainActivity.this, name, Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
}
至此,一个最简单支持基本类型数据传递的通过AIDL方式进行多进程通讯就完成了,先运行服务端APP,再运行客户端APP,然后点击客户端APP屏幕中间的按钮就会弹出“HSG --- AIDL --- Okay!”字样的toast信息了。
自定义数据类型
在实际应用场景下,很大可能性,不可以只支持基本数据类型的数据传递,还需要支持自定义数据类型的数据传递,其大概步骤如下:
-
服务端(Server):
第1步:自定义类型要实现Parcelable接口;
第2步:新建一个同名AIDL文件对自定义类型进行声明;
第3步:AIDL接口中导入我们的AIDL类型;
第4步:在接口文件中定义支持自定义类型的接口方法;
第5步:Sync
整个工程(Build --> make project),在service中实现刚添加的支持自定义类型的接口方法。 -
客户端(Client):
第1步:拷贝自定义类型文件和AIDL目录下所有文件到客户端目录下,包名需要完全和服务端保持一致;
第2步:Sync
整个工程(Build --> make project),其他步骤与基本数据类型相同,然后便可以调用支持自定义类型的接口方法了。
代码实例如下:
-
服务端(Server):
第1步:自定义User类型并实现Parcelable接口;
package com.example.study.aidl.demo;
import android.os.Parcel;
import android.os.Parcelable;
public class User implements Parcelable {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
protected User(Parcel in) {
name = in.readString();
age = in.readInt();
}
public static final Creator CREATOR = new Creator() {
@Override
public User createFromParcel(Parcel in) {
return new User(in);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.name);
dest.writeInt(this.age);
}
}
第2步:在AIDL目录下新建一个同名AIDL文件对自定义类型进行声明;
// User.aidl
package com.example.study.aidl.demo;
// Declare any non-default types here with import statements
parcelable User;
注意: 自定义类型对应的AIDL文件包名与自定义Java文件包名保持一致。
第3步:AIDL接口文件中导入自定义类型对应的AIDL类型;
// IMyAidlInterface.aidl
package com.example.study.aidl.aidlserver;
// Declare any non-default types here with import statements
import com.example.study.aidl.demo.User;
interface IMyAidlInterface {
String getName();
}
第4步:在接口文件中定义支持自定义类型的接口方法;
// IMyAidlInterface.aidl
package com.example.study.aidl.aidlserver;
// Declare any non-default types here with import statements
import com.example.study.aidl.demo.User;
interface IMyAidlInterface {
String getName();
User getUser();
}
第5步:Sync
整个工程(Build --> make project),在service中实现刚添加的支持自定义类型的接口方法。
package com.example.study.aidl.aidlserver;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.example.study.aidl.demo.User;
public class MyService extends Service {
private static final String TAG = "MyService";
private IMyAidlInterface.Stub myBinder = new IMyAidlInterface.Stub() {
@Override
public String getName() throws RemoteException {
return "HSG --- AIDL --- Okay!";
}
@Override
public User getUser() {
return new User("Jack", 89);
}
};
public MyService() {
}
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate() called");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand() called with: intent = [" + intent + "], flags = [" + flags + "], startId = [" + startId + "]");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy() called");
}
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind() called with: intent = [" + intent + "]");
return this.myBinder;
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "onUnbind() called with: intent = [" + intent + "]");
return super.onUnbind(intent);
}
}
-
客户端(Client):
第1步:拷贝自定义类型文件和AIDL目录下所有文件到客户端目录下,包名需要完全和服务端保持一致;
注意是同时拷贝自定义类型Java文件和AIDL目录下所有文件,路径包名完全一致。
第2步:
Sync
整个工程(Build --> make project),其他步骤与基本数据类型相同,然后便可以调用支持自定义类型的接口方法了。
package com.example.study.aidl.aidlclient;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;
import com.example.study.aidl.aidlserver.IMyAidlInterface;
public class MainActivity extends AppCompatActivity {
private IMyAidlInterface myAidlInterface;
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myAidlInterface = IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent("com.example.study.aidl.MyServer");
intent.setPackage("com.example.study.aidl.aidlserver");
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
String name = myAidlInterface.getName();
String user = myAidlInterface.getUser().toString();
Toast.makeText(MainActivity.this, name + ", " + user, Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
}
至此,一个简单支持基本类型和自定义类型数据传递的通过AIDL方式进行多进程通讯的实例就完成了,先运行服务端APP,再运行客户端APP,然后点击客户端APP屏幕中间的按钮就会弹出“HSG --- AIDL --- Okay!,User{name='Jack', age=89}”字样的toast信息了。
总结
AIDL是Android中多进程通讯的一种方式,其本质就是起一个可被多个APP共享的远程Service作为服务端,然后其他APP作为客户端,通过binder方式与其进行信息交互,AIDL定义了一套自己的语法规则而已。
代码传送门:
- 服务端(Server)
- 客户端(Client)
参考
- Android中AIDL的使用详解
- Android:远程服务Service(含AIDL & IPC讲解)