AIDL的简单使用和注意事项

概述

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的章节,里面对技术细节讲解的非常详细。

AIDLClientDemo
AIDLServiceDemo

猜你喜欢

转载自blog.csdn.net/lj19851227/article/details/81449333
今日推荐