Android IPC机制(二):AIDL的基本使用方法

一、前言

上一篇文章,讲述了实现序列化和反序列化的基本方式,是实现进程间通讯的必要条件,而这篇文章主要来讲一讲IPC的主要方式之一——AIDL方式。除了AIDL方式,IPC还有其他进程间通讯方式,比如Messager、ContentProvider、Socket等,这些以后会讲到。现在先说说AIDL的基本使用方法。

二、什么是AIDL?

AIDL全称:Android Interface Definition Language,即Android接口定义语言。由于不同的进程不能共享内存,所以为了解决进程间通讯的问题,Android使用一种接口定义语言来公开服务的接口,本质上,AIDL非常像一个接口,通过公开接口,让别的进程调用该接口,从而实现进程间的通讯。

三、使用AIDL

以下结合一个具体实例来示范AIDL的使用方法。
1、建立.aidl文件
  为了方便AIDL的开发,建议把所有和AIDL相关的类和文件放入同一个包中,这样方便把整个包复制,以便其他模块或者应用需要用到同一个AIDL。在Android Studio下,专门为AIDL文件创建了一个文件夹,方便我们的管理:

项目结构

  可以看到,笔者新建了一个service模块,该模块在manifests的声明如下:

<service android:name=".MyAidlService"  
            android:enabled="true"  
            android:exported="true"></service>

这个模块为app模块提供服务,与app模块处于不同进程,所以模拟了进程间通讯的场景。在service模块,新建一个AIDL文件夹,然后新建一个包,这里包名为com.chenyu.service,然后新建AIDL文件:IMyAidl.aidl:

// IMyAidl.aidl  
package com.chenyu.service;  
  
// Declare any non-default types here with import statements  
import com.chenyu.service.Person;  
interface IMyAidl {  
    void addPerson(in Person person);  
    List<Person> getPersonList();  
} 

这与定义一个接口的语法基本相同,都是以Interface为关键字定义。里面声明了两个方法,分别是addPerson(in Person person)与getPersonList()。AIDL中除了基本数据类型,其他类型的参数必须标上方向,in、out、或者inout,in表示输入型参数,out表示输出型参数,inout表示输入输出型参数,我们要根据需要实际指定参数类型,因为底层的数据处理开销非常大,如果不指定类型,编译将会无法通过。

AIDL支持的数据类型
①基本数据类型(int,long,char,boolean,double)
②string和CharSequence
③List:只支持ArrayList,以及里面所有的元素必须能够被AIDL支持 ④Map:只支持HashMap,以及里面所有的元素必须能够被AIDL支持 ⑤Parcelable:所有实现了Parcelable接口的对象
⑥AIDL:所有AIDL接口本身也可以在AIDL文件中使用。

注意一下:这里使用了自定义的Parcelable对象:Person类,但是AIDL不认识这个类,所以我们要创建一个与Person类同名的AIDL文件:Person.aidl

// IMyAidl.aidl  
package com.chenyu.service;  
//声明Person
parcelable Person;  

只有这样,IMyAidl.aidl才能知道其中的Person是使用了Parcelable接口的类,注意,Person类的包名与Person.aidl的包名一定要相同,即无论其他应用或者其他模块,只要有AIDL,都应该保证AIDL的所有包结构一致,才能保证顺利进行IPC通讯,减少不必要的麻烦。

2、Person类,实现Parcelable接口
  在java文件夹中,创建com.chenyu.service包,这样就与上面的是相同包名了。

package com.chenyu.service;  
  
import android.os.Parcel;  
import android.os.Parcelable;  
  
  
public class Person implements Parcelable {  
    private String name;  
    private int age;  
    private int number;  
  
    public Person(Parcel source) {  
        this.name=source.readString();  
        this.age=source.readInt();  
        this.number=source.readInt();  
    }  
  
    //getter、setter method  
    //...  
    public Person(int age, String name, int number) {  
        this.age = age;  
        this.name = name;  
        this.number = number;  
    }  
  
    @Override  
    public int describeContents() {  
        return 0;  
    }  
  
    @Override  
    public void writeToParcel(Parcel dest, int flags) {  
        dest.writeString(name);  
        dest.writeInt(age);  
        dest.writeInt(number);  
    }  
    public static final Parcelable.Creator<Person> CREATOR=new Creator<Person>() {  
        @Override  
        public Person createFromParcel(Parcel source) {  
            return new Person(source);  
        }  
  
        @Override  
        public Person[] newArray(int size) {  
            return new Person[size];  
        }  
    };  
  
    @Override  
    public String toString() {  
        return "Person{" +  
                "name='" + name + '\\\\'' +  
                ", age=" + age +  
                ", number=" + number +  
                '}';  
    }  
}

对于Parcelable接口的详细解析,可参考上一篇文章,这里不再赘述。

3、实现服务端
  上面我们定义了一个AIDL接口,接下来要做的是实现这个AIDL接口,在java/com.chenyu.service中,创建MyAidlService.java文件:

package com.chenyu.service;  
  
import android.app.Service;  
import android.content.Intent;  
import android.os.IBinder;  
import android.os.RemoteException;  
import android.util.Log;  
  
import java.util.ArrayList;  
import java.util.List;  
  
public class MyAidlService extends Service {  
    private ArrayList<Person> persons;  
    @Override  
    public IBinder onBind(Intent intent) {  
        persons=new ArrayList<Person>();  
        Log.d("cy", "success bind");  
        return iBinder;  
    }  
    private IBinder iBinder= new IMyAidl.Stub() {  
        @Override  
        public void addPerson(Person person) throws RemoteException {  
            persons.add(person);  
        }  
  
        @Override  
        public List<Person> getPersonList() throws RemoteException {  
            return persons;  
        }  
    };  
  
    @Override  
    public void onCreate() {  
        super.onCreate();  
        Log.d("cy", "onCreate ");  
    }  
}

我们来看一下服务端是如何实现接口的:
  (1)为了实现来自.aidl文件生成的接口,需要继承Binder接口(例如Ibinder接口),并且实现从.aidl文件中继承的方法,在上面代码中,使用匿名实例实现一个叫IMyAidl(定义在IMyAidl.aidl中)的接口,实现了两个方法,addPerson和getPersonList.
  (2)onBind方法:该方法在客户端与服务端连接的时候回调,实现客户端和服务端的绑定,并返回一个Binder实例,这里返回的是iBinder,而IBinder是(1)中实现了接口的匿名实例,即客户端拿到的实际上实现了接口的一个实例,这样,客户端通过Binder就与服务端建立了连接,客户端通过Binder远程调用服务端的实例方法,这样也即实现了进程间通讯。

4、实现客户端
  在实现客户端之前,先确保把aidl的包复制过来,就相上面笔者所给出的结构图一样,包括Person类也应该复制过来。显示界面比较简单,就不贴出来了,主要看Activity的代码:

package com.chenyu.myaidl;  
  
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.Button;  
import com.chenyu.service.IMyAidl;  
import com.chenyu.service.Person;  
  
import java.util.List;  
  
public class MainActivity extends Activity implements View.OnClickListener {  
  
    private Button btn;  
    IMyAidl iMyAidl;  
    private ServiceConnection conn=new ServiceConnection() {  
        @Override  
        public void onServiceConnected(ComponentName name, IBinder service) {  
            Log.d("cylog", "onServiceConnected success");  
            iMyAidl=IMyAidl.Stub.asInterface(service);  //  1  
  
        }  
  
        @Override  
        public void onServiceDisconnected(ComponentName name) {  
            Log.d("cylog", "onServicedisConnected ");  
            iMyAidl=null;  
        }  
    };  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        initView();  
        bindService();  
    }  
  
    private void initView() {  
        btn= (Button) findViewById(R.id.cal);  
        btn.setOnClickListener(this);  
    }  
  
    @Override  
    public void onClick(View v) {  
        try {  
            iMyAidl.addPerson(new Person(21, "陈育", 22255));  
            List<Person> persons = iMyAidl.getPersonList();  //   2  
            Log.d("cylog",persons.toString());  
        } catch (RemoteException e) {  
            e.printStackTrace();  
        }  
    }  
  
    private void bindService() {  
        Intent intent=new Intent();  
        intent.setComponent(new ComponentName("com.chenyu.service","com.chenyu.service.MyAidlService"));  
        bindService(intent,conn, Context.BIND_AUTO_CREATE);  
    }  
  
    @Override  
    protected void onDestroy() {  
        super.onDestroy();  
        unbindService(conn);  
    }  
}

与绑定一般Service的语法差不多,但是在安卓5.0之后,必须显式指定Service的包名和类名即:

private void bindService() {  
        Intent intent=new Intent();  
        intent.setComponent(new ComponentName("com.chenyu.service","com.chenyu.service.MyAidlService"));  
        bindService(intent,conn, Context.BIND_AUTO_CREATE);  
    }  

在bindService(intent,conn,Context.BIND_AUTO_CREATE)方法中有几个参数需要说明一下,
①conn:该参数代表了与服务端的连接,即ServiceConnection.
②Context.BIND_AUTO_CREATE:该参数表示绑定的同时创建一个Service。
  在发出请求绑定成功之后,会回调①处的代码,此时,可在回调方法onServiceConnected()方法中,获取服务端返回的IMyAidl实例,在客户端拿到该实例之后,就可以通过调用相应的方法进行远程通讯了,比如上述的②处代码。
最后,看一下运行结果,先运行service,然后运行app:

运行结果
进程显示

  可以看出,app端和service端的确是构成了进程间通讯,并且完成了进程间通讯。
  以上是利用AIDL实现进程通讯的基本方法,希望对大家有所帮助。关于AIDL的核心原理以及Binder,AIDL优化,会在下一篇文章详细讲述。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容