aidl_aidlearning教程 - CSDN
aidl 订阅
AIDL:Android Interface Definition Language,即Android接口定义语言。 展开全文
AIDL:Android Interface Definition Language,即Android接口定义语言。
信息
外文名
Android Interface Definition Language
简    称
aidl
中文名
Android接口定义语言
系    统
Android
aidl什么是AIDL
Android系统中的进程之间不能共享内存,因此,需要提供一些机制在不同进程之间进行数据通信。为了使其他的应用程序也可以访问本应用程序提供的服务,Android系统采用了远程过程调用(Remote Procedure Call,RPC)方式来实现。与很多其他的基于RPC的解决方案一样,Android使用一种接口定义语言(Interface Definition Language,IDL)来公开服务的接口。我们知道4个Android应用程序组件中的3个(Activity、BroadcastReceiver和ContentProvider)都可以进行跨进程访问,另外一个Android应用程序组件Service同样可以。因此,可以将这种可以跨进程访问的服务称为AIDL(Android Interface Definition Language)服务。
收起全文
精华内容
参与话题
  • Android AIDL使用详解

    万次阅读 多人点赞 2011-05-16 17:56:00
    什么是aidl:aidl是 Android Interface definition language的缩写,一看就明白,它是一种android内部进程通信接口的描述语言,通过它我们可以定义进程间的通信接口 icp:interprocess communication :内部进程...

    1.什么是aidl:aidl是 Android Interface definition language的缩写,一看就明白,它是一种android内部进程通信接口的描述语言,通过它我们可以定义进程间的通信接口
    icp:interprocess communication :内部进程通信

     

    2.既然aidl可以定义并实现进程通信,那么我们怎么使用它呢?文档/android-sdk/docs/guide/developing/tools/aidl.html中对步骤作了详细描述:

    --1.Create your .aidl file - This file defines an interface (YourInterface.aidl) that defines the methods and fields available to a client.
    创建你的aidl文件,我在后面给出了一个例子,它的aidl文件定义如下:写法跟java代码类似,但是这里有一点值得注意的就是它可以引用其它aidl文件中定义的接口,但是不能够引用你的java类文件中定义的接口

    --2.Add the .aidl file to your makefile - (the ADT Plugin for Eclipse manages this for you). Android includes the compiler, called AIDL, in the tools/ directory.
    编译你的aidl文件,这个只要是在eclipse中开发,你的adt插件会像资源文件一样把aidl文件编译成java代码生成在gen文件夹下,不用手动去编译:编译生成AIDLService.java如我例子中代码


    --3.Implement your interface methods - The AIDL compiler creates an interface in the Java programming language from your AIDL interface. This interface has an inner abstract class named Stub that inherits the interface (and implements a few additional methods necessary for the IPC call). You must create a class that extends YourInterface.Stub and implements the methods you declared in your .aidl file.
    实现你定义aidl接口中的内部抽象类Stub,public static abstract class Stub extends android.os.Binder implements com.cao.android.demos.binder.aidl.AIDLService
    Stub类继承了Binder,并继承我们在aidl文件中定义的接口,我们需要实现接口方法,下面是我在例子中实现的Stub类:
     

    Stub翻译成中文是存根的意思,注意Stub对象是在被调用端进程,也就是服务端进程,至此,服务端aidl服务端得编码完成了。

    --4.Expose your interface to clients - If you're writing a service, you should extend Service and override Service.onBind(Intent) to return an instance of your class that implements your interface.
    第四步告诉你怎么在客户端如何调用服务端得aidl描述的接口对象,doc只告诉我们需要实现Service.onBind(Intent)方法,该方法会返回一个IBinder对象到客户端,绑定服务时不是需要一个ServiceConnection对象么,在没有了解aidl用法前一直不知道它是什么作用,其实他就是用来在客户端绑定service时接收service返回的IBinder对象的:

     

    mService就是AIDLService对象,具体可以看我后面提供的示例代码,需要注意在客户端需要存一个服务端实现了的aidl接口描述文件,但是客户端只是使用该aidl接口,不需要实现它的Stub类,获取服务端得aidl对象后mService = AIDLService.Stub.asInterface(service);,就可以在客户端使用它了,对mService对象方法的调用不是在客户端执行,而是在服务端执行。

    4.aidl中使用java类,需要实现Parcelable接口,并且在定义类相同包下面对类进行声明:

    上面我定义了Rect1类
    之后你就可以在aidl接口中对该类进行使用了
    package com.cao.android.demos.binder.aidl; 
    import com.cao.android.demos.binder.aidl.Rect1;
    interface AIDLActivity {  
        void performAction(in Rect1 rect);  

    注意in/out的说明,我这里使用了in表示输入参数,out没有试过,为什么使用in/out暂时没有做深入研究。

    5.aidl使用完整示例,为了清除说明aidl使用,我这里写了一个例子,例子参考了博客:

    http://blog.csdn.net/saintswordsman/archive/2010/01/04/5130947.aspx

    作出说明

    例子实现了一个AIDLTestActivity,AIDLTestActivity通过bindservice绑定一个服务AIDLTestService,通过并获取AIDLTestActivity的一个aidl对象AIDLService,该对象提供两个方法,一个是registerTestCall注册一个aidl对象,通过该方法,AIDLTestActivity把本身实现的一个aidl对象AIDLActivity传到AIDLTestService,在AIDLTestService通过操作AIDLActivity这个aidl远端对象代理,使AIDLTestActivity弹出一个toast,完整例子见我上传的资源:

    http://download.csdn.net/source/3284820

    文章仓促而成,有什么疑问欢迎大家一起讨论。

     

    展开全文
  • Android:学习AIDL,这一篇文章就够了(上)

    万次阅读 多人点赞 2016-12-01 22:09:54
    前言在决定用这个标题之前甚是忐忑,主要是担心自己对AIDL的理解不够深入,到时候大家看了之后说——你这是什么玩意儿,就这么点东西就敢说够了?简直是坐井观天不知所谓——那样就很尴尬了。不过又转念一想,我辈...

    前言

    在决定用这个标题之前甚是忐忑,主要是担心自己对AIDL的理解不够深入,到时候大家看了之后说——你这是什么玩意儿,就这么点东西就敢说够了?简直是坐井观天不知所谓——那样就很尴尬了。不过又转念一想,我辈年轻人自当有一种一往无前的锐气,标题大气一点岂不更好?并且大家都是文明人,总归更多的是理解与补充而不是侮辱与谩骂?所以最终还是厚颜用了这么一个不怎么有耻的标题。

    好了,接下来进入正题,谈谈我对AIDL的理解和认识。

    正文

    1,概述

    AIDL是一个缩写,全称是Android Interface Definition Language,也就是Android接口定义语言。是的,首先我们知道的第一点就是:AIDL是一种语言。既然是一种语言,那么相应的就很自然的衍生出了一些问题:

    • 为什么要设计出这么一门语言?
    • 它有哪些语法?
    • 我们应该如何使用它?
    • 再深入一点,我们可以思考,我们是如何通过它来达到我们的目的的?
    • 更深入一点,为什么要这么设计这门语言?会不会有更好的方式来实现我们的目的?

    接下来,我们就一步步的来解答上面的这些问题。

    ps:1,在研究AIDL相关的东西之前,一些必要的知识储备是要有的。一方面是关于Android中service相关的知识,要了解的比较通透才行,关于这方面的东西可以参考 Android中的Service:默默的奉献者 (1)Android中的Service:Binder,Messenger,AIDL(2) 这两篇博文。另一方面是关于Android中序列化的相关知识,这方面的东西文中会简单提及,但是如果想要深入的研究一下的话最好还是去找一些这方面的资料看一下。 2,我的编译环境为Android Studio2.1.2,SDK Version 23,JDK 1.7。

    2,为什么要设计这门语言?

    设计这门语言的目的是为了实现进程间通信,尤其是在涉及多进程并发情况下的进程间通信

    每一个进程都有自己的Dalvik VM实例,都有自己的一块独立的内存,都在自己的内存上存储自己的数据,执行着自己的操作,都在自己的那片狭小的空间里过完自己的一生。每个进程之间都你不知我,我不知你,就像是隔江相望的两座小岛一样,都在同一个世界里,但又各自有着自己的世界。而AIDL,就是两座小岛之间沟通的桥梁。相对于它们而言,我们就好像造物主一样,我们可以通过AIDL来制定一些规则,规定它们能进行哪些交流——比如,它们可以在我们制定的规则下传输一些特定规格的数据。

    总之,通过这门语言,我们可以愉快的在一个进程访问另一个进程的数据,甚至调用它的一些方法,当然,只能是特定的方法。

    但是,如果仅仅是要进行跨进程通信的话,其实我们还有其他的一些选择,比如 BroadcastReceiver , Messenger 等,但是 BroadcastReceiver 占用的系统资源比较多,如果是频繁的跨进程通信的话显然是不可取的;Messenger 进行跨进程通信时请求队列是同步进行的,无法并发执行,在有些要求多进程的情况下不适用——这种时候就需要使用 AIDL 了。如果想要了解它们更详细的区别的话,可以去我的另一篇博文看看:Android中的Service:Binder,Messenger,AIDL(2)

    3,它有哪些语法?

    其实AIDL这门语言非常的简单,基本上它的语法和 Java 是一样的,只是在一些细微处有些许差别——毕竟它只是被创造出来简化Android程序员工作的,太复杂不好——所以在这里我就着重的说一下它和 Java 不一样的地方。主要有下面这些点:

    • 文件类型:用AIDL书写的文件的后缀是 .aidl,而不是 .java。
    • 数据类型:AIDL默认支持一些数据类型,在使用这些数据类型的时候是不需要导包的,但是除了这些类型之外的数据类型,在使用之前必须导包,就算目标文件与当前正在编写的 .aidl 文件在同一个包下——在 Java 中,这种情况是不需要导包的。比如,现在我们编写了两个文件,一个叫做 Book.java ,另一个叫做 BookManager.aidl,它们都在 com.lypeer.aidldemo 包下 ,现在我们需要在 .aidl 文件里使用 Book 对象,那么我们就必须在 .aidl 文件里面写上 import com.lypeer.aidldemo.Book; 哪怕 .java 文件和 .aidl 文件就在一个包下。
      默认支持的数据类型包括:
      • Java中的八种基本数据类型,包括 byte,short,int,long,float,double,boolean,char。
      • String 类型。
      • CharSequence类型。
      • List类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable(下文关于这个会有详解)。List可以使用泛型。
      • Map类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。Map是不支持泛型的。
    • 定向tag:这是一个极易被忽略的点——这里的“被忽略”指的不是大家都不知道,而是很少人会正确的使用它。在我的理解里,定向 tag 是这样的:AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。其中,数据流向是针对在客户端中的那个传入方法的对象而言的。in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。具体的分析大家可以移步我的另一篇博文:你真的理解AIDL中的in,out,inout么?
      另外,Java 中的基本类型和 String ,CharSequence 的定向 tag 默认且只能是 in 。还有,请注意,请不要滥用定向 tag ,而是要根据需要选取合适的——要是不管三七二十一,全都一上来就用 inout ,等工程大了系统的开销就会大很多——因为排列整理参数的开销是很昂贵的。
    • 两种AIDL文件:在我的理解里,所有的AIDL文件大致可以分为两类。一类是用来定义parcelable对象,以供其他AIDL文件使用AIDL中非默认支持的数据类型的。一类是用来定义方法接口,以供系统使用来完成跨进程通信的。可以看到,两类文件都是在“定义”些什么,而不涉及具体的实现,这就是为什么它叫做“Android接口定义语言”。
      注:所有的非默认支持数据类型必须通过第一类AIDL文件定义才能被使用。

    下面是两个例子,对于常见的AIDL文件都有所涉及:

    // Book.aidl
    //第一类AIDL文件的例子
    //这个文件的作用是引入了一个序列化对象 Book 供其他的AIDL文件使用
    //注意:Book.aidl与Book.java的包名应当是一样的
    package com.lypeer.ipcclient;
    
    //注意parcelable是小写
    parcelable Book;
    // BookManager.aidl
    //第二类AIDL文件的例子
    package com.lypeer.ipcclient;
    //导入所需要使用的非默认支持数据类型的包
    import com.lypeer.ipcclient.Book;
    
    interface BookManager {
    
        //所有的返回值前都不需要加任何东西,不管是什么数据类型
        List<Book> getBooks();
        Book getBook();
        int getBookCount();
    
        //传参时除了Java基本类型以及String,CharSequence之外的类型
        //都需要在前面加上定向tag,具体加什么量需而定
        void setBookPrice(in Book book , int price)
        void setBookName(in Book book , String name)
        void addBookIn(in Book book);
        void addBookOut(out Book book);
        void addBookInout(inout Book book);
    }

    4,如何使用AIDL文件来完成跨进程通信?

    在进行跨进程通信的时候,在AIDL中定义的方法里包含非默认支持的数据类型与否,我们要进行的操作是不一样的。如果不包含,那么我们只需要编写一个AIDL文件,如果包含,那么我们通常需要写 n+1 个AIDL文件( n 为非默认支持的数据类型的种类数)——显然,包含的情况要复杂一些。所以我接下来将只介绍AIDL文件中包含非默认支持的数据类型的情况,至于另一种简单些的情况相信大家是很容易从中触类旁通的。

    4.1,使数据类实现 Parcelable 接口

    由于不同的进程有着不同的内存区域,并且它们只能访问自己的那一块内存区域,所以我们不能像平时那样,传一个句柄过去就完事了——句柄指向的是一个内存区域,现在目标进程根本不能访问源进程的内存,那把它传过去又有什么用呢?所以我们必须将要传输的数据转化为能够在内存之间流通的形式。这个转化的过程就叫做序列化与反序列化。简单来说是这样的:比如现在我们要将一个对象的数据从客户端传到服务端去,我们就可以在客户端对这个对象进行序列化的操作,将其中包含的数据转化为序列化流,然后将这个序列化流传输到服务端的内存中去,再在服务端对这个数据流进行反序列化的操作,从而还原其中包含的数据——通过这种方式,我们就达到了在一个进程中访问另一个进程的数据的目的。

    而通常,在我们通过AIDL进行跨进程通信的时候,选择的序列化方式是实现 Parcelable 接口。关于实现 Parcelable 接口之后里面具体有那些方法啦,每个方法是干嘛的啦,这些我就不展开来讲了,那並非这篇文章的重点,我下面主要讲一下如何快速的生成一个合格的可序列化的类(以Book.java为例)。

    注:若AIDL文件中涉及到的所有数据类型均为默认支持的数据类型,则无此步骤。因为默认支持的那些数据类型都是可序列化的。

    4.1.1,编译器自动生成

    我当前用的编译器是Android Studio 2.1.2,它是自带了 Parcelable 接口的模板的,只需要我们敲几下键盘就可以轻松的生成一个可序列化的 Parcelable 实现类。

    首先,创建一个类,正常的书写其成员变量,建立getter和setter并添加一个无参构造,比如:

    public class Book{
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getPrice() {
            return price;
        }
    
        public void setPrice(int price) {
            this.price = price;
        }
    
        private String name;
    
        private int price;
    
        public Book() {}
    
    }

    然后 implements Parcelable ,接着 as 就会报错,将鼠标移到那里,按下 alt+enter(as默认的自动解决错误的快捷键,如果你们的as有修改过快捷键的话以修改后的为准) 让它自动解决错误,这个时候它会帮你完成一部分的工作:

    Parcelable实现第一步

    在弹出来的框里选择所有的成员变量,然后确定。你会发现类里多了一些代码,但是现在还是会报错,Book下面仍然有一条小横线,再次将鼠标移到那里,按下 alt+enter 让它自动解决错误:

    Parcelable实现第二步

    这次解决完错误之后就不会报错了,这个 Book 类也基本上实现了 Parcelable 接口,可以执行序列化操作了。

    但是请注意,这里有一个坑:默认生成的模板类的对象只支持为 in 的定向 tag 。为什么呢?因为默认生成的类里面只有 writeToParcel() 方法,而如果要支持为 out 或者 inout 的定向 tag 的话,还需要实现 readFromParcel() 方法——而这个方法其实并没有在 Parcelable 接口里面,所以需要我们从头写。具体为什么大家可以去看看:你真的理解AIDL中的in,out,inout么?

    那么这个 readFromParcel() 方法应当怎么写呢?这样写:

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(price);
    }
    
    /**
     * 参数是一个Parcel,用它来存储与传输数据
     * @param dest
     */
    public void readFromParcel(Parcel dest) {
        //注意,此处的读值顺序应当是和writeToParcel()方法中一致的
        name = dest.readString();
        price = dest.readInt();
    }

    像上面这样添加了 readFromParcel() 方法之后,我们的 Book 类的对象在AIDL文件里就可以用 out 或者 inout 来作为它的定向 tag 了。

    此时,完整的 Book 类的代码是这样的:

    package com.lypeer.ipcclient;
    
    import android.os.Parcel;
    import android.os.Parcelable;
    
    /**
     * Book.java
     *
     * Created by lypeer on 2016/7/16.
     */
    public class Book implements Parcelable{
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getPrice() {
            return price;
        }
    
        public void setPrice(int price) {
            this.price = price;
        }
    
        private String name;
        private int price;
        public Book(){}
    
        public Book(Parcel in) {
            name = in.readString();
            price = in.readInt();
        }
    
        public static final Creator<Book> CREATOR = new Creator<Book>() {
            @Override
            public Book createFromParcel(Parcel in) {
                return new Book(in);
            }
    
            @Override
            public Book[] newArray(int size) {
                return new Book[size];
            }
        };
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(name);
            dest.writeInt(price);
        }
    
        /**
         * 参数是一个Parcel,用它来存储与传输数据
         * @param dest
         */
        public void readFromParcel(Parcel dest) {
            //注意,此处的读值顺序应当是和writeToParcel()方法中一致的
            name = dest.readString();
            price = dest.readInt();
        }
    
        //方便打印数据
        @Override
        public String toString() {
            return "name : " + name + " , price : " + price;
        }
    }

    至此,关于AIDL中非默认支持数据类型的序列化操作就完成了。

    4.1.2,插件生成

    我不是很清楚 Eclipse 或者较低版本的 as 上会不会像 as 2.1.2 这样帮我们在实现 Parcelable 接口的过程中做如此多的操作,但是就算不会,我们还有其他的招数——通过插件来帮我们实现 Parcelable 接口。

    具体的实现方式和实现过程大家可以参见这篇文章:告别手写parcelable

    4.2,书写AIDL文件

    首先我们需要一个 Book.aidl 文件来将 Book 类引入使得其他的 AIDL 文件其中可以使用 Book 对象。那么第一步,如何新建一个 AIDL 文件呢?Android Studio已经帮我们把这个集成进去了:

    新建AIDL文件

    鼠标移到app上面去,点击右键,然后 new->AIDL->AIDL File,按下鼠标左键就会弹出一个框提示生成AIDL文件了。生成AIDL文件之后,项目的目录会变成这样的:

    建立AIDL文件后的项目目录

    比起以前多了一个叫做 aidl 的包,而且他的层级是和 java 包相同的,并且 aidl 包里默认有着和 java 包里默认的包结构。那么如果你用的是 Eclipse 或者较低版本的 as ,编译器没有这个选项怎么办呢?没关系,我们也可以自己写。打开项目文件夹,依次进入 app->src->main,在 main 包下新建一个和 java 文件夹平级的 aidl 文件夹,然后我们手动在这个文件夹里面新建和 java 文件夹里面的默认结构一样的文件夹结构,再在最里层新建 .aidl 文件就可以了:

    自己新建AIDL文件的目录

    注意看图中的文件目录。

    Ok,如何新建AIDL文件说的差不多了,接下来就该写AIDL文件的内容了。内容的话如果上一节有认真看的话基本上是没什么问题的。在这里,我们需要两个AIDL文件,我是这样写的:

    // Book.aidl
    //第一类AIDL文件
    //这个文件的作用是引入了一个序列化对象 Book 供其他的AIDL文件使用
    //注意:Book.aidl与Book.java的包名应当是一样的
    package com.lypeer.ipcclient;
    
    //注意parcelable是小写
    parcelable Book;
    // BookManager.aidl
    //第二类AIDL文件
    //作用是定义方法接口
    package com.lypeer.ipcclient;
    //导入所需要使用的非默认支持数据类型的包
    import com.lypeer.ipcclient.Book;
    
    interface BookManager {
    
        //所有的返回值前都不需要加任何东西,不管是什么数据类型
        List<Book> getBooks();
    
        //传参时除了Java基本类型以及String,CharSequence之外的类型
        //都需要在前面加上定向tag,具体加什么量需而定
        void addBook(in Book book);
    }

    注意:这里又有一个坑!大家可能注意到了,在 Book.aidl 文件中,我一直在强调:Book.aidl与Book.java的包名应当是一样的。这似乎理所当然的意味着这两个文件应当是在同一个包里面的——事实上,很多比较老的文章里就是这样说的,他们说最好都在 aidl 包里同一个包下,方便移植——然而在 Android Studio 里并不是这样。如果这样做的话,系统根本就找不到 Book.java 文件,从而在其他的AIDL文件里面使用 Book 对象的时候会报 Symbol not found 的错误。为什么会这样呢?因为 Gradle 。大家都知道,Android Studio 是默认使用 Gradle 来构建 Android 项目的,而 Gradle 在构建项目的时候会通过 sourceSets 来配置不同文件的访问路径,从而加快查找速度——问题就出在这里。Gradle 默认是将 java 代码的访问路径设置在 java 包下的,这样一来,如果 java 文件是放在 aidl 包下的话那么理所当然系统是找不到这个 java 文件的。那应该怎么办呢?

    又要 java文件和 aidl 文件的包名是一样的,又要能找到这个 java 文件——那么仔细想一下的话,其实解决方法是很显而易见的。首先我们可以把问题转化成:如何在保证两个文件包名一样的情况下,让系统能够找到我们的 java 文件?这样一来思路就很明确了:要么让系统来 aidl 包里面来找 java 文件,要么把 java 文件放到系统能找到的地方去,也即放到 java 包里面去。接下来我详细的讲一下这两种方式具体应该怎么做:

    • 修改 build.gradle 文件:在 android{} 中间加上下面的内容:
    sourceSets {
        main {
            java.srcDirs = ['src/main/java', 'src/main/aidl']
        }
    }

    也就是把 java 代码的访问路径设置成了 java 包和 aidl 包,这样一来系统就会到 aidl 包里面去查找 java 文件,也就达到了我们的目的。只是有一点,这样设置后 Android Studio 中的项目目录会有一些改变,我感觉改得挺难看的。

    • 把 java 文件放到 java 包下去:把 Book.java 放到 java 包里任意一个包下,保持其包名不变,与 Book.aidl 一致。只要它的包名不变,Book.aidl 就能找到 Book.java ,而只要 Book.java 在 java 包下,那么系统也是能找到它的。但是这样做的话也有一个问题,就是在移植相关 .aidl 文件和 .java 文件的时候没那么方便,不能直接把整个 aidl 文件夹拿过去完事儿了,还要单独将 .java 文件放到 java 文件夹里去。

    我们可以用上面两个方法之一来解决找不到 .java 文件的坑,具体用哪个就看大家怎么选了,反正都挺简单的。

    到这里我们就已经将AIDL文件新建并且书写完毕了,clean 一下项目,如果没有报错,这一块就算是大功告成了。

    4.3,移植相关文件

    我们需要保证,在客户端和服务端中都有我们需要用到的 .aidl 文件和其中涉及到的 .java 文件,因此不管在哪一端写的这些东西,写完之后我们都要把这些文件复制到另一端去。如果是用的上面两个方法中的第一个解决的找不到 .java 文件的问题,那么直接将 aidl 包复制到另一端的 main 目录下就可以了;如果是使用第二个方法的话,就除了把把整个 aidl 文件夹拿过去,还要单独将 .java 文件放到 java 文件夹里去。

    4.4,编写服务端代码

    通过上面几步,我们已经完成了AIDL及其相关文件的全部内容,那么我们究竟应该如何利用这些东西来进行跨进程通信呢?其实,在我们写完AIDL文件并 clean 或者 rebuild 项目之后,编译器会根据AIDL文件为我们生成一个与AIDL文件同名的 .java 文件,这个 .java 文件才是与我们的跨进程通信密切相关的东西。事实上,基本的操作流程就是:在服务端实现AIDL中定义的方法接口的具体逻辑,然后在客户端调用这些方法接口,从而达到跨进程通信的目的。

    接下来我直接贴上我写的服务端代码:

    /**
     * 服务端的AIDLService.java
     * <p/>
     * Created by lypeer on 2016/7/17.
     */
    public class AIDLService extends Service {
    
        public final String TAG = this.getClass().getSimpleName();
    
        //包含Book对象的list
        private List<Book> mBooks = new ArrayList<>();
    
        //由AIDL文件生成的BookManager
        private final BookManager.Stub mBookManager = new BookManager.Stub() {
            @Override
            public List<Book> getBooks() throws RemoteException {
                synchronized (this) {
                    Log.e(TAG, "invoking getBooks() method , now the list is : " + mBooks.toString());
                    if (mBooks != null) {
                        return mBooks;
                    }
                    return new ArrayList<>();
                }
            }
    
    
            @Override
            public void addBook(Book book) throws RemoteException {
                synchronized (this) {
                    if (mBooks == null) {
                        mBooks = new ArrayList<>();
                    }
                    if (book == null) {
                        Log.e(TAG, "Book is null in In");
                        book = new Book();
                    }
                    //尝试修改book的参数,主要是为了观察其到客户端的反馈
                    book.setPrice(2333);
                    if (!mBooks.contains(book)) {
                        mBooks.add(book);
                    }
                    //打印mBooks列表,观察客户端传过来的值
                    Log.e(TAG, "invoking addBooks() method , now the list is : " + mBooks.toString());
                }
            }
        };
    
        @Override
        public void onCreate() {
            super.onCreate();
            Book book = new Book();
            book.setName("Android开发艺术探索");
            book.setPrice(28);
            mBooks.add(book);   
        }
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            Log.e(getClass().getSimpleName(), String.format("on bind,intent = %s", intent.toString()));
            return mBookManager;
        }
    }

    整体的代码结构很清晰,大致可以分为三块:第一块是初始化。在 onCreate() 方法里面我进行了一些数据的初始化操作。第二块是重写 BookManager.Stub 中的方法。在这里面提供AIDL里面定义的方法接口的具体实现逻辑。第三块是重写 onBind() 方法。在里面返回写好的 BookManager.Stub 。

    接下来在 Manefest 文件里面注册这个我们写好的 Service ,这个不写的话我们前面做的工作都是无用功:

    <service
        android:name=".service.AIDLService"
        android:exported="true">
            <intent-filter>
                <action android:name="com.lypeer.aidl"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
    </service>

    到这里我们的服务端代码就编写完毕了,如果你对里面的一些地方感觉有些陌生或者根本不知所云的话,说明你对 Service 相关的知识已经有些遗忘了,建议再去看看这两篇博文:Android中的Service:默默的奉献者 (1)Android中的Service:Binder,Messenger,AIDL(2)

    4.5,编写客户端代码

    前面说过,在客户端我们要完成的工作主要是调用服务端的方法,但是在那之前,我们首先要连接上服务端,完整的客户端代码是这样的:

    /**
     * 客户端的AIDLActivity.java
     * 由于测试机的无用debug信息太多,故log都是用的e
     * <p/>
     * Created by lypeer on 2016/7/17.
     */
    public class AIDLActivity extends AppCompatActivity {
    
        //由AIDL文件生成的Java类
        private BookManager mBookManager = null;
    
        //标志当前与服务端连接状况的布尔值,false为未连接,true为连接中
        private boolean mBound = false;
    
        //包含Book对象的list
        private List<Book> mBooks;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_aidl);
        }
    
        /**
         * 按钮的点击事件,点击之后调用服务端的addBookIn方法
         *
         * @param view
         */
        public void addBook(View view) {
            //如果与服务端的连接处于未连接状态,则尝试连接
            if (!mBound) {
                attemptToBindService();
                Toast.makeText(this, "当前与服务端处于未连接状态,正在尝试重连,请稍后再试", Toast.LENGTH_SHORT).show();
                return;
            }
            if (mBookManager == null) return;
    
            Book book = new Book();
            book.setName("APP研发录In");
            book.setPrice(30);
            try {
                mBookManager.addBook(book);
                Log.e(getLocalClassName(), book.toString());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 尝试与服务端建立连接
         */
        private void attemptToBindService() {
            Intent intent = new Intent();
            intent.setAction("com.lypeer.aidl");
            intent.setPackage("com.lypeer.ipcserver");
            bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
        }
    
        @Override
        protected void onStart() {
            super.onStart();
            if (!mBound) {
                attemptToBindService();
            }
        }
    
        @Override
        protected void onStop() {
            super.onStop();
            if (mBound) {
                unbindService(mServiceConnection);
                mBound = false;
            }
        }
    
        private ServiceConnection mServiceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                Log.e(getLocalClassName(), "service connected");
                mBookManager = BookManager.Stub.asInterface(service);
                mBound = true;
    
                if (mBookManager != null) {
                    try {
                        mBooks = mBookManager.getBooks();
                        Log.e(getLocalClassName(), mBooks.toString());
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                Log.e(getLocalClassName(), "service disconnected");
                mBound = false;
            }
        };
    }

    同样很清晰,首先建立连接,然后在 ServiceConnection 里面获取 BookManager 对象,接着通过它来调用服务端的方法。

    4.6,开始通信吧!

    通过上面的步骤,我们已经完成了所有的前期工作,接下来就可以通过AIDL来进行跨进程通信了!将两个app同时运行在同一台手机上,然后调用客户端的 addBook() 方法,我们会看到服务端的 logcat 信息是这样的:

    //服务端的 log 信息,我把无用的信息头去掉了,然后给它编了个号
    1,on bind,intent = Intent { act=com.lypeer.aidl pkg=com.lypeer.ipcserver }
    2,invoking getBooks() method , now the list is : [name : Android开发艺术探索 , price : 28]
    3,invoking addBooks() method , now the list is : [name : Android开发艺术探索 , price : 28, name : APP研发录In , price : 2333]

    客户端的信息是这样的:

    //客户端的 log 信息
    1,service connected
    2,[name : Android开发艺术探索 , price : 28]
    3,name : APP研发录In , price : 30

    所有的 log 信息都很正常并且符合预期——这说明我们到这里为止的步骤都是正确的,按照上面说的来做是能够正确的使用AIDL来进行跨进程通信的。

    结语

    这一篇文章主要介绍了我们在概述里提到的前三个问题,即:

    • 为什么要设计AIDL语言?
    • AIDL的语法是什么?
    • 如何使用AIDL语言完成跨进程通信?

    本来我是准备在这篇文章里把我那五个问题都讲完的,结果写到这里发现篇幅已经有些长了,再写的话可能就少有人有这个耐性读下去了——那么写在后面的这些又有什么意义呢?于是就干脆从这里截断,将AIDL的工作原理和它的设计思想以及我对于它的这种设计的一些看法放在下一篇博文里来讲述——刚好,有那么点基础篇和提高篇的意思,哈哈。

    文中相关代码可点击 传送门 下载。

    谢谢大家。

    展开全文
  • Android 进阶7:进程通信之 AIDL 的使用

    万次阅读 多人点赞 2017-05-29 07:41:37
    记得 2015 年实习面试,笔试题里就有这道题:请介绍下 AIDL。当时的我是懵逼的,只好老老实实空着。没想到后来面试时面试官大哥嘿嘿一笑说他也没用过这玩意,真是够实诚的。笔试完查了这个知识点,似懂非懂也没深究...

    读完本文你将了解:

    记得 2015 年实习面试,笔试题里就有这道题:请介绍下 AIDL。

    当时的我是懵逼的,只好老老实实空着。没想到后来面试时面试官大哥嘿嘿一笑说他也没用过这玩意,真是够实诚的。

    笔试完查了这个知识点,似懂非懂也没深究。去年看《安卓开发艺术探索》时也学了这部分内容,但是可能当时水平不够,或者只是看起来努力,没有真正理解精髓,没多久就又忘了个七八成。

    这次复习,还是老老实实敲出来,总结成文字吧,方便以后回顾。

    AIDL 是什么

    AIDL(Android 接口定义语言) 是 Android 提供的一种进程间通信 (IPC) 机制。

    我们可以利用它定义客户端与服务使用进程间通信 (IPC) 进行相互通信时都认可的编程接口。

    在 Android 上,一个进程通常无法访问另一个进程的内存。 尽管如此,进程需要将其对象分解成操作系统能够识别的原语,并将对象编组成跨越边界的对象。

    编写执行这一编组操作的代码是一项繁琐的工作,因此 Android 会使用 AIDL 来处理。

    通过这种机制,我们只需要写好 aidl 接口文件,编译时系统会帮我们生成 Binder 接口。

    AIDL 支持的数据类型

    共 4 种:

    1. Java 的基本数据类型
    2. List 和 Map
      • 元素必须是 AIDL 支持的数据类型
      • Server 端具体的类里则必须是 ArrayList 或者 HashMap
    3. 其他 AIDL 生成的接口
    4. 实现 Parcelable 的实体

    AIDL 如何编写

    AIDL 的编写主要为以下三部分:

    1. 创建 AIDL
      • 创建要操作的实体类,实现 Parcelable 接口,以便序列化/反序列化
      • 新建 aidl 文件夹,在其中创建接口 aidl 文件以及实体类的映射 aidl 文件
      • Make project ,生成 Binder 的 Java 文件
    2. 服务端
      • 创建 Service,在其中创建上面生成的 Binder 对象实例,实现接口定义的方法
      • onBind() 中返回
    3. 客户端
      • 实现 ServiceConnection 接口,在其中拿到 AIDL 类
      • bindService()
      • 调用 AIDL 类中定义好的操作请求

    AIDL 实例

    下面以实例代码演示一个 AIDL 的编写。

    1.创建 AIDL

    ①创建要操作的实体类,实现 Parcelable 接口,以便序列化/反序列化

    
    package net.sxkeji.shixinandroiddemo2.bean;
    
    import android.os.Parcel;
    import android.os.Parcelable;
    
    public class Person implements Parcelable {
        private String mName;
    
        public Person(String name) {
            mName = name;
        }
    
        protected Person(Parcel in) {
            mName = in.readString();
        }
    
        public static final Creator<Person> CREATOR = new Creator<Person>() {
            @Override
            public Person createFromParcel(Parcel in) {
                return new Person(in);
            }
    
            @Override
            public Person[] newArray(int size) {
                return new Person[size];
            }
        };
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(mName);
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "mName='" + mName + '\'' +
                    '}';
        }
    }

    实现 Parcelable 接口是为了后序跨进程通信时使用。

    关于 Parcelable 可以看我的这篇文章 Android 进阶6:两种序列化方式 Serializable 和 Parcelable

    注意 实体类所在的包名。

    ②新建 aidl 文件夹,在其中创建接口 aidl 文件以及实体类的映射 aidl 文件

    在 main 文件夹下新建 aidl 文件夹,使用的包名要和 java 文件夹的包名一致:

    shixiznhang

    先创建实体类的映射 aidl 文件,Person.aidl:

    // Person.aidl
    package net.sxkeji.shixinandroiddemo2.bean;
    
    //还要和声明的实体类在一个包里
    parcelable Person;

    在其中声明映射的实体类名称与类型

    注意,这个 Person.aidl 的包名要和实体类包名一致。

    然后创建接口 aidl 文件,IMyAidl.aidl:

    // IMyAidl.aidl
    package net.sxkeji.shixinandroiddemo2;
    
    // Declare any non-default types here with import statements
    import net.sxkeji.shixinandroiddemo2.bean.Person;
    
    interface IMyAidl {
        /**
         * 除了基本数据类型,其他类型的参数都需要标上方向类型:in(输入), out(输出), inout(输入输出)
         */
        void addPerson(in Person person);
    
        List<Person> getPersonList();
    }

    在接口 aidl 文件中定义将来要在跨进程进行的操作,上面的接口中定义了两个操作:

    • addPerson: 添加 Person
    • getPersonList:获取 Person 列表

    需要注意的是:

    • 非基本类型的数据需要导入,比如上面的 Person,需要导入它的全路径。
      • 这里的 Person 我理解的是 Person.aidl,然后通过 Person.aidl 又找到真正的实体 Person 类。
    • 方法参数中,除了基本数据类型,其他类型的参数都需要标上方向类型
      • in(输入), out(输出), inout(输入输出)

    ③Make Project ,生成 Binder 的 Java 文件

    AIDL 真正的强大之处就在这里,通过简单的定义 aidl 接口,然后编译,就会为我们生成复杂的 Java 文件。

    点击 Build -> Make Project,然后等待构建完成。

    然后就会在 build/generated/source/aidl/你的 flavor/ 下生成一个 Java 文件:

    shixinzhang

    现在我们有了跨进程 Client 和 Server 的通信媒介,接着就可以编写客户端和服务端代码了。

    我们先跑通整个过程,这个文件的内容下篇文章介绍。

    2.编写服务端代码

    创建 Service,在其中创建上面生成的 Binder 对象实例,实现接口定义的方法;然后在 onBind() 中返回

    创建将来要运行在另一个进程的 Service,在其中实现了 AIDL 接口中定义的方法:

    public class MyAidlService extends Service {
        private final String TAG = this.getClass().getSimpleName();
    
        private ArrayList<Person> mPersons;
    
        /**
         * 创建生成的本地 Binder 对象,实现 AIDL 制定的方法
         */
        private IBinder mIBinder = new IMyAidl.Stub() {
    
            @Override
            public void addPerson(Person person) throws RemoteException {
                mPersons.add(person);
            }
    
            @Override
            public List<Person> getPersonList() throws RemoteException {
                return mPersons;
            }
        };
    
        /**
         * 客户端与服务端绑定时的回调,返回 mIBinder 后客户端就可以通过它远程调用服务端的方法,即实现了通讯
         * @param intent
         * @return
         */
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            mPersons = new ArrayList<>();
            LogUtils.d(TAG, "MyAidlService onBind");
            return mIBinder;
        }
    }

    上面的代码中,创建的对象是一个 IMyAidl.Stub() ,它是一个 Binder,具体为什么是它我们下篇文章介绍。

    别忘记在 Manifest 文件中声明:

    <service
        android:name="net.sxkeji.shixinandroiddemo2.service.MyAidlService"
        android:enabled="true"
        android:exported="true"
        android:process=":aidl"/>

    服务端实现了接口,在 onBind() 中返回这个 Binder,客户端拿到就可以操作数据了。

    3.编写客户端代码

    这里我们以一个 Activity 为客户端。

    ①实现 ServiceConnection 接口,在其中拿到 AIDL 类

    private IMyAidl mAidl;
    
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //连接后拿到 Binder,转换成 AIDL,在不同进程会返回个代理
            mAidl = IMyAidl.Stub.asInterface(service);
        }
    
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mAidl = null;
        }
    };

    在 Activity 中创建一个服务连接对象,在其中调用 IMyAidl.Stub.asInterface() 方法将 Binder 转为 AIDL 类。

    ②接着绑定服务

    Intent intent1 = new Intent(getApplicationContext(), MyAidlService.class);
    bindService(intent1, mConnection, BIND_AUTO_CREATE);

    要执行 IPC,必须使用 bindService() 将应用绑定到服务上。

    注意:

    5.0 以后要求显式调用 Service,所以我们无法通过 action 或者 filter 的形式调用 Service,具体内容可以看这篇文章 Android 进阶:Service 的一些细节

    ③拿到 AIDL 类后,就可以调用 AIDL 类中定义好的操作,进行跨进程请求

    @OnClick(R.id.btn_add_person)
    public void addPerson() {
        Random random = new Random();
        Person person = new Person("shixin" + random.nextInt(10));
    
        try {
            mAidl.addPerson(person);
            List<Person> personList = mAidl.getPersonList();
            mTvResult.setText(personList.toString());
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    运行结果

    shixinzhang

    可以看到,Activity 与 另外一个进程的 Service 通信成功了。

    总结

    这篇文章介绍了 AIDL 的简单编写流程,其中也踩过一些坑,比如文件所在包的路径不统一,绑定服务收不到回调等问题。

    到最后虽然跨进程通信成功,但是我们还是有很多疑问的,比如:

    • AIDL 生成的文件内容?
    • 什么是 Binder?
    • 为什么要这么写?

    知其然还要知其所以然,这一切都要从 Binder 讲起,且听下一回合介绍。

    代码地址

    Thanks

    《Android 开发艺术探索》
    https://developer.android.com/guide/components/aidl.html
    http://www.jianshu.com/p/b9b15252b3d6
    http://rainbow702.iteye.com/blog/1149790

    展开全文
  • Android aidl Binder框架浅析

    万次阅读 多人点赞 2016-08-10 19:41:02
    1、概述Binder能干什么?Binder可以提供系统中任何程序都可以访问的全局服务。这个功能当然是任何系统都应该提供的,下面我们简单看一下Android的Binder的框架Android Binder框架分为服务器接口、Binder驱动、以及...
    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38461079 ,本文出自【张鸿洋的博客】

    1、概述

    Binder能干什么?Binder可以提供系统中任何程序都可以访问的全局服务。这个功能当然是任何系统都应该提供的,下面我们简单看一下Android的Binder的框架

    Android Binder框架分为服务器接口、Binder驱动、以及客户端接口;简单想一下,需要提供一个全局服务,那么全局服务那端即是服务器接口,任何程序即客户端接口,它们之间通过一个Binder驱动访问。

    服务器端接口:实际上是Binder类的对象,该对象一旦创建,内部则会启动一个隐藏线程,会接收Binder驱动发送的消息,收到消息后,会执行Binder对象中的onTransact()函数,并按照该函数的参数执行不同的服务器端代码。

    Binder驱动:该对象也为Binder类的实例,客户端通过该对象访问远程服务。

    客户端接口:获得Binder驱动,调用其transact()发送消息至服务器

    如果大家对上述不了解,没关系,下面会通过例子来更好的说明,实践是检验真理的唯一标准嘛

    2、AIDL的使用

    如果对Android比较熟悉,那么一定使用过AIDL,如果你还不了解,那么也没关系,下面会使用一个例子展示AIDL的用法。

    我们使用AIDL实现一个跨进程的加减法调用

    1、服务端

    新建一个项目,创建一个包名:com.zhy.calc.aidl,在包内创建一个ICalcAIDL文件:

    package com.zhy.calc.aidl;
    interface ICalcAIDL
    {
    	int add(int x , int y);
    	int min(int x , int y );
    }

    注意,文件名为ICalcAIDL.aidl

    然后在项目的gen目录下会生成一个ICalcAIDL.java文件,暂时不贴这个文件的代码了,后面会详细说明

    然后我们在项目中新建一个Service,代码如下:

    package com.example.zhy_binder;
    
    import com.zhy.calc.aidl.ICalcAIDL;
    
    import android.app.Service;
    import android.content.Intent;
    import android.os.IBinder;
    import android.os.RemoteException;
    import android.util.Log;
    
    public class CalcService extends Service
    {
    	private static final String TAG = "server";
    
    	public void onCreate()
    	{
    		Log.e(TAG, "onCreate");
    	}
    
    	public IBinder onBind(Intent t)
    	{
    		Log.e(TAG, "onBind");
    		return mBinder;
    	}
    
    	public void onDestroy()
    	{
    		Log.e(TAG, "onDestroy");
    		super.onDestroy();
    	}
    
    	public boolean onUnbind(Intent intent)
    	{
    		Log.e(TAG, "onUnbind");
    		return super.onUnbind(intent);
    	}
    
    	public void onRebind(Intent intent)
    	{
    		Log.e(TAG, "onRebind");
    		super.onRebind(intent);
    	}
    
    	private final ICalcAIDL.Stub mBinder = new ICalcAIDL.Stub()
    	{
    
    		@Override
    		public int add(int x, int y) throws RemoteException
    		{
    			return x + y;
    		}
    
    		@Override
    		public int min(int x, int y) throws RemoteException
    		{
    			return x - y;
    		}
    
    	};
    
    }
    
    在此Service中,使用生成的ICalcAIDL创建了一个mBinder的对象,并在Service的onBind方法中返回

    最后记得在AndroidManifest中注册

     <service android:name="com.example.zhy_binder.CalcService" >
                <intent-filter>
                    <action android:name="com.zhy.aidl.calc" />
    
                    <category android:name="android.intent.category.DEFAULT" />
                </intent-filter>
            </service>

    这里我们指定了一个name,因为我们一会会在别的应用程序中通过Intent来查找此Service;这个不需要Activity,所以我也就没写Activity,安装完成也看不到安装图标,悄悄在后台运行着。

    到此,服务端编写完毕。下面开始编写客户端

    2、客户端

    客户端的代码比较简单,创建一个布局,里面包含4个按钮,分别为绑定服务,解除绑定,调用加法,调用减法

    布局文件:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >
    
        <Button
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:onClick="bindService"
            android:text="BindService" />
    
        <Button
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:onClick="unbindService"
            android:text="UnbindService" />
    
        <Button
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:onClick="addInvoked"
            android:text="12+12" />
    
        <Button
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:onClick="minInvoked"
            android:text="50-12" />
    
    </LinearLayout>

    主Activity

    package com.example.zhy_binder_client;
    
    import android.app.Activity;
    import android.content.ComponentName;
    import android.content.Context;
    import android.content.Intent;
    import android.content.ServiceConnection;
    import android.os.Bundle;
    import android.os.IBinder;
    import android.util.Log;
    import android.view.View;
    import android.widget.Toast;
    
    import com.zhy.calc.aidl.ICalcAIDL;
    
    public class MainActivity extends Activity
    {
    	private ICalcAIDL mCalcAidl;
    
    	private ServiceConnection mServiceConn = new ServiceConnection()
    	{
    		@Override
    		public void onServiceDisconnected(ComponentName name)
    		{
    			Log.e("client", "onServiceDisconnected");
    			mCalcAidl = null;
    		}
    
    		@Override
    		public void onServiceConnected(ComponentName name, IBinder service)
    		{
    			Log.e("client", "onServiceConnected");
    			mCalcAidl = ICalcAIDL.Stub.asInterface(service);
    		}
    	};
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState)
    	{
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    
    	}
    	
    	/**
    	 * 点击BindService按钮时调用
    	 * @param view
    	 */
    	public void bindService(View view)
    	{
    		Intent intent = new Intent();
    		intent.setAction("com.zhy.aidl.calc");
    		bindService(intent, mServiceConn, Context.BIND_AUTO_CREATE);
    	}
    	/**
    	 * 点击unBindService按钮时调用
    	 * @param view
    	 */
    	public void unbindService(View view)
    	{
    		unbindService(mServiceConn);
    	}
    	/**
    	 * 点击12+12按钮时调用
    	 * @param view
    	 */
    	public void addInvoked(View view) throws Exception
    	{
    
    		if (mCalcAidl != null)
    		{
    			int addRes = mCalcAidl.add(12, 12);
    			Toast.makeText(this, addRes + "", Toast.LENGTH_SHORT).show();
    		} else
    		{
    			Toast.makeText(this, "服务器被异常杀死,请重新绑定服务端", Toast.LENGTH_SHORT)
    					.show();
    
    		}
    
    	}
    	/**
    	 * 点击50-12按钮时调用
    	 * @param view
    	 */
    	public void minInvoked(View view) throws Exception
    	{
    
    		if (mCalcAidl != null)
    		{
    			int addRes = mCalcAidl.min(58, 12);
    			Toast.makeText(this, addRes + "", Toast.LENGTH_SHORT).show();
    		} else
    		{
    			Toast.makeText(this, "服务端未绑定或被异常杀死,请重新绑定服务端", Toast.LENGTH_SHORT)
    					.show();
    
    		}
    
    	}
    
    }
    

    很标准的绑定服务的代码。

    直接看运行结果:

    我们首先点击BindService按钮,查看log

    08-09 22:56:38.959: E/server(29692): onCreate
    08-09 22:56:38.959: E/server(29692): onBind
    08-09 22:56:38.959: E/client(29477): onServiceConnected
    
    可以看到,点击BindService之后,服务端执行了onCreate和onBind的方法,并且客户端执行了onServiceConnected方法,标明服务器与客户端已经联通

    然后点击12+12,50-12可以成功的调用服务端的代码并返回正确的结果

    下面我们再点击unBindService

    08-09 22:59:25.567: E/server(29692): onUnbind
    08-09 22:59:25.567: E/server(29692): onDestroy
    
    由于我们当前只有一个客户端绑定了此Service,所以Service调用了onUnbind和onDestory

    然后我们继续点击12+12,50-12,通过上图可以看到,依然可以正确执行,也就是说即使onUnbind被调用,连接也是不会断开的,那么什么时候会端口呢?

    即当服务端被异常终止的时候,比如我们现在在手机的正在执行的程序中找到该服务:

    点击停止,此时查看log

    08-09 23:04:21.433: E/client(30146): onServiceDisconnected
    
    可以看到调用了onServiceDisconnected方法,此时连接被断开,现在点击12+12,50-12的按钮,则会弹出Toast服务端断开的提示。

    说了这么多,似乎和Binder框架没什么关系,下面我们来具体看一看AIDL为什么做了些什么。

    3、分析AIDL生成的代码


    1、服务端

    先看服务端的代码,可以看到我们服务端提供的服务是由

    private final ICalcAIDL.Stub mBinder = new ICalcAIDL.Stub()
    	{
    
    		@Override
    		public int add(int x, int y) throws RemoteException
    		{
    			return x + y;
    		}
    
    		@Override
    		public int min(int x, int y) throws RemoteException
    		{
    			return x - y;
    		}
    
    	};

    ICalcAILD.Stub来执行的,让我们来看看Stub这个类的声明:

    public static abstract class Stub extends android.os.Binder implements com.zhy.calc.aidl.ICalcAIDL
    

    清楚的看到这个类是Binder的子类,是不是符合我们文章开通所说的服务端其实是一个Binder类的实例

    接下来看它的onTransact()方法:

    @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
    {
    switch (code)
    {
    case INTERFACE_TRANSACTION:
    {
    reply.writeString(DESCRIPTOR);
    return true;
    }
    case TRANSACTION_add:
    {
    data.enforceInterface(DESCRIPTOR);
    int _arg0;
    _arg0 = data.readInt();
    int _arg1;
    _arg1 = data.readInt();
    int _result = this.add(_arg0, _arg1);
    reply.writeNoException();
    reply.writeInt(_result);
    return true;
    }
    case TRANSACTION_min:
    {
    data.enforceInterface(DESCRIPTOR);
    int _arg0;
    _arg0 = data.readInt();
    int _arg1;
    _arg1 = data.readInt();
    int _result = this.min(_arg0, _arg1);
    reply.writeNoException();
    reply.writeInt(_result);
    return true;
    }
    }
    return super.onTransact(code, data, reply, flags);
    }

    文章开头也说到服务端的Binder实例会根据客户端依靠Binder驱动发来的消息,执行onTransact方法,然后由其参数决定执行服务端的代码。

    可以看到onTransact有四个参数

    code , data ,replay , flags

    code 是一个整形的唯一标识,用于区分执行哪个方法,客户端会传递此参数,告诉服务端执行哪个方法

    data客户端传递过来的参数

    replay服务器返回回去的值

    flags标明是否有返回值,0为有(双向),1为没有(单向)

    我们仔细看case TRANSACTION_min中的代码

    data.enforceInterface(DESCRIPTOR);与客户端的writeInterfaceToken对用,标识远程服务的名称

    int _arg0;
    _arg0 = data.readInt();
    int _arg1;
    _arg1 = data.readInt();

    接下来分别读取了客户端传入的两个参数

    int _result = this.min(_arg0, _arg1);
    reply.writeNoException();
    reply.writeInt(_result);

    然后执行this.min,即我们实现的min方法;返回result由reply写回。

    add同理,可以看到服务端通过AIDL生成Stub的类,封装了服务端本来需要写的代码。

    2、客户端

    客户端主要通过ServiceConnected与服务端连接

    private ServiceConnection mServiceConn = new ServiceConnection()
    	{
    		@Override
    		public void onServiceDisconnected(ComponentName name)
    		{
    			Log.e("client", "onServiceDisconnected");
    			mCalcAidl = null;
    		}
    
    		@Override
    		public void onServiceConnected(ComponentName name, IBinder service)
    		{
    			Log.e("client", "onServiceConnected");
    			mCalcAidl = ICalcAIDL.Stub.asInterface(service);
    		}
    	};

    如果你比较敏锐,应该会猜到这个onServiceConnected中的IBinder实例,其实就是我们文章开通所说的Binder驱动,也是一个Binder实例

    在ICalcAIDL.Stub.asInterface中最终调用了:

    return new com.zhy.calc.aidl.ICalcAIDL.Stub.Proxy(obj);

    这个Proxy实例传入了我们的Binder驱动,并且封装了我们调用服务端的代码,文章开头说,客户端会通过Binder驱动的transact()方法调用服务端代码

    直接看Proxy中的add方法

    @Override public int add(int x, int y) throws android.os.RemoteException
    {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    int _result;
    try {
    _data.writeInterfaceToken(DESCRIPTOR);
    _data.writeInt(x);
    _data.writeInt(y);
    mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
    _reply.readException();
    _result = _reply.readInt();
    }
    finally {
    _reply.recycle();
    _data.recycle();
    }
    return _result;
    }

    首先声明两个Parcel对象,一个用于传递数据,一个用户接收返回的数据

    _data.writeInterfaceToken(DESCRIPTOR);与服务器端的enforceInterfac对应

    _data.writeInt(x);
    _data.writeInt(y);写入需要传递的参数

    mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);

    终于看到了我们的transact方法,第一个对应服务端的code,_data,_repay分别对应服务端的data,reply,0表示是双向的

    _reply.readException();
    _result = _reply.readInt();

    最后读出我们服务端返回的数据,然后return。可以看到和服务端的onTransact基本是一行一行对应的。

    到此,我们已经通过AIDL生成的代码解释了Android Binder框架的工作原理。Service的作用其实就是为我们创建Binder驱动,即服务端与客户端连接的桥梁。

    AIDL其实通过我们写的aidl文件,帮助我们生成了一个接口,一个Stub类用于服务端,一个Proxy类用于客户端调用。那么我们是否可以不通过写AIDL来实现远程的通信呢?下面向大家展示如何完全不依赖AIDL来实现客户端与服务端的通信。

    4、不依赖AIDL实现程序间通讯


    1、服务端代码

    我们新建一个CalcPlusService.java用于实现两个数的乘和除

    package com.example.zhy_binder;
    
    import android.app.Service;
    import android.content.Intent;
    import android.os.Binder;
    import android.os.IBinder;
    import android.os.Parcel;
    import android.os.RemoteException;
    import android.util.Log;
    
    public class CalcPlusService extends Service
    {
    	private static final String DESCRIPTOR = "CalcPlusService";
    	private static final String TAG = "CalcPlusService";
    
    	public void onCreate()
    	{
    		Log.e(TAG, "onCreate");
    	}
    
    	@Override
    	public int onStartCommand(Intent intent, int flags, int startId)
    	{
    		Log.e(TAG, "onStartCommand");
    		return super.onStartCommand(intent, flags, startId);
    	}
    
    	public IBinder onBind(Intent t)
    	{
    		Log.e(TAG, "onBind");
    		return mBinder;
    	}
    
    	public void onDestroy()
    	{
    		Log.e(TAG, "onDestroy");
    		super.onDestroy();
    	}
    
    	public boolean onUnbind(Intent intent)
    	{
    		Log.e(TAG, "onUnbind");
    		return super.onUnbind(intent);
    	}
    
    	public void onRebind(Intent intent)
    	{
    		Log.e(TAG, "onRebind");
    		super.onRebind(intent);
    	}
    
    	private MyBinder mBinder = new MyBinder();
    
    	private class MyBinder extends Binder
    	{
    		@Override
    		protected boolean onTransact(int code, Parcel data, Parcel reply,
    				int flags) throws RemoteException
    		{
    			switch (code)
    			{
    			case 0x110:
    			{
    				data.enforceInterface(DESCRIPTOR);
    				int _arg0;
    				_arg0 = data.readInt();
    				int _arg1;
    				_arg1 = data.readInt();
    				int _result = _arg0 * _arg1;
    				reply.writeNoException();
    				reply.writeInt(_result);
    				return true;
    			}
    			case 0x111:
    			{
    				data.enforceInterface(DESCRIPTOR);
    				int _arg0;
    				_arg0 = data.readInt();
    				int _arg1;
    				_arg1 = data.readInt();
    				int _result = _arg0 / _arg1;
    				reply.writeNoException();
    				reply.writeInt(_result);
    				return true;
    			}
    			}
    			return super.onTransact(code, data, reply, flags);
    		}
    
    	};
    
    }
    

    我们自己实现服务端,所以我们自定义了一个Binder子类,然后复写了其onTransact方法,我们指定服务的标识为CalcPlusService,然后0x110为乘,0x111为除;

    记得在AndroidMenifest中注册

     <service android:name="com.example.zhy_binder.CalcPlusService" >
                <intent-filter>
                    <action android:name="com.zhy.aidl.calcplus" />
                    <category android:name="android.intent.category.DEFAULT" />
                </intent-filter>
            </service>

    服务端代码结束。

    2、客户端代码

    单独新建了一个项目,代码和上例很类似

    首先布局文件:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >
    
        <Button
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:onClick="bindService"
            android:text="BindService" />
    
        <Button
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:onClick="unbindService"
            android:text="UnbindService" />
    
        <Button
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:onClick="mulInvoked"
            android:text="50*12" />
        
        <Button
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:onClick="divInvoked"
            android:text="36/12" />
    
    </LinearLayout>

    可以看到加入了乘和除

    然后是Activity的代码

    package com.example.zhy_binder_client03;
    
    import android.app.Activity;
    import android.content.ComponentName;
    import android.content.Context;
    import android.content.Intent;
    import android.content.ServiceConnection;
    import android.os.Bundle;
    import android.os.IBinder;
    import android.os.RemoteException;
    import android.util.Log;
    import android.view.View;
    import android.widget.Toast;
    
    public class MainActivity extends Activity
    {
    
    	private IBinder mPlusBinder;
    	private ServiceConnection mServiceConnPlus = new ServiceConnection()
    	{
    		@Override
    		public void onServiceDisconnected(ComponentName name)
    		{
    			Log.e("client", "mServiceConnPlus onServiceDisconnected");
    		}
    
    		@Override
    		public void onServiceConnected(ComponentName name, IBinder service)
    		{
    
    			Log.e("client", " mServiceConnPlus onServiceConnected");
    			mPlusBinder = service;
    		}
    	};
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState)
    	{
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    
    	}
    
    	public void bindService(View view)
    	{
    		Intent intentPlus = new Intent();
    		intentPlus.setAction("com.zhy.aidl.calcplus");
    		boolean plus = bindService(intentPlus, mServiceConnPlus,
    				Context.BIND_AUTO_CREATE);
    		Log.e("plus", plus + "");
    	}
    
    	public void unbindService(View view)
    	{
    		unbindService(mServiceConnPlus);
    	}
    
    	public void mulInvoked(View view)
    	{
    
    		if (mPlusBinder == null)
    		{
    			Toast.makeText(this, "未连接服务端或服务端被异常杀死", Toast.LENGTH_SHORT).show();
    		} else
    		{
    			android.os.Parcel _data = android.os.Parcel.obtain();
    			android.os.Parcel _reply = android.os.Parcel.obtain();
    			int _result;
    			try
    			{
    				_data.writeInterfaceToken("CalcPlusService");
    				_data.writeInt(50);
    				_data.writeInt(12);
    				mPlusBinder.transact(0x110, _data, _reply, 0);
    				_reply.readException();
    				_result = _reply.readInt();
    				Toast.makeText(this, _result + "", Toast.LENGTH_SHORT).show();
    
    			} catch (RemoteException e)
    			{
    				e.printStackTrace();
    			} finally
    			{
    				_reply.recycle();
    				_data.recycle();
    			}
    		}
    
    	}
    	
    	public void divInvoked(View view)
    	{
    
    		if (mPlusBinder == null)
    		{
    			Toast.makeText(this, "未连接服务端或服务端被异常杀死", Toast.LENGTH_SHORT).show();
    		} else
    		{
    			android.os.Parcel _data = android.os.Parcel.obtain();
    			android.os.Parcel _reply = android.os.Parcel.obtain();
    			int _result;
    			try
    			{
    				_data.writeInterfaceToken("CalcPlusService");
    				_data.writeInt(36);
    				_data.writeInt(12);
    				mPlusBinder.transact(0x111, _data, _reply, 0);
    				_reply.readException();
    				_result = _reply.readInt();
    				Toast.makeText(this, _result + "", Toast.LENGTH_SHORT).show();
    
    			} catch (RemoteException e)
    			{
    				e.printStackTrace();
    			} finally
    			{
    				_reply.recycle();
    				_data.recycle();
    			}
    		}
    
    	}
    }
    

    为了明了,我直接在mulInvoked里面写了代码,和服务端都没有抽象出一个接口。首先绑定服务时,通过onServiceConnected得到Binder驱动即mPlusBinder;

    然后准备数据,调用transact方法,通过code指定执行服务端哪个方法,代码和上面的分析一致。

    下面看运行结果:


    是不是很好的实现了我们两个应用程序间的通讯,并没有使用aidl文件,也从侧面分析了我们上述分析是正确的。


    好了,就到这里,相信大家看完这篇博文,对aidl和Binder的理解也会更加深刻。


    测试代码点击下载

    代码先安装server端的代码,然后再安装client端的。。。











    展开全文
  • AndroidStudio 使用AIDL

    万次阅读 2017-02-19 20:56:15
    这个标题不是很好,因为Android Studio是一个IDE,AIDL是一种IPC通信机制。本质上没有任何的关联。但是大家都懂得,这是一个在Android Studio中写的一个AIDL的小DEMO. 步骤很繁琐,本来不准备写的。但是写一下是为了...
  • AIDL

    千次阅读 2017-02-17 17:51:18
    AIDL AIDL的定义 ①aidl是Android interface definition Language 的英文缩写,意思Android 接口定义语言。 ②使用aidl可以帮助我们发布以及调用远程服务,实现跨进程通信。 ③将服务的aidl放到对应的...
  • AIDL的使用

    千次阅读 2018-06-11 10:11:55
    AIDL的概念 AIDL:全称是Android Interface Definition Language(Android接口定义语言)。它主要用在Android进程间通信中,Android中有一种进程间通信方式是Binder,AIDL就是帮我们自动生成了Binder相关代码,不需要...
  • android之AIDL

    千次阅读 2019-06-24 11:57:48
    1.什么是aidl Android使用的一种接口定义语言(Interface Definition Language,IDL)来公开服务的接口。因此,可以将这种可以跨进程访问的服务称为AIDL(Android Interface Definition Language)服务。 2.AIDL基本...
  • AndroidStudio 引用 aidl 文件的两种方法

    万次阅读 2019-05-06 15:19:23
    AndroidStudio 引入 aidl 文件,一般来说,有两种方法第一种方法直接在 src/main 目录下新建 aidl 文件夹,并将我们的 aidl 文件放到该目录下。因为 AndroidStudio 默认的 aidl 文件默认配置是这样的。第二种方法 把...
  • Android Studio AIDL 自定义类型包找不到的问题
  • Android 使用了aidl时,进行混淆

    千次阅读 2015-07-30 21:57:22
    当项目中使用了ITelephony.aidl时。 aidl混淆会报错:com.android.internal.telephony.ITelephony$Stub$Proxy cannot be cast to *************** 解决方法: 不混淆aidl文件。 1、在项目 project....
  • eclipse创建aidl文件

    千次阅读 2016-07-27 12:31:19
    先选中要创建aidl文件的包,例如:  然后File->New->File,弹出对话框: ... 然后在 File name 输入框里输入文件名,以.aidl后缀结尾,如:ICat.aidl ... 然后在aidl文件中给出正确的aidl接口代码,保
  • AIDL 无法自动生成aidl.java

    千次阅读 2012-09-18 12:35:18
    在做进程间通信时,使用系统aidl文件。 我把这个文件夹(包括aidl文件)拷贝到项目, 居然不自动生成aidl.java. 1、解决方案: 在aidl包下、新建一个合格的aidl文件, 新建的aidl可以自动生成aidl.java。 再删除...
  • android AIDL中支持的数据类型

    千次阅读 2017-10-26 15:19:20
    AIDL中支持的数据类型有: 支持类型 需要import 备注 Java基本类型 不需要import   String, CharSequence,  List,  Map 不需要import List,Map内的元素必须是AIDL支持 的类型; List接收方...
  • AndroidStudio如何添加aidl文件

    千次阅读 2016-01-15 10:17:05
    本文介绍了AndroidStudio如何添加AIDL文件
  • android studio 下aidl的使用遇到的坑

    千次阅读 2016-05-09 12:02:37
    这几天在看学习android下IPC的机制,在深入学习aidl时候碰到个蛋疼的问题,在此记录下来。 对于最新版本的android studio 右键new AIDL-AIDL File后,会自动在src/main/目录下创建路径 aidl/创建的aidl文件即放在这...
  • android aidl出现无法import

    千次阅读 2012-03-05 15:20:11
    当采用eclipse 写aidl时出现couldn't find import for class 原因是你import的包没有在framework.aidl里parcelable过 所以解决办法很简单,找到对应api-level的framework.aidl(可通过搜索) 在里面...
  • 在目录src/main 下新建了aidl 文件夹之后,在aidl文件夹中也创建了相同的包路径,创建了AIDL文件
  • 在Android Studio下如何创建aidl文件 1、首先你需要在工程module目录下的src下main中新建一个aidl文件夹,操作方式如下: 2、创建后android studio会自动在mian下新建aidl文件夹,并且在aidl文件下...
  • Android AIDL使用步骤

    千次阅读 2020-04-01 10:33:57
    前段时间项目总有用到AIDL,因为时间原因,没有及时记录下,今天想到这个突然感觉有点遗忘了,就又去复习了一波顺便记录下,方便下次查看,毕竟好记性不如烂笔头(滑稽)。 服务端: 1.在Android Studio中 src目录...
1 2 3 4 5 ... 20
收藏数 35,647
精华内容 14,258
关键字:

aidl