IPC机制——使用AIDL


  AIDL(Android interface definition lanuage),安卓接口定义语言。是一种android内部进程通信的描述语言。

  AIDL支持的数据类型

  1、基本数据类型

  2、String和Charsequence

  3、List,只支持ArrayList,其中的对象必须序列化

  4、Map、只支持HashMap,其中对象必须序列化

  5、Parcelable对象

  6、AIDL接口

  

  Messenger是以串行的方式处理客户端发来的信息,如果大量的信息同时发送至服务端,服务端仍然只能逐个处理,所以如果有大量的并发请求,再用Messenger实现就不大合适了。并且使用Messenger无法调用服务端的方法。此时,AIDL是更好的实现IPC的方法。

  使用AIDL的流程

  1,服务端

    服务端首先创建一个Service用于监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在AIDL文件中声明,最后在Service中实现这个AIDL接口。 

      2、客户端

    绑定服务端的Service,绑定成功后,将返回的Binder对象转换程AIDL接口所属的类型。

  

  具体实现

  1、AIDL接口的创建

    在Androidstudio中,可直接在project视图下的src—>main目录下新建AIDL文件会自动创建AIDL文件夹以及对应的AIDL文件。

    一个声明自定义接口的AIDL文件(其中IOnNewBookArrivedListener为监听回调接口,用于服务端回调客户端方法)

// IBookManager.aidl
package com.example.aidltest;

// Declare any non-default types here with import statements
import com.example.aidltest.Book;
import com.example.aidltest.IOnNewBookArrivedListener;
interface IBookManager {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    List<Book> getBookList();
    void addBook(in Book book);
    void registerNewBookListener(IOnNewBookArrivedListener listener);
    void unregisterNewBookListener(IOnNewBookArrivedListener listener);
}

  除此之外对于AIDL中所使用的Parcelable对象也必须创建同名AIDL文件并声明序列化

// Book.aidl
package com.example.aidltest;

// Declare any non-default types here with import statements

parcelable Book;

  在AIDL文件中,即便是处于同一包下,使用时仍需要声明。

  序列化对象应存放在同名的java目录下的包中,而不是AIDL下,否则将编译错误(找不到java文件)。

  声明AIDL文件后,编译,会自动生成接口对应的java文件。

  AIDL的包结构在服务端以及客户端必须保持一致,否则运行会发生错误。

  2、远程服务端Service的实现

  创建Binder对象并在onBind方法中返回,该对象继承自AIDL接口的Stub并实现其内部声明的AIDL方法。

private Binder mBinder = new IBookManager.Stub(){
    //实现接口方法
}

  在AIDL方法中需处理线程同步,如对于ArrayList的并发读/写,可用线程同步的CopyOnWriteArrayList实现。(类似的还有ConcurrentHashMap),虽然AIDL只支持ArrayList,但在这里使用CopyOnwriteArrayList是可以的,因为Binder在访问该对象时,是以List的规范访问的,并最终形成一个新的

ArrayList对象发送给客户端。

  3、客户端的实现

  绑定服务,在ServiceConnection对象中的onServiceConnected方法中获得AIDL接口对象。并通过该接口调用服务端的远程方法即可。(service为服务端发送的Binder对象)

IBookManager bookManager = IBookManager.Stub.asInterface(service);

  

  为了使服务端也能够调用客户端的方法,再声明一个回调接口AIDL文件,方法类似。

  1、远程服务端的改进

  在远程服务端创建监听器集合,用来保存不同客户端的监听器对象。在服务端的AIDL接口中新增监听接口注册方法,用于客户端向服务端注册接口。

  由于Binder传送的序列化对象实际上为不同的两个对象,所以不能通过普通的方法来删除监听接口集合中的特定监听接口。为解决这个问题,Android提供了专门的RemoteCallbackList用于保存及删除监听接口,在注册和取消注册时只需分别调用register和unregister方法并传入监听器对象即可。

private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<IOnNewBookArrivedListener>();

   遍历监听器集合时先调用beginBroadcast得到集合长度,在循环遍历调用getBroadcastItem(i)取出对象,最后必须调用finishBroadcast方法。

 private void onNewBookArrived(Book book)throws RemoteException{
        mBookList.add(book);
        for (int i=0; i < mListenerList.beginBroadcast();i++){
            IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
            if (listener != null){
                listener.onNewBookArrived(book);
            }

        }
        mListenerList.finishBroadcast();
    }

    在客户端调用AIDL接口方法中的注册监听监听方法,向服务端添加监听接口。 随后服务端即可通过监听接口调用客户端的方法。

    以上基本完成了简单的AIDL应用。需注意的是被调用的方法是运行在实现接口的进程的Binder线程池中的,并且在执行方法时,调用方的线程会被挂起,所以应注意避免耗时操作或者在非UI线程中调用方法。同样的也应避免在接口方法中访问UI相关内容,若需要则通过Handle切换至UI线程。

   

   Binder是有可能意外死亡的,所以当服务意外停止时,应重新连接服务。有两种方法。

   第一种,给Binder设置DeathRecipient监听,当Binder死亡时会厚道binderDied方法回掉,在该方法中重新连接服务。

   第二种,在onServiceDIsconnection中重新连接。

     两种方法的区别主要在,一个运行在Binder线程池中,另一个运行在UI线程中。

    

   为AIDL的访问设置权限限制,同样有两种方法

   第一种,在AndroidManifest中声明自定权限,在onBind方法增加权限判断(checkCallingOrSelfPermission方法),不通过则返回null

   第二种,在服务端的onTransact方法中进行权限验证,失败则返回false。

   除了上述使用权限判断的方法之外,还可以采用Uid和Pid来做验证,getCallingUid和getCallingPid可以拿的客户端所属应用的Uid和Pid。并以此进一步获得包名等信息。 

public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            String packageName = null;
            String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
            if (packages != null && packages.length > 0)
                packageName = packages[0];
            if (!packageName.startsWith("/*包名前缀*/"))
                return false;
            return super.onTransact(code, data, reply, flags);
        }

 

    

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM