大话android 进程通信之AIDL

来源:互联网 发布:mac基于linux还是unix 编辑:程序博客网 时间:2024/06/15 02:52

上一篇的service涉及到进程通信问题,主要解决办法是通过 messenger来发送消息,这也是Google推荐的进程通信方式,比较简单易懂嘛~~,messenger底层也是通过binder来实现的,对于binder,这里就不做介绍了。但是如果允许不同应用的客户端用 IPC 方式访问服务、在服务中处理多线程就不太适合了,还是得乖乖用AIDL,AIDL这玩意,估计不少人都会有点陌生吧,接来下就通过跨应用对书本进行增删查,来讲解如何通过AIDL实现 跨进程通信。

一、定义AIDL接口

在开始设计 AIDL 接口之前,要注意 AIDL 接口的调用是直接函数调用。不应该假设发生调用的线程。视调用来自本地进程还是远程进程中的线程,实际情况会有所差异。具体而言:

  • 来自本地进程的调用在发起调用的同一线程内执行。如果该线程是主 UI 线程,则该线程继续在 AIDL 接口中执行。如果该线程是其他线程,则其便是在服务中执行代码的线程。因此,只有在本地线程访问服务时,我们才能完全控制哪些线程在服务中执行(但如果真是这种情况,根本不应该使用 AIDL,而是应该通过实现 Binder 类创建接口)。
  • 来自远程进程的调用分派自平台在自有进程内部维护的线程池。必须为来自未知线程的多次并发传入调用做好准备。换言之,AIDL 接口的实现必须是完全线程安全实现。
  • oneway 关键字用于修改远程调用的行为。使用该关键字时,远程调用不会阻塞;它只是发送事务数据并立即返回。接口的实现最终接收此调用时,是以正常远程调用形式将其作为来自Binder 线程池的常规调用进行接收。如果oneway 用于本地调用,则不会有任何影响,调用仍是同步调用。 
我们一般会定义一个AIDL文件(当然,也可以通过实现binder类来自己创建接口,不过暂时没那必要)。首先创建两个应用client端和service端,其中client端主要用于查询,增加数据,service端则负责实现数据存储。
先看service端,我们在src\main目录下创建aidl 包,在aidl下再创建一个目录来存放接口,同时在java目录下创建相同的目录,总体目录如下:

我们先来看aidl路径下的IBookManager.aidl 接口,很简单,就定义一个接口供client调用
package com.example.aidl;import com.example.aidl.Book;interface IBookManager {     List<Book> getBookList();     void addBook(in Book book);     boolean removeBook(in Book book);}
在AIDl文件中可以使用的数据类型有:
  • Java 编程语言中的所有原语类型(如 intlongcharboolean 等等)
  • String
  • CharSequence
  • List

    List 中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或您声明的可打包类型。可选择将List 用作“通用”类(例如,List<String>)。另一端实际接收的具体类始终是ArrayList,但生成的方法使用的是List 接口。

  • Map

    Map 中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或您声明的可打包类型。不支持通用 Map(如Map<String,Integer> 形式的 Map)。另一端实际接收的具体类始终是HashMap,但生成的方法使用的是Map 接口。

我们注意到这里的接口方法比一般的Java接口多了一个“in” ,在AIDl文件中规定除了基本数据类型外,其他数据必须表上方向:in ,out或者inout,in代表输入型参数,out代表输出型参数,inout 代表输入输出型参数。AIDL接口只支持方法,不支持声明静态常量。
由于我们使用到book这个对象,因此,需要声明该对象的AIDL文件,并在AIDL接口里面手动导入该文件,该文件声明如下
package com.example.aidl;parcelable Book;
转到Java目录下,在相同的包下创建一个book对象,并使其实现Parcelable接口(AIDL要求接口中的类必须要实现Parcelable接口,并在AIDL目录下声明该类为parcelable,如上所示)
public class Book implements Parcelable {    public int bookId;    public String bookName;    public Book(int bookId, String bookName) {        this.bookId = bookId;        this.bookName = bookName ;    }    private Book(Parcel parcel){        bookId = parcel.readInt();        bookName = parcel.readString() ;    }    @Override    public int describeContents() {        return 0;    }    @Override    public void writeToParcel(Parcel dest, int flags) {      dest.writeInt(bookId);        dest.writeString(bookName);    }    public static final Creator<Book> CREATOR = new Creator<Book>(){        @Override        public Book createFromParcel(Parcel parcel) {            return new Book(parcel);        }        @Override        public Book[] newArray(int i) {            return new Book[i];        }    };}

关键来了!!(敲黑板)   准备工作做完后,我们就可以生成相应的binder类了,选择build->make project即可,gradle会自动帮我们创建一个IBookManager的Java接口,生成的接口包括一个名为 Stub 的子类,这个子类是其父接口(例如,IBookManager.Stub)的抽象实现,用于声明 .aidl 文件中的所有方法。

二、实现AIDL接口

我们创建一个BookManagerService 并继承service,创建一个binder对象
   private Binder binder = new IBookManager.Stub(){        @Override        public List<Book> getBookList() throws RemoteException {            return null;        }        @Override        public void addBook(Book book) throws RemoteException {        }        @Override        public boolean removeBook(Book book) throws RemoteException {                return false ;        }    };

现在,binder 是 Stub 类的一个实例,用于定义服务的 RPC 接口。在下一步中,将向客户端公开该实例,以便客户端能与服务进行交互。

在实现 AIDL 接口时应注意遵守以下这几个规则:

  • 由于不能保证在主线程上执行传入调用,因此一开始就需要做好多线程处理准备,并将服务正确地编译为线程安全服务。
  • 默认情况下,RPC 调用是同步调用。如果明知服务完成请求的时间不止几毫秒,就不应该从Activity的主线程调用服务,因为这样做可能会使应用挂起(Android可能会显示“Application is Not Responding”对话框)— 我们通常应该从客户端内的单独线程调用服务。
  • 在跨进程通信引发的任何异常都不会回传给调用方。

三、向client端公开该接口

我们通过重写onBind 方法来返回该bind接口
   @Override    public IBinder onBind(Intent intent) {        Log.i(TAG, "onBind: ");        return binder;    }
现在,当客户端(如 Activity)调用 bindService() 以连接此服务时,客户端的 onServiceConnected() 回调会接收服务的 onBind() 方法返回的 binder实例。
最后我们在配置文件中声明该service,并设置其可以被其他应用调用
        <service android:name="com.example.aidl.BookManagerService"            android:enabled="true"            android:exported="true" />
至此,service端的准备工作就基本完成了,再来看看client端的

四、client端的实现

同service端一样,我们需要在client端创建一样的路径(因为底层采用对象的反序列化,路径必须一致),直接从service端copy一份过来即可,点击make project 创建对应的Java接口类,client端的准备工作就好了

我们来看 MainActivity,先是绑定service端的服务
   public void bindService(View view){        Intent intent = new Intent() ;        intent.setComponent(new                ComponentName("com.ujs.service"                ,"com.example.aidl.BookManagerService"));        bindService(intent,serviceConnection, Context.BIND_AUTO_CREATE);    }
这里我们用到intent.setComponent()方法,这是Android为了方便启动其他应用的组件而提供的,ComponentName有两个参数,第一个是目标应用的包名,第二个参数是目标组件的详细路径,然后我们传入一个serviceconnection,在onServiceConnected方法中获取service端的接口,这样我们就可以愉快的通过这个接口与service通信了
   private ServiceConnection serviceConnection = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            Log.i(TAG, "onServiceConnected: -  ");            iBookManager = IBookManager.Stub.asInterface(service);            try {                List<Book> list = iBookManager.getBookList();                for(Book b:list){                    Log.i(TAG, "onServiceConnected: "+b.bookName);                    Log.i(TAG, "onServiceConnected: "+b.bookId);                }            } catch (Exception e) {                e.printStackTrace();            }        }        @Override        public void onServiceDisconnected(ComponentName name) {            Log.i(TAG, "onServiceDisconnected: ");        }    };
我们可以通知service添加一本书
   public void addBook(View view){        try {            Book web = new Book(1,"web App");            iBookManager.addBook(web);            Log.i(TAG, "addBook: "+web.toString());            Log.i(TAG, "addBook: 添加数据了");        } catch (RemoteException e) {            e.printStackTrace();        }    }
从service端获取所有书本信息
    public void upDate(View view){        try {            List<Book> list = iBookManager.getBookList();            for(Book book : list){                Log.i(TAG, "\nbook name : "+book.bookName+" book id :"+book.bookId);            }        } catch (RemoteException e) {            e.printStackTrace();        }    }
通知service端删除一本书,并获取返回值
    public void remove(View view){        try {            boolean b = iBookManager.removeBook(web);            if(b)                Log.i(TAG, "remove: service 删除数据了");            else                Log.i(TAG, "remove: 删除失败了");        } catch (RemoteException e) {            e.printStackTrace();        }    }

至此,基于AIDL进程通信就打通了,掌握这些就满足基本的进程通信需求了~~
附上源码






原创粉丝点击