AIDL(Android Interface Definition Language),翻译成中文就是安卓接口定义语言的意思,是用于定义服务端和客户端通信接口的一种描述语言。其主要作用是IPC(Android进程间通讯),简单的来说就是AIDL可以让一个APP使用另外一个APP的Service,使得两个或者多个APP之间可以信息交互,使得多个APP之间只需要使用一套代码,这样对于同一个功能就不用在多个APP中都写一遍逻辑了,减少了重复代码。
语法AIDL的语法十分简单,与Java语言基本保持一致,需要记住的规则有以下几点:
AIDL文件以 .aidl 为后缀名
AIDL支持的数据类型分为如下几种:
八种基本数据类型:byte、char、short、int、long、float、double、boolean
String,CharSequence
实现了Parcelable接口的数据类型
List 类型。List承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象
Map类型。Map承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象
AIDL文件可以分为两类。一类用来声明实现了Parcelable接口的数据类型,以供其他AIDL文件使用那些非默认支持的数据类型。还有一类是用来定义接口方法,声明要暴露哪些接口给客户端调用,定向Tag就是用来标注这些方法的参数值
定向Tag。定向Tag表示在跨进程通信中数据的流向,用于标注方法的参数值,分为 in、out、inout 三种。其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。此外,如果AIDL方法接口的参数值类型是:基本数据类型、String、CharSequence或者其他AIDL文件定义的方法接口,那么这些参数值的定向 Tag 默认是且只能是 in,所以除了这些类型外,其他参数值都需要明确标注使用哪种定向Tag。定向Tag具体的使用差别后边会有介绍
明确导包。在AIDL文件中需要明确标明引用到的数据类型所在的包名,即使两个文件处在同个包名下
下面我举例使用AIDL实现一个APP访问另外一个APP创建的数据库。
首先我们创建一个新的项目作为服务端APP,创建一个database,这里我使用Room框架。自定义类型User,直接上代码
build.gradle中引入room支持
androidTestImplementation "android.arch.persistence.room:testing:1.1.1"
implementation 'android.arch.persistence.room:runtime:1.1.1'
annotationProcessor 'android.arch.persistence.room:compiler:1.1.1'
androidTestImplementation "android.arch.persistence.room:testing:1.1.1"
实体类User,注意要在AIDL中使用自定义类型,实体类必须实现Parcelable接口来进行序列化。而且这个类必须添加一个无参的构造方法,通常无参的构造方法即使不写编译器也会帮我们自动生成,但是这里必须手动加上。
package com.example.testapp.database;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "user")
public class User implements Parcelable {
public User() {
//无参构造
}
protected User(Parcel in) {
id = in.readLong();
name = in.readString();
sex = 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 long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@PrimaryKey
@ColumnInfo(name = "id")
private long id;
@ColumnInfo(name = "name")
private String name;
@ColumnInfo(name = "sex")
private String sex;
@ColumnInfo(name = "age")
private int age;
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id);
dest.writeString(name);
dest.writeString(sex);
dest.writeInt(age);
}
}
创建UserDao:
package com.example.testapp.database;
import java.util.List;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
@Dao
public interface UserDao {
@Query("SELECT * FROM user")
LiveData<List> getUserLiveData();
@Query("SELECT * FROM user")
List getAll();
@Insert
void insert(User user);
@Delete
void delect(User user);
@Update
void update(User user);
}
Room数据库支持直接返回LiveData对象的查询结果,AIDL这个例子没有用到,有兴趣可以看我这篇博客
LiveData结合Room数据库使用以及线程问题
UserDatabase:
package com.example.testapp.database;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.sqlite.db.SupportSQLiteDatabase;
@Database(entities = {User.class},version = 1,exportSchema = false)
public abstract class UserDatabase extends RoomDatabase {
public static final String DB_NAME = "UserDataBase.db";
private static volatile UserDatabase instance;
public static synchronized UserDatabase getInstance(Context context){
if (instance == null){
instance = createDatabase(context);
}
return instance;
}
private static UserDatabase createDatabase(Context context) {
return Room.databaseBuilder(context,UserDatabase.class,DB_NAME).allowMainThreadQueries().addCallback(new RoomDatabase.Callback(){
@Override
public void onCreate(@NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
}
@Override
public void onOpen(@NonNull SupportSQLiteDatabase db) {
super.onOpen(db);
}
}).build();
}
public abstract UserDao getUserDao();
}
数据库相关的逻辑已经编写完毕,可以通过
UserDatabase.getInstance(mContext).getUserDao().insert(user);
来给数据库添加数据。
下面是AIDL相关:
将视图切换到Project目录下,右键点击项目创建AIDL文件。
给service添加一个隐式意图的action,以方便另外一个app可以找到这个service。
到这里服务端APP搞定了,下面编写客户端APP的代码。
新建工程,将aidl整个包拷贝到新app的对应位置,包名必须一致。同样需要拷贝的还有数据bean User.java。这里同样这个类所在的包必须和服务端User类所在的包名一模一样。比如我服务端User类在com.example.testapp.database包下,那么我就需要在客户端app下也创建一个一样的包把User类放进去。
现在我们就可以去调用服务端的Service了。
简单编写一个Activity,在Activity中使用TextView显示数据库中所有User的name。
package com.example.test2app;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.example.testapp.database.DBAidlInterface;
import com.example.testapp.database.User;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private DBAidlInterface aidlInterface;
private TextView textView;
private Context mContext;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
aidlInterface = DBAidlInterface.Stub.asInterface(service);
Toast.makeText(mContext,"已连接",Toast.LENGTH_LONG).show();
}
@Override
public void onServiceDisconnected(ComponentName name) {
aidlInterface = null;
Toast.makeText(mContext,"断开连接",Toast.LENGTH_LONG).show();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.text_view);
mContext = this;
}
@Override
protected void onStart() {
super.onStart();
Intent intent =new Intent();
//Android现在对隐式意图管理严格,除了要setAction包名也需要设置,这里包名是服务端app的包名
intent.setAction("com.example.testapp.aidl");
intent.setPackage("com.example.testapp");
//绑定服务
bindService(intent,mConnection,Context.BIND_AUTO_CREATE);
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onDestroy() {
super.onDestroy();
//解绑服务
unbindService(mConnection);
}
//Button OnClick方法
public void getData(View view) {
if (aidlInterface!=null){
try {
List users = aidlInterface.getUsers();
String str = "";
for (User user: users){
str = str + user.getName()+" ";
}
textView.setText(str);
} catch (RemoteException e) {
e.printStackTrace();
}
}else {
}
}
}
将两个APP都安装到手机上,在服务端APP上添加数据库数据,切换到客户端app上点击按钮就能拿到最新的数据了。
扩展想到这个例子主要是之前看Content Provider的时候发现网上操作Content Provider的例子几乎都是使用的原生SQLiteOpenHelper的,并没有结合GreenDao,Room这种sqlite框架或者Realm数据库来使用,使用起来不是很方便。ContentProvider也是Android跨进程数据共享方案,和AIDL一样底层都是Binder机制,就想着能不能用AIDL来跨进程访问数据库,现在看来是可以的。但是有一个问题,客户端在绑定Service的时候需要服务端的进程是活着的。所以在启动前需要先唤醒服务端进程或者给服务端做保活才稳定。还是Content Provider好用~