Android使用AIDL共享Room数据库

什么是AIDL:

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<User> CREATOR = new Creator<User>() {
        @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<User>> getUserLiveData();
		
    @Query("SELECT * FROM user")
    List<User> 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文件。
在这里插入图片描述
文件名称自定义就好,创建完成后可以看到默认生成的代码。

// DBAidlInterface.aidl
package com.example.testapp.database;
import com.example.testapp.database.User;
// Declare any non-default types here with import statements

interface DBAidlInterface {
    /**
     * 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);
    List<User> getUsers();
}

basicTypes这个方法是自动生成的,可以删除,这个方法只是告诉你哪些基本类型可以直接在AIDL中使用。

List getUsers()是我们自己创建的方法,表示需要返回一个List< User>类型的的结果。

注意AIDL文件目前并不能自动导包,所以import信息需要你自己敲上去。

因为我们用到了自定义类型User,所以在这个AIDL文件同包下我们还需要新建一个AIDL文件用来帮助AIDL找到这个User类,这部必不可少,否则会报找不到类的错误。

文件名必须和自定义类型名称相同。这里我们的自定义类型是User,那么这个AIDL文件就需要命名为User.aidl.
在这里插入图片描述
内容只需要两行

package com.example.testapp.database;//设置User所在包名
parcelable User;//声明User为parcelable类型

sycn project以下以生成AIDL相关代码。

此时build文件下下就生成了相关文件DBAidlInterface
在这里插入图片描述

这个代码都是自动生成的,贴出来看一哈

/*
 * This file is auto-generated.  DO NOT MODIFY.
 */
package com.example.testapp.database;
// Declare any non-default types here with import statements

public interface DBAidlInterface extends android.os.IInterface
{
  /** Default implementation for DBAidlInterface. */
  public static class Default implements com.example.testapp.database.DBAidlInterface
  {
    /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         */
    @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException
    {
    }
    @Override public java.util.List<com.example.testapp.database.User> getUsers() throws android.os.RemoteException
    {
      return null;
    }
    @Override
    public android.os.IBinder asBinder() {
      return null;
    }
  }
  /** Local-side IPC implementation stub class. */
  public static abstract class Stub extends android.os.Binder implements com.example.testapp.database.DBAidlInterface
  {
    private static final java.lang.String DESCRIPTOR = "com.example.testapp.database.DBAidlInterface";
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an com.example.testapp.database.DBAidlInterface interface,
     * generating a proxy if needed.
     */
    public static com.example.testapp.database.DBAidlInterface asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.example.testapp.database.DBAidlInterface))) {
        return ((com.example.testapp.database.DBAidlInterface)iin);
      }
      return new com.example.testapp.database.DBAidlInterface.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
    {
      java.lang.String descriptor = DESCRIPTOR;
      switch (code)
      {
        case INTERFACE_TRANSACTION:
        {
          reply.writeString(descriptor);
          return true;
        }
        case TRANSACTION_basicTypes:
        {
          data.enforceInterface(descriptor);
          int _arg0;
          _arg0 = data.readInt();
          long _arg1;
          _arg1 = data.readLong();
          boolean _arg2;
          _arg2 = (0!=data.readInt());
          float _arg3;
          _arg3 = data.readFloat();
          double _arg4;
          _arg4 = data.readDouble();
          java.lang.String _arg5;
          _arg5 = data.readString();
          this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
          reply.writeNoException();
          return true;
        }
        case TRANSACTION_getUsers:
        {
          data.enforceInterface(descriptor);
          java.util.List<com.example.testapp.database.User> _result = this.getUsers();
          reply.writeNoException();
          reply.writeTypedList(_result);
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
    private static class Proxy implements com.example.testapp.database.DBAidlInterface
    {
      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;
      }
      /**
           * Demonstrates some basic types that you can use as parameters
           * and return values in AIDL.
           */
      @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) 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.writeInt(anInt);
          _data.writeLong(aLong);
          _data.writeInt(((aBoolean)?(1):(0)));
          _data.writeFloat(aFloat);
          _data.writeDouble(aDouble);
          _data.writeString(aString);
          boolean _status = mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().basicTypes(anInt, aLong, aBoolean, aFloat, aDouble, aString);
            return;
          }
          _reply.readException();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      @Override public java.util.List<com.example.testapp.database.User> getUsers() throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        java.util.List<com.example.testapp.database.User> _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          boolean _status = mRemote.transact(Stub.TRANSACTION_getUsers, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().getUsers();
          }
          _reply.readException();
          _result = _reply.createTypedArrayList(com.example.testapp.database.User.CREATOR);
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
      public static com.example.testapp.database.DBAidlInterface sDefaultImpl;
    }
    static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_getUsers = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    public static boolean setDefaultImpl(com.example.testapp.database.DBAidlInterface impl) {
      if (Stub.Proxy.sDefaultImpl == null && impl != null) {
        Stub.Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }
    public static com.example.testapp.database.DBAidlInterface getDefaultImpl() {
      return Stub.Proxy.sDefaultImpl;
    }
  }
  /**
       * Demonstrates some basic types that you can use as parameters
       * and return values in AIDL.
       */
  public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
  public java.util.List<com.example.testapp.database.User> getUsers() throws android.os.RemoteException;
}

OK,服务端app的最后一步就是创建Service了,和普通Service类似,只是我们的IBinder对象改为继承刚才生成代码中的内部静态抽象类 Stub。

package com.example.testapp.database;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;

import java.util.List;

public class DBService extends Service {
    public DBService() {
    }

    @Override
    public IBinder onBind(Intent intent) {

        return new DBBinder();
    }

    class DBBinder extends DBAidlInterface.Stub{

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }

        @Override
        public List<User> getUsers() throws RemoteException {
        //为了方便我的Room配置允许主线程操作,数据很少不会造成ANR
            List<User> all = UserDatabase.getInstance(getApplicationContext()).getUserDao().getAll();
            return all;
        }
    }
}

清单文件里注册一下service

 <service
            android:name=".database.DBService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.example.testapp.aidl"/>
            </intent-filter>
        </service>

给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<User> 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好用~

发布了28 篇原创文章 · 获赞 27 · 访问量 8294

猜你喜欢

转载自blog.csdn.net/weixin_44666188/article/details/105582000