Android IPC系列(一):AIDL使用详解

简介: AIDL可以实现进程间的通信,由于每个进程都是运行在独立的空间,不同的进程想要交互需要借助一些特殊的方式,AIDL就是其中的一种,AIDL是一种模板,因为实际交互过程中,并不是AIDL起的作用,具体会在之后源码分析解释,AIDL的作用是为了避免重复编写代码而出现的一个模板

## 概述

AIDL可以实现进程间的通信,由于每个进程都是运行在独立的空间,不同的进程想要交互需要借助一些特殊的方式,AIDL就是其中的一种,AIDL是一种模板,因为实际交互过程中,并不是AIDL起的作用,具体会在之后源码分析解释,AIDL的作用是为了避免重复编写代码而出现的一个模板


## 语法

AIDL的语法十分简单,与Java语言基本保持一致,需要记住的规则有以下几点:


* AIDL文件以 .aidl 为后缀名

AIDL支持的数据类型分为如下几种:

  * 八种基本数据类型:byte、char、int、long、float、double、boolean

String,CharSequence,其中不支持short类型

  * 实现了Parcelable接口的数据类型

 * List 类型。List承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象

 * Map类型。Map承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象



* 定向Tag。定向Tag表示在跨进程通信中数据的流向,用于标注方法的参数值,分为 in、out、inout 三种。其中

 * in 表示数据只能由客户端流向服务端

 * out 表示数据只能由服务端流向客户端

 * inout 则表示数据可在服务端与客户端之间双向流通

 * 此外,如果AIDL方法接口的参数值类型是:基本数据类型、String、CharSequence或者其他AIDL文件定义的方法接口,那么这些参数值的定向 Tag 默认是且只能是 in,所以除了这些类型外,其他参数值都需要明确标注使用哪种定向Tag。定向Tag具体的使用差别后边会有介绍

* 明确导包。在AIDL文件中需要明确标明引用到的数据类型所在的包名,即使两个文件处在同个包名下


## 服务端

* 先建立一个项目

* 由于要传输自定义User对象,所以定义一个User的aidl文件,直接生成

![在这里插入图片描述](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/8/9/16c7511d28274a4e~tplv-t2oaga2asx-image.image)

创建完成后,系统就会默认创建一个 aidl 文件夹,文件夹下的目录结构即是工程的包名,Book.aidi 文件就在其中

然后更改User.aidl文件内容


```

package com.baidu.bpit.aibaidu.aidl;


parcelable User;

```

* 然后生成一个User的类,实现Parcelable

```

public class User implements Parcelable {


   public String name;


   public User(String name){

       this.name=name;

   }


   protected User(Parcel in) {

       name = in.readString();

   }


   public static final Creator<User> CREATOR = new Creator<User>() {

       @Override

       public User createFromParcel(Parcel in) {

           return new User(in);

       }


       @Override

       public User[] newArray(int size) {

           return new User[size];

       }

   };


   @Override

   public int describeContents() {

       return 0;

   }


   @Override

   public void writeToParcel(Parcel dest, int flags) {

       dest.writeString(name);

   }

}

```

* 然后在定义一个BookName的aidl文件,向客户端暴露可调用的接口,需要手动导入User,import com.baidu.bpit.aibaidu.aidl.User;


```

package com.baidu.bpit.aibaidu.aidl;

import com.baidu.bpit.aibaidu.aidl.User;

interface BookName {

  String getName();


  List<User> getList();


}

```

* 这时候重新build一下工程

* 现在需要来创建一个 Service 供客户端远程绑定了,返回你的自定义的Binder


```


public class ServiceService extends Service {

   public ServiceService() {

   }


   @Override

   public IBinder onBind(Intent intent) {

       return new MyBinder();

   }


   class MyBinder extends BookName.Stub {


       @Override

       public String getName() throws RemoteException {

           return "西游记";

       }


       @Override

       public List<User> getList() throws RemoteException {

           User user = new User("111");

           User user1 = new User("222");

           List<User> users = new ArrayList<>();

           users.add(user1);

           users.add(user);

           return users;

       }

   }

}

```

* AndroidManifest.xml文件定义

```

<service

           android:name=".ServiceService"

           android:enabled="true"

           android:exported="true">

           <intent-filter>

               <action android:name="com.aaa.aaa" />

           </intent-filter>

   </service>

```

## 客户端

* 首先把服务端的aidl文件夹,整体复制到客户端

* 之后,需要创建和服务端User类所在的相同包名来存放 User类

* ![在这里插入图片描述](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/8/9/16c7511d27f82e07~tplv-t2oaga2asx-image.image)

* 在MainActivity绑定服务端的service,点击按钮获取书名


```


public class MainActivity extends AppCompatActivity {


   private BookName bookName;


   @Override

   protected void onCreate(Bundle savedInstanceState) {

       super.onCreate(savedInstanceState);

       setContentView(R.layout.activity_main);

       bindServer();

       initView();

   }


   private void initView() {

       findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {

           @Override

           public void onClick(View v) {

               try {

                   Log.d("mmmgetName", bookName.getName());

                   List<User> list = bookName.getList();

                   for (User user : list) {

                       Log.d("mmmgetList", user.name);

                   }


               } catch (RemoteException e) {

                   e.printStackTrace();

               }

           }

       });

   }


   private void bindServer() {

       Intent mIntent = new Intent();

       //你定义的service的action

       mIntent.setAction("com.aaa.aaa");

       //这里你需要设置你应用的包名

       mIntent.setPackage("com.baidu.bpit.aibaidu.aidl");

       bindService(mIntent, new ServiceConnection() {

           @Override

           public void onServiceConnected(ComponentName name, IBinder service) {

               bookName = BookName.Stub.asInterface(service);

           }


           @Override

           public void onServiceDisconnected(ComponentName name) {


           }

       }, BIND_AUTO_CREATE);

   }

}


```

* 点击按钮打印


```

01-18 22:11:17.642 4542-4542/com.baidu.bpit.aibaidu.client D/mmmgetName: 西游记

01-18 22:11:17.643 4542-4542/com.baidu.bpit.aibaidu.client D/mmmgetList: 222

   111

```

正确获取数据



## 定向TAG

有三种定向TAG

* inout:服务端修改数据,会同步到客户端,因此可以说数据是双向流动的

* in:数据只从客户端流向服务端,服务端修改数据不会影响客户端

* out:数据只能由服务端传向客户端,及时客户端传入一个对象,这个对象也是空的,即没有数据,服务端获取该对象后,对该对象任何操作都会同步到客户端这里



**修改aidl**


```

interface BookName {

  String getName();


  List<User> getList();


  void addInout(inout User user);

  void addIn(in User user);

  void addout(out User user);

}

```

这次增加了三个方法addInout,addIn,addout,之后分别测试这三个方法


**修改User类**

User类需要添加俩个方法,一个无参构造,一个readFromParcel


```

public class User implements Parcelable {


   public String name;


   public User(){


   }


   public User(String name){

       this.name=name;

   }


   protected User(Parcel in) {

       name = in.readString();

   }


   public static final Creator<User> CREATOR = new Creator<User>() {

       @Override

       public User createFromParcel(Parcel in) {

           return new User(in);

       }


       @Override

       public User[] newArray(int size) {

           return new User[size];

       }

   };


   @Override

   public int describeContents() {

       return 0;

   }


   @Override

   public void writeToParcel(Parcel dest, int flags) {

       dest.writeString(name);

   }

   public void readFromParcel(Parcel dest) {

       name = dest.readString();

   }

}

```

## 先测试一下inout

**服务端**


```


public class ServiceService extends Service {

   public ServiceService() {

   }


   @Override

   public IBinder onBind(Intent intent) {

       return new MyBinder();

   }


   class MyBinder extends BookName.Stub {


       @Override

       public String getName() throws RemoteException {

           return "西游记";

       }


       @Override

       public List<User> getList() throws RemoteException {

           User user = new User("111");

           User user1 = new User("222");

           List<User> users = new ArrayList<>();

           users.add(user1);

           users.add(user);

           return users;

       }


       @Override

       public void addInout(User user) throws RemoteException {

               Log.d("mmmserver","服务端获取到:"+user.name);

               user.name="服务端更改";

               Log.d("mmmserver","服务端修改书名:"+user.name);

       }


       @Override

       public void addIn(User user) throws RemoteException {


       }


       @Override

       public void addout(User user) throws RemoteException {


       }

   }

}

```

主要看inout方法,服务端接受到客户端传来的信息后,修改信息内容


**客户端**


```

 private void initView() {

       findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {

           @Override

           public void onClick(View v) {

               try {

                   User user = new User("客户端传入");

                   Log.d("mmmclient", "客户端向服务端传入一本书,书名:" + user.name);

                   bookName.addInout(user);

                   Log.d("mmmclient", "服务端修改书名后,书名:" + user.name);


               } catch (RemoteException e) {

                   e.printStackTrace();

               }

           }

       });

   }

```

点击按钮后,向服务端传入数据,服务端收到数据,会对数据更改,客户端再次查看此数据,看是否同步


```

01-18 23:15:18.529 5606-5606/com.baidu.bpit.aibaidu.client D/mmmclient: 客户端向服务端传入一本书,书名:客户端传入

01-18 23:15:18.529 5527-5554/com.baidu.bpit.aibaidu.aidl D/mmmserver: 服务端获取到:客户端传入

01-18 23:15:18.530 5527-5554/com.baidu.bpit.aibaidu.aidl D/mmmserver: 服务端修改书名:服务端更改

01-18 23:15:18.530 5606-5606/com.baidu.bpit.aibaidu.client D/mmmclient: 服务端修改书名后,书名:服务端更改

```

看到服务端修改可以及时同步到客户端,这就是inout 数据双向流动


## 测试in

**服务端**


```

 @Override

       public void addIn(User user) throws RemoteException {

               Log.d("mmmserverIn", "服务端获取到:" + user.name);

               user.name = "服务端更改";

               Log.d("mmmserverIn", "服务端修改书名:" + user.name);

       }

```

**客户端**


```

   private void initView() {

       findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {

           @Override

           public void onClick(View v) {

               try {

                   User user = new User("客户端传入");

                   Log.d("mmmclient", "客户端向服务端传入一本书,书名:" + user.name);

                   bookName.addIn(user);

                   Log.d("mmmclient", "服务端修改书名后,书名:" + user.name);


               } catch (RemoteException e) {

                   e.printStackTrace();

               }

           }

       });

   }

```

当点击按钮,会发送数据到服务端,服务端会更改数据内容,客户端再次查看数据,看是否被改变


```

01-18 23:26:23.079 5815-5815/com.baidu.bpit.aibaidu.client D/mmmclient: 客户端向服务端传入一本书,书名:客户端传入

01-18 23:26:23.080 5736-5763/com.baidu.bpit.aibaidu.aidl D/mmmserverIn: 服务端获取到:客户端传入

01-18 23:26:23.081 5736-5763/com.baidu.bpit.aibaidu.aidl D/mmmserverIn: 服务端修改书名:服务端更改

01-18 23:26:23.081 5815-5815/com.baidu.bpit.aibaidu.client D/mmmclient: 服务端修改书名后,书名:客户端传入

```

看以看出服务端修改数据,并不会影响客户端


## 测试OUT

**服务端**


```

 @Override

       public void addout(User user) throws RemoteException {

           Log.d("mmmserverout", "服务端获取到:" + user.name);

           user.name = "服务端更改";

           Log.d("mmmserverout", "服务端修改书名:" + user.name);

       }

```

**客户端**


```


   private void initView() {

       findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {

           @Override

           public void onClick(View v) {

               try {

                   User user = new User("客户端传入");

                   Log.d("mmmclient", "客户端向服务端传入一本书,书名:" + user.name);

                   bookName.addout(user);

                   Log.d("mmmclient", "服务端修改书名后,书名:" + user.name);

               } catch (RemoteException e) {

                   e.printStackTrace();

               }

           }

       });

   }

```

客户端向服务端传入数据,服务端收到后,更改数据,客户端再次查看数据


```

01-18 23:36:21.997 6100-6100/com.baidu.bpit.aibaidu.client D/mmmclient: 客户端向服务端传入一本书,书名:客户端传入

01-18 23:36:21.998 6023-6037/com.baidu.bpit.aibaidu.aidl D/mmmserverout: 服务端获取到:null

                                    服务端修改书名:服务端更改

01-18 23:36:21.998 6100-6100/com.baidu.bpit.aibaidu.client D/mmmclient: 服务端修改书名后,书名:服务端更改

```

可以看到服务端收到的是空对象,服务端更改影响客户端


GitHub:

参考:https://www.jianshu.com/p/29999c1a93cd

相关文章
|
编解码 前端开发 Android开发
如何让Android平台像IPC一样实现GB28181前端设备接入
好多开发者在做国标对接的时候,首先想到的是IPC摄像头,通过参数化配置,接入到国标平台,实现媒体数据的按需查看等操作。
223 0
|
安全 Java 定位技术
Android 浅度解析:AIDL & Binder (1)
Android 浅度解析:AIDL & Binder (1)
757 0
|
Android开发
Android AIDL 的使用
Android AIDL 的使用
213 1
|
大数据 Android开发
Android使用AIDL+MemoryFile传递大数据
Android使用AIDL+MemoryFile传递大数据
314 0
|
Android开发
Android stdio 无法新建或打开AIDL文件(解决方法)
Android stdio 无法新建或打开AIDL文件(解决方法)
1361 0
|
Java Android开发
[Android AIDL] --- AIDL工程搭建
[Android AIDL] --- AIDL工程搭建
218 0
|
Java 开发工具 Android开发
[Android AIDL] --- AIDL原理简析
[Android AIDL] --- AIDL原理简析
546 0
|
编解码 开发工具 Android开发
如何在Android端实现轻量级RTSP服务(类似于IPC)
首先声明一点,本blog提到的轻量级RTSP服务,类似于网络摄像头(IPC),而非传统意义的接受外部推流的RTSP服务器。
1021 0