大话android 进程通信之AIDL

上一篇的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。
      1)in代表输入型参数(服务端将会收到客户端对象的 完整数据,但是客户端对象 不会因为服务端对传参的修改而发生变动
      2)out代表输出型参数(服务端将会收到 客户端对象,该 对象不为空,但是它里面的 字段为空,但是在服务端对该对象作任何修改之后客户端的传参对象都会 同步改动
      3)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进程通信就打通了,掌握这些就满足基本的进程通信需求了~~
 
 
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值