Android AIDL用法介绍

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.java
public 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.aidl
package com.rico.aidl;
parcelable Movie;

// IMovieMgr.aidl
package com.rico.aidl;

// Declare any non-default types here with import statements
import 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.java
public 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: 速度激情8
07-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 statements
import 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: 速度激情8
07-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开发艺术探索】

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

chengyuan9160

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值