Android AIDL用法介绍

来源:互联网 发布:关于程序员的电影 编辑:程序博客网 时间:2024/05/17 22:37

Android AIDL用法介绍

一、简介

  1. 服务端
    服务端首先要创建一个Service来监听客户端连接请求,然后创建一个aidl文件,将接口暴露给客户端,最后在Service中实现这个aidl接口

  2. 客户端
    先绑定服务端的Service,将服务端返回的Binder对象转成aidl接口对应的类型,然后就可以调用aidl接口了

  3. AIDL接口
    并不是所有的数据类型在aidl文件中都可以使用,那aidl文件支持哪些数据类型?

    (a) 基本数据类型(int、long、char、boolean、double等)
    (b) String和CharSequence
    (c) List:只支持ArrayList,里面每个元素都必须被AIDL支持
    (d) Map:只支持HashMap,key和value都必须被AIDL支持
    (e) Parcelable:所有序列化的对象
    (f) AIDL:所有AIDL接口本身也可以在AIDL文件中使用
    (g) AIDL除了基本数据类型,其他类型参数需要标上in、out或inout,in为输入型参数,out为输出型参数
    (h) AIDL接口中只支持方法,不支持使用静态常量

二、示例

场景是客户端调用服务端aidl接口getMovieList获取影片列表,客户端与服务端位于不同进程(或应用),下面通过例子详细了解下aidl跨进程通讯的基本用法

首先创建Movie.java、Movie.aidl和IMovieMgr.aidl文件,IMovieMgr中声明getMovieList接口,代码如下

// Movie.javapublic class Movie implements Parcelable{  public String name;  public Movie(String name) {    this.name = name;  }  @Override public void writeToParcel(Parcel dest, int flags) {      dest.writeString(name);  }  @Override public int describeContents() {    return 0;  }  public static final Parcelable.Creator<Movie> CREATOR = new Parcelable.Creator<Movie>() {    @Override public Movie createFromParcel(Parcel source) {      return new Movie(source);    }    @Override public Movie[] newArray(int size) {      return new Movie[size];    }  };  private Movie(Parcel in) {    this.name = in.readString();  }}// Movie.aidlpackage com.rico.aidl;parcelable Movie;// IMovieMgr.aidlpackage com.rico.aidl;// Declare any non-default types here with import statementsimport com.rico.aidl.Movie;interface IMovieMgr {    /**     * 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<Movie> getMovieList();    void addMovie(in Movie movie);}

编译后系统自动生成IMovieMgr.java,通过对IMovieMgr.java的源码分析,可以进一步了解Binder的工作机制

接着定义服务端MovieMgrService.java,这里使用CopyOnWriteArrayList存储影片,原因是CopyOnWriteArrayList支持并发读写,aidl方法是在服务端的binder线程池中执行,当多个客户端同时连接服务端,存在多个线程同时访问资源的情形,所以我们需要在aidl方法中处理线程同步,这里直接使用CopyOnWriteArrayList来自动进行线程同步,代码如下

// AndroidManifest.xml<service android:name=".MovieMgrService" android:process=":remote"></service>// MovieMgrService.javapublic class MovieMgrService extends Service{  CopyOnWriteArrayList<Movie> mMoveList = new CopyOnWriteArrayList<>();  private Binder mBinder = new IMovieMgr.Stub() {    @Override public List<Movie> getMovieList() throws RemoteException {      System.out.println("movices return mMovieList");      return mMoveList;    }    @Override public void addMovie(Movie movie) throws RemoteException {      mMoveList.add(movie);    }    @Override    public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble,        String aString) throws RemoteException {    }  };  @Override public void onCreate() {    super.onCreate();    mMoveList.add(new Movie("钢铁侠"));    mMoveList.add(new Movie("速度激情8"));    System.out.println("movices onCreate");  }  @Nullable @Override public IBinder onBind(Intent intent) {    return mBinder;  }}

最后在客户端调用aidl接口

public class MovieMgrActivity extends Activity{  ServiceConnection mConnection = new ServiceConnection() {    @Override public void onServiceConnected(ComponentName name, IBinder service) {      IMovieMgr movieMgr = IMovieMgr.Stub.asInterface(service);      try {        List<Movie> list = movieMgr.getMovieList();        for (Movie movie : list) {          System.out.println(movie.name);        }      } catch (RemoteException e) {      }    }    @Override public void onServiceDisconnected(ComponentName name) {    }  };  @Override protected void onCreate(@Nullable Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    Intent i = new Intent(this, MovieMgrService.class);    bindService(i, mConnection, Context.BIND_AUTO_CREATE);  }  @Override protected void onDestroy() {    super.onDestroy();    unbindService(mConnection);  }}

运行一下:

07-24 00:08:50.872 21145-21145/com.rico.aidl I/System.out: 钢铁侠07-24 00:08:50.872 21145-21145/com.rico.aidl I/System.out: 速度激情8

到这里,基本用法介绍完毕,但是aidl的难点还没有涉及,下面继续分析~

服务端还提供了addMovie添加影片的接口,我们尝试在客户端添加一部影片,看看能否添加成功?只需要修改onServiceConnected接口

    @Override public void onServiceConnected(ComponentName name, IBinder service) {      IMovieMgr movieMgr = IMovieMgr.Stub.asInterface(service);      try {        System.out.println("添加一部影片:七月安生");        movieMgr.addMovie(new Movie("七月安生"));        System.out.println("影片列表:");        List<Movie> list = movieMgr.getMovieList();        for (Movie movie : list) {          System.out.println(movie.name);        }      } catch (RemoteException e) {      }    }

运行如下:

07-24 07:27:02.207 29092-29092/com.rico.aidl I/System.out: 添加一部影片:七月安生07-24 07:27:02.207 29092-29092/com.rico.aidl I/System.out: 影片列表:07-24 07:27:02.208 29092-29092/com.rico.aidl I/System.out: 钢铁侠07-24 07:27:02.208 29092-29092/com.rico.aidl I/System.out: 速度激情807-24 07:27:02.208 29092-29092/com.rico.aidl I/System.out: 七月安生

考虑一种情况,能不能在有影片的时候,自动通知客户端?这是典型的观察者模式,接下来实现这个需求

首先定义IOnNewMovieAddedListener.aidl监听器,针对注册了新影片提醒功能的客户端,服务端才会主动提醒

package com.rico.aidl;// Declare any non-default types here with import statementsimport com.rico.aidl.Movie;interface IOnNewMovieAddedListener {    void onNewMovieAdded(in Movie movie);}

接着修改客户端代码,注册监听器IOnNewMovieAddedListener,改动如下:

public class MovieMgrActivity extends Activity{  IMovieMgr movieMgr;  ServiceConnection mConnection = new ServiceConnection() {    @Override public void onServiceConnected(ComponentName name, IBinder service) {      movieMgr = IMovieMgr.Stub.asInterface(service);      try {        List<Movie> list = movieMgr.getMovieList();        for (Movie movie : list) {          System.out.println(movie.name);        }        movieMgr.registerListener(mListener);      } catch (RemoteException e) {      }    }    @Override public void onServiceDisconnected(ComponentName name) {    }  };  IOnNewMovieAddedListener mListener = new IOnNewMovieAddedListener.Stub() {    @Override public void onNewMovieAdded(Movie movie) throws RemoteException {      System.out.println("新电影【"+movie.name+"】上线了");    }  };  @Override protected void onCreate(@Nullable Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    Intent i = new Intent(this, MovieMgrService.class);    bindService(i, mConnection, Context.BIND_AUTO_CREATE);  }  @Override protected void onDestroy() {    unbindService(mConnection);    try {      if (mListener != null) {        movieMgr.unregisterListener(mListener);      }    } catch (RemoteException e) {    }    super.onDestroy();  }}

最后修改服务端代码,服务端每个5s自动生成一部新电影,并通知注册过提醒功能的客户端:

public class MovieMgrService extends Service{  CopyOnWriteArrayList<Movie> mMoveList = new CopyOnWriteArrayList<>();  CopyOnWriteArrayList<IOnNewMovieAddedListener> mListenerList = new CopyOnWriteArrayList<>();  private Binder mBinder = new IMovieMgr.Stub() {    @Override public List<Movie> getMovieList() throws RemoteException {      return mMoveList;    }    @Override public void addMovie(Movie movie) throws RemoteException {      mMoveList.add(movie);    }    @Override public void registerListener(IOnNewMovieAddedListener listener)        throws RemoteException {      if (!mListenerList.contains(listener)) {        mListenerList.add(listener);      }    }    @Override public void unregisterListener(IOnNewMovieAddedListener listener)        throws RemoteException {      if (mListenerList.contains(listener)) {        mListenerList.remove(listener);      }    }  };  private void onNewMovieAdded(Movie movie) throws RemoteException {    System.out.println("mListenerList size = "+mListenerList.size());    mMoveList.add(movie);    for (IOnNewMovieAddedListener listener:mListenerList) {      try {        listener.onNewMovieAdded(movie);      } catch (RemoteException e) {        e.printStackTrace();      }    }  }  @Override public void onCreate() {    super.onCreate();    mMoveList.add(new Movie("钢铁侠"));    mMoveList.add(new Movie("速度激情8"));    new Thread(new Runnable() {      @Override public void run() {        try {          int id = 0;          while (!isDestory) {            Thread.sleep(5000);            onNewMovieAdded(new Movie("New Movie "+id++));          }        } catch (Exception e) {          e.printStackTrace();        }      }    }).start();  }  boolean isDestory = false;  @Override public void onDestroy() {    super.onDestroy();    isDestory = true;  }  @Nullable @Override public IBinder onBind(Intent intent) {    return mBinder;  }}

运行一下:

07-24 08:22:22.931 27228-27228/com.rico.aidl I/System.out: 钢铁侠07-24 08:22:22.931 27228-27228/com.rico.aidl I/System.out: 速度激情807-24 08:22:27.930 27228-27240/com.rico.aidl I/System.out: 新电影【New Movie 0】上线了07-24 08:22:32.932 27228-27241/com.rico.aidl I/System.out: 新电影【New Movie 1】上线了07-24 08:22:37.935 27228-27240/com.rico.aidl I/System.out: 新电影【New Movie 2】上线了07-24 08:22:42.939 27228-27241/com.rico.aidl I/System.out: 新电影【New Movie 3】上线了

到这里还没完,当我们按返回键关闭页面的时候,理论上在onDestory中调用movieMgr.unregisterListener解除绑定,可是日志显示:

07-24 08:37:29.020 9341-9353/com.rico.aidl:remote I/System.out: not found, can't unregister

为什么解绑失败了?

在解绑过程中服务端无法找到之前注册的listener,在客户端我们注册和解绑明明用的是同一个listener,为什么服务端无法正常解绑?这种注册解绑的方式是对的,但在多进程中却不适用了,原因是Binder会把客户端传递过来的重新转换成新的对象,服务端接收到的对象已经不是客户端传递的对象了,对象跨进程传输的本质是反序列化的过程,这也是对象为什么需要实现Parcelable接口的原因。

解决方法:使用RemoteCallbackList

RemoteCallbackList是系统专门提供用于删除跨进程listener的接口,虽然跨进程传输客户端的同一个对象会在服务端产生不同对象,但这些新的对象有一个共同点就是他们底层的Binder对象是同一个,我们只要遍历服务端所有的listener,找出那个和解注册listener具有相同binder对象的服务端的listener并删除即可,这些事情RemoteCallbackList都替我们完成了,并且RemoteCallbackList内部还实现了线程同步的功能。

修改服务端代码:

// MovieMgrService.java  RemoteCallbackList<IOnNewMovieAddedListener> mListenerList = new RemoteCallbackList<>();  private Binder mBinder = new IMovieMgr.Stub() {    @Override public List<Movie> getMovieList() throws RemoteException {      return mMoveList;    }    @Override public void addMovie(Movie movie) throws RemoteException {      mMoveList.add(movie);    }    @Override public void registerListener(IOnNewMovieAddedListener listener)        throws RemoteException {      mListenerList.register(listener);    }    @Override public void unregisterListener(IOnNewMovieAddedListener listener)        throws RemoteException {      mListenerList.unregister(listener);    }  };  private void onNewMovieAdded(Movie movie) throws RemoteException {    mMoveList.add(movie);    int num = mListenerList.beginBroadcast();    for (int i=0; i<num; i++) {      IOnNewMovieAddedListener l = mListenerList.getBroadcastItem(i);      if (l != null) {        l.onNewMovieAdded(movie);      }    }    mListenerList.finishBroadcast();  }

aidl的使用介绍完了,最后在强调一下,客户端调用远程的aidl接口,由于被调用的方法运行在服务端binder线程池中,同时客户端被挂起,如果服务端执行了耗时操作,客户端会出现ANR问题,所以不要在UI线程中访问aidl接口~

深入理解aidl,可以阅读之前的博客
Android Binder通讯机制和Android多进程模式


参考【Android开发艺术探索】