概述
AIDL(Android interface definition Language)——Android 接口定义语言, 是 Android 提供的一种进程间通信 (IPC) 机制。可以利用它定义客户端与服务端使用进程间通信 (IPC) 进行相互通信时都认可的编程接口。 在 Android 上,一个进程通常无法访问另一个进程的内存。 尽管如此,进程需要将其对象分解成操作系统能够识别的原语,并将对象编组成跨越边界的对象。 编写执行这一编组操作的代码是一项繁琐的工作,因此 Android 会使用 AIDL 来处理。如果你有阅读源码的习惯,你会发现在Android系统提供的服务中大量使用了AIDL机制,使得其他进程能够访问并使用系统提供的服务。
使用步骤
服务端通过一个前台的Service结合SqliteDatabase数据持久化技术实现了一个简单的学生管理系统,提供学生的增删改查功能。客户端通过AIDL技术绑定服务端,进而访问和使用服务端提供的服务,对学生进行增删改查操作。同时,对服务端数据的改变进行监听,以便客户端做出相应的响应。下面分别从服务端和客户端的实现,一步一步讲解对应的实现。
服务端的实现
(1)创建服务端.aidl文件
- 自定义数据封装类
派生Parcelable自定义了Student类用于封装学生相关信息,里面包含了预先设定的几个属性。
public class Student implements Parcelable {
private long id;
private String name;
private int gender;
private int age;
private int score;
...
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(this.id);
dest.writeString(this.name);
dest.writeInt(this.gender);
dest.writeInt(this.age);
dest.writeInt(this.score);
}
protected Student(Parcel in) {
this.id = in.readLong();
this.name = in.readString();
this.gender = in.readInt();
this.age = in.readInt();
this.score = in.readInt();
}
public static final Creator<Student> CREATOR = new Creator<Student>() {
@Override
public Student createFromParcel(Parcel source) {
return new Student(source);
}
@Override
public Student[] newArray(int size) {
return new Student[size];
}
};
}
如果在AIDL文件中用到了自定义的Parcelable对象,必须创建一个和它同名的AIDL文件,并在其中声明它为Parcelable。
// Student.aidl
package com.android.peter.aidlservicedemo.bean;
// 注意:文件中Student声明处parcelable为小写
parcelable Student;
- 创建数据变化监听接口文件
// IOnDataChangeListener.aidl
package com.android.peter.aidlservicedemo;
interface IOnDataChangeListener {
void onDataChange();
}
- 创建服务接口文件
除了基本数据类型,其他类型的参数都需要标上方向类型:in(输入), out(输出), inout(输入输出)。
// IStudentManager.aidl
package com.android.peter.aidlservicedemo;
import com.android.peter.aidlservicedemo.bean.Student;
import com.android.peter.aidlservicedemo.IOnDataChangeListener;
interface IStudentManager {
List<Student> getStudentList();
long addStudent(in ContentValues contentValues);
int deletedStudent(String whereClause, in String[] whereArgs);
int updateStudent(in ContentValues contentValues, String whereClause, in String[] whereArgs);
List<Student> queryStudent(in String[] columns, String selection,in String[] selectionArgs,
String groupBy, String having,String orderBy, String limit);
void registerDataChangeListener(IOnDataChangeListener listener);
void unregisterDataChangeListener(IOnDataChangeListener listener);
}
(2)实现服务端接口
public class StudentManagerService extends Service {
...
private RemoteCallbackList<IOnDataChangeListener> mListenerList = new RemoteCallbackList<>();
private IBinder mService = new IStudentManager.Stub() {
// 检查调用进程的权限
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
int checkPermission = checkCallingOrSelfPermission(PERMISSION_STUDENT_MANAGER_SERVICE);
if( checkPermission != PackageManager.PERMISSION_GRANTED) {
Log.i(TAG,"Do not have permission !");
return false;
}
String packageName = null;
String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
if(packages != null && packages.length > 0) {
packageName = packages[0];
}
if (packageName != null && !packageName.startsWith("com.android.peter")) {
Log.i(TAG,"Package name must be contains \"com.android.peter\" !");
return false;
}
return super.onTransact(code, data, reply, flags);
}
@Override
public List<Student> getStudentList() throws RemoteException {
Log.d(TAG,"getStudentList");
return StudentListDaoImpl.getInstance(mContext).getAllStudent();
}
@Override
public long addStudent(ContentValues contentValues) throws RemoteException {
Log.d(TAG,"addStudent contentValues = " + contentValues);
long id = StudentListDaoImpl.getInstance(mContext).insert(contentValues);
// the row ID of the newly inserted row, or -1 if an error occurred
if(id != -1) {
notifyDataChanged();
}
return id;
}
@Override
public int deletedStudent(String whereClause, String[] whereArgs) throws RemoteException {
Log.d(TAG,"deletedStudent whereClause = " + whereClause + " , whereArgs = " + whereArgs);
int num = StudentListDaoImpl.getInstance(mContext).delete(whereClause,whereArgs);
// the number of rows affected if a whereClause is passed in, 0 otherwise.
if(num > 0) {
notifyDataChanged();
}
return num;
}
@Override
public int updateStudent(ContentValues contentValues, String whereClause, String[] whereArgs) throws RemoteException {
Log.d(TAG,"deletedStudent contentValues = " + contentValues + " , whereClause = " + whereClause + " , whereArgs = " + whereArgs);
int num = StudentListDaoImpl.getInstance(mContext).update(contentValues,whereClause,whereArgs);
// the number of rows affected
if(num > 0) {
notifyDataChanged();
}
return num;
}
@Override
public List<Student> queryStudent(String[] columns, String selection,
String[] selectionArgs, String groupBy, String having,
String orderBy, String limit) throws RemoteException {
Log.d(TAG,"queryStudent columns = " + columns + " , selection = " + selection + " , selectionArgs = " + selectionArgs
+ " , groupBy = " + groupBy + " , having = " + having + " , orderBy = " + orderBy + " , limit = " + limit);
return StudentListDaoImpl.getInstance(mContext).query(columns,selection,selectionArgs,groupBy,having,orderBy,limit);
}
@Override
public void registerDataChangeListener(IOnDataChangeListener listener) throws RemoteException {
mListenerList.register(listener);
}
@Override
public void unregisterDataChangeListener(IOnDataChangeListener listener) throws RemoteException {
mListenerList.unregister(listener);
}
};
...
// 数据发生变化时候调用
private void notifyDataChanged() {
mListenerList.beginBroadcast();
int N = mListenerList.getRegisteredCallbackCount();
for(int i = 0 ; i < N ; i++) {
try {
mListenerList.getBroadcastItem(i).onDataChange();
} catch (RemoteException e) {
e.printStackTrace();
}
}
mListenerList.finishBroadcast();
}
...
}
(3)公开相关接口文件
客户端的实现
(1)把相关接口文件拷贝到相应位置
需要把所有相关的文件和类全部拷贝到客户端对应的位置,切记不要修改完整路径和文件名,否则会编译不过。
(2)绑定服务、获得服务
public class ClientActivity extends AppCompatActivity {
private final static String TAG = "peter.ClientActivity";
private final static String REMOTE_SERVICE_PACKAGE_NAME = "com.android.peter.aidlservicedemo";
private final static String REMOTE_SERVICE_CLASS_NAME = "com.android.peter.aidlservicedemo.StudentManagerService";
private final static int MESSAGE_DATA_CHANGED = 20180804;
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case MESSAGE_DATA_CHANGED:
Log.i(TAG,"I have received a data changed message!");
break;
default:
// do nothing
}
}
};
private IStudentManager mRemoteService;
private IOnDataChangeListener mOnDataChangeLister = new IOnDataChangeListener.Stub() {
@Override
public void onDataChange() throws RemoteException {
Log.i(TAG,"onDataChange");
// running in Binder's thread pool,could be used to do long-time task,
// and could not to access to UI thread directly
mHandler.sendEmptyMessage(MESSAGE_DATA_CHANGED);
}
};
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.i(TAG,"Remote service is died !");
if(mRemoteService == null) {
return;
}
mRemoteService.asBinder().unlinkToDeath(mDeathRecipient,0);
mRemoteService = null;
// rebind remote service
bindRemoteService();
}
};
private ServiceConnection mSC = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG,"onServiceConnected name = " + name);
// 获得服务端服务
mRemoteService = IStudentManager.Stub.asInterface(service);
if(mRemoteService != null) {
try {
// 设置死亡代理
mRemoteService.asBinder().linkToDeath(mDeathRecipient,0);
// 注册监听器
mRemoteService.registerDataChangeListener(mOnDataChangeLister);
} catch (RemoteException e) {
e.printStackTrace();
}
} else {
Log.i(TAG,"Connect error!");
}
// onServiceConnected is running in main thread,
// can not do long-time task
new Thread(new Runnable() {
@Override
public void run() {
insertStudent();
queryStudent();
updateStudent();
deleteStudent();
}
}).start();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG,"onServiceDisconnected name = " + name);
mRemoteService = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_client);
bindRemoteService();
}
// bind remote service
private void bindRemoteService() {
Intent intent = new Intent();
intent.setComponent(new ComponentName(REMOTE_SERVICE_PACKAGE_NAME, REMOTE_SERVICE_CLASS_NAME));
bindService(intent,mSC, Context.BIND_AUTO_CREATE);
}
// inset three students
private void insertStudent() {
Log.i(TAG,"insertStudent");
ContentValues peter = new ContentValues();
peter.put(StudentTable.COLUMN_NAME,"peter");
peter.put(StudentTable.COLUMN_GENDER,0);
peter.put(StudentTable.COLUMN_AGE,33);
peter.put(StudentTable.COLUMN_SCORE,100);
ContentValues lemon = new ContentValues();
lemon.put(StudentTable.COLUMN_NAME,"lemon");
lemon.put(StudentTable.COLUMN_GENDER,1);
lemon.put(StudentTable.COLUMN_AGE,30);
lemon.put(StudentTable.COLUMN_SCORE,100);
ContentValues baoyamei = new ContentValues();
baoyamei.put(StudentTable.COLUMN_NAME,"baoyamei");
baoyamei.put(StudentTable.COLUMN_GENDER,1);
baoyamei.put(StudentTable.COLUMN_AGE,30);
baoyamei.put(StudentTable.COLUMN_SCORE,90);
try {
if(mRemoteService != null) {
mRemoteService.addStudent(peter);
mRemoteService.addStudent(lemon);
mRemoteService.addStudent(baoyamei);
List<Student> studentList = mRemoteService.getStudentList();
Log.i(TAG,"studentList = " + studentList.toString());
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
// query the student who's age is 30
private void queryStudent() {
Log.i(TAG,"queryStudent");
if(mRemoteService != null) {
try{
List<Student> queryList = mRemoteService.queryStudent(StudentTable.TABLE_COLUMNS,StudentTable.COLUMN_AGE + "=?",
new String[]{"30"},null,null,null,null);
Log.i(TAG,"queryList = " + queryList.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
// update the student who's score is 90
private void updateStudent() {
Log.i(TAG,"updateStudent");
if(mRemoteService != null) {
ContentValues lemon = new ContentValues();
lemon.put(StudentTable.COLUMN_SCORE,100);
try {
mRemoteService.updateStudent(lemon,StudentTable.COLUMN_NAME + "=?",new String[]{"baoyamei"});
List<Student> studentList = mRemoteService.getStudentList();
Log.i(TAG,"studentList = " + studentList.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
// delete the student who's name is baoyamei
private void deleteStudent() {
Log.i(TAG,"deleteStudent");
if(mRemoteService != null) {
try{
mRemoteService.deletedStudent(StudentTable.COLUMN_SCORE + "=?",new String[]{"100"});
List<Student> studentList = mRemoteService.getStudentList();
Log.i(TAG,"studentList = " + studentList.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if(mRemoteService != null && mRemoteService.asBinder().isBinderAlive()) {
try {
mRemoteService.unregisterDataChangeListener(mOnDataChangeLister);
} catch (RemoteException e) {
e.printStackTrace();
}
}
if(mSC != null) {
unbindService(mSC);
}
}
}
小结
本文结合实例扼要的介绍了AIDL的使用方法,关于SqliteDatabase的使用可以参考我的另一篇文章——Android数据存储之SQLiteDatabase。使用过程中应该注意的地方:
- 在定义Parcelable类和对应的.aidl文件的名字以及完整路径必须保持一致,否则编译会报错。同时,注意在.aidl文件中的parcelable声明为小写。
- 除了基本数据类型,其他类型的参数都需要标上方向类型:in(输入), out(输出), inout(输入输出)。
- 需要把所有相关的文件和类全部拷贝到客户端对应的位置,切记不要修改完整路径和文件名,否则会编译不过。
- 在实现监听器的时候,推荐选用RemoteCallbackList类作为服务端存储监听器列表的容器。进程间是不允许直接传递对象的,是通过序列化和反序列化来实现的,这也是为什么在定义自定义数据类型时需要派生Parcelable类。RemoteCallbackList内部是以CallBack的IBinder对象作为key值,以CallBack对象作为value值进行存储的,服务端反序列化后生成的CallBack对象其实是新生成的,但是底层对应IBinder并没有改变,客户端和服务端的CallBack对象是通过IBinder来关联的。
- 类似于访问网络,与服务端交互的时候很有可能也是耗时操作,最好不要在UI线程使用,有可能导致ANR。
- 通过单实例保证服务端全局只有一个SQLiteOpenHelper对象与数据库连接,解决数据库多线程并发访问问题。
- 从安全角度出发,需要为你的服务添加权限控制和包名检测,只有具有权限和指定包名的应用才能访问服务。添加权限控制的方法有多种,比如可以在AndroidManifest中添加权限声明,然后在Service声明处添加android:permission属性。或是在实现服务端接口的时候重写onTransact方法,这个方法在每次服务被调用的时候都会先被调用,可以在里面判断权限和包名。
更多的细节可以参看《Android开发艺术探索》关于AIDL的章节,里面对技术细节讲解的非常详细。