Android面经(14/30)AIDL全解析

牛客高级系列专栏:

安卓(安卓系统开发也要掌握)

嵌入式

本人是2020年毕业于广东工业大学研究生:许乔丹,有国内大厂CVTE和世界500强企业安卓开发经验,该专栏整理本人对常见安卓高频开发面试题的理解;

网上安卓资料千千万,笔者将继续维护专栏,一杯奶茶价格不止提供答案解析,承诺提供专栏内容免费技术答疑,直接咨询即可。助您提高安卓面试准备效率,为您面试保驾护航!

正文开始⬇

如果需要实现跨进程通讯,AIDL是非常好的选择,在日常开发中接触到AIDL的机会非常多,个人在面试中暂时没被问过,但实际开发中无论是在应用程序中还是系统服务中,都使用过AIDL。我们看看面试官可能会问什么吧:

  1. 请介绍什么是AIDL⭐⭐⭐⭐⭐
  2. 有几种AIDL文件?⭐⭐⭐
  3. 一个程序AIDL文件的数量? ⭐⭐
  4. 你有没有使用过AIDL?谈谈你如何实现的? ⭐⭐⭐⭐
  5. 知道AIDL语法中的定向tag吗?⭐⭐⭐
  6. 你能不能简单描述AIDL实现的原理?⭐⭐⭐

看完以下的解析,一定可以让面试官眼前一亮。

目录

  • 1、什么是AIDL
    • 1.1 AIDL支持的基础数据
    • 1.2 两种AIDL文件
      • 1.2.1 定义parcalable对象
      • 1.2.2 定义接口
    • 1.3 定向Tag
  • 2、使用AIDL实现跨进程通讯
    • 2.1 AIDL文件的数量
    • 2.2 应用程序中使用AIDL
      • 2.2.1 parcalable对象对象定义和实现
      • 2.2.2 AIDL接口定义文件
      • 2.2.3 服务端
      • 2.2.4 客户端
      • 2.2.5 几个重要类和方法的解析
      • 2.2.6 AIDL实现原理
        • 2.2.6.1 客户端
        • 2.2.6.2 服务端
  • 3、系统服务中使用AIDL(引导)

1、什么是AIDL

AIDL全称Android Interface Definition Language,也就是Android接口定义语言。因为AIDL的文件内容都是只有定义,而没有真正的实现。设计了这门语言是为了方便实现跨进程通讯,AIDL文件的后缀是“.aidl“。

当然AIDL只是一种辅助工具,可以让我们少写代码同时少出错。如果不用AIDL,一样可以实现跨进程通讯,只是比较麻烦。AIDL 的原理是Binder,真正有跨进程通讯能力的也是 Binder。

1.1 AIDL支持的基础数据

AIDL作为跨进程的方式之一,数据传输功能并不可少,AIDL默认支持的数据类型有:

  • Java中的八种基本数据类型,包括 int、long、float、double、byte,short,boolean,char
  • String 类型
  • CharSequence类型
  • List类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是AIDL是定义的parcelable对象(见1.2小节),List可以使用泛型
  • Map类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是AIDL是定义的parcelable对象(见1.2小节),Map是不支持泛型

1.2 两种AIDL文件

1.2.1 定义parcalable对象

如果传输的对象是自定义的类,难道AIDL无法支持?显然官方也考虑到该问题,AIDL第一种文件类型是用于定义parcelable对象(AIDL 只能传送继承 Parcelable 接口的类)。如需要传输一个Person类,需要在同个包下定义以下2个文件:

alt

  • Step1:需要先定义Person.aidl文件
package com.android.aidl.demo;

// Declare any non-default types here with import statements
// parcelable使用小写字母
parcelable Person;
  • Step2:然后再编写Person的实现类:
public class Person implements Parcelable {
    public int id;
    public String name;

    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }
    ...
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(id);
        dest.writeString(name);
    }
}

1.2.2 定义接口

AIDL另一种文件用于定义方法接口,以提供给其他进程调用本进程的能力。

// IMyService.aidl
package com.android.aidl.demo;

import com.android.aidl.demo.Person; // 1
// Declare any non-default types here with import statements

interface IMyService {
    void addPerson(in Person person); // 2

    List<Person> getPersonList();
}

其中,有一个知识点,Person实现类Person.java文件和定义接口的IMyService.aidl文件虽然在同个包里,但IMyService.aidl文件还是必须手动导入Person类,如[注释1]所示。

编写IMyService.aidl文件后,在Android Studio编译一下工程,就会在下图路径自动编程生成IMyService.java文件:

alt

1.3 定向Tag

仔细看[注释2],会发现相比于普通函数参数,这里多了一个‘in’符号,这是AIDL的语法之一,称之为“定向Tag”,共包含3种定向Tag:in、out、inout。这三个tag是用于控制客户端和服务端数据流向,且数据流向的主体以客户端传入服务端的对象为标准。具体看看如下代码:

void addPerson(in Person person);
void addPerson(out Person person);
void addPerson(inout Person person);

//调用
Person person = new Person("xurui");
addPerson(person);

此时数据流向的主体就是Person对象,在此假设Person对象里面就一个字符变量“name”,三个tag具体作用如下:

  • in:数据只能从客户端流向服务端,服务端会收到客户端传入的一个完整的对象,即收到一个Person对象,对象内部的name属性的值为“xurui”,但此时服务端对该Person对象进行的修改不会同步返回到客户端;
  • out:数据只能由服务端流向客户端,服务端只能收到客户端传入的对象的空值对象,即服务端收到的Person对象里面的name属性是空值,而非“xurui”,但服务端对接收到的空对象的任何修改都会同步返回到客户端;
  • inout:数据在客户端和服务端双向流通;服务端可以收到客户端传入一个Person对象,里面的name属性数值为“xurui”,此时服务端对该对象做的任何修改也会同步返回给客户端。

2、使用AIDL实现跨进程通讯

在日常开发中,一般使用到AIDL的情况就是自定义了一个Service,想提供该Service的能力供其他进程或者其他模块调用。而自定义Service最常见的方法有两种:

  • 通过Android Studio创建一个包含Service的应用程序;
  • 创建自定义系统服务;

所以本节以创建自定义应用程序和自定义系统服务为例,讲解如果AIDL的使用方法。

2.1 AIDL文件的数量

在实现跨进程通讯时,如果AIDL文件仅包含AIDL所支持的基础数据类型,则只需要1个AIDL文件。但如果像1.2.1小节的图片所示,需要支持自定义的Person类,则需要1+1=2个AIDL文件,即有N个非默认支持的基础数据类型,就需要N+1个AIDL文件。

2.2 应用程序中使用AIDL

在应用程序中使用AIDL,常见的就是一个Activity绑定一个Service,并调用Service提供的函数方法。无论Activity和Service在同个应用程序,还是分别在两个应用程序,对AIDL的使用都是大同小异的。不过有一点要注意,如果是同在一个应用程序,那么只需要一份AIDL文件。如果Activity和Service在两个程序,则两个程序各要有一份AIDL文件。我们以同在一个应用程序进行讲解。主要包括四大部分:parcalable对象定义和实现、AIDL接口定义文件、服务端、客户端;

2.2.1 parcalable对象对象定义和实现

本案例程序框架图和parcalable对象定义和实现(Person类),都在1.2.1小节详细说过,这里不重复叙述。

2.2.2 AIDL接口定义文件

本案例AIDL接口定义文件也在1.2.2小节介绍了,即IMyService.aidl文件,这里也不重复。

2.2.3 服务端

直接上代码:

public class MyPersonServer extends Service {

    class MyServiceNative extends IMyService.Stub { //3

        List<Person> myPersonList = new ArrayList<>();

        @Override
        public void addPerson(Person person) {
            myPersonList.add(person);
        }

        @Override
        public List<Person> getPersonList() {
            return myPersonList;
        }

        @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            return super.onTransact(code, data, reply, flags);
        }
    }

    private MyServiceNative mMyServiceNative = new MyServiceNative();

    @Override
    public IBinder onBind(Intent intent) {
        return mMyServiceNative; //4
    }
}

在MyPersonServer.java代码中,主要做了两件事。

  • [注释3]创建MyServiceNative内部类继承了Android Studio自动生成的IMyService.java文件里的Stub类,并重新实现了IMyService.aidl文件定义的两个接口addPerson()和getPersonList();
  • [注释4],通过onBind()返回MyServiceNative的实例。

2.2.4 客户端

客户端代码就在Activity里实现:

public class ClientActivity extends AppCompatActivity implements View.OnClickListener {
    Button btnBind;
    Button btnAddPerson;
    Button btnGetSize;
    TextView tvResult;
    IMyService myService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnBind = (Button) findViewById(R.id.btn_bind);
        btnAddPerson = (Button) findViewById(R.id.btn_add_person);
        btnGetSize = (Button) findViewById(R.id.btn_get_size);

        btnBind.setOnClickListener(this);
        btnAddPerson.setOnClickListener(this);
        btnGetSize.setOnClickListener(this);

        tvResult = (TextView) findViewById(R.id.txt_result);
    }

    @Override
    protected void onDestroy() {
        unbindService(mConn);
        super.onDestroy();
    }
    private ServiceConnection mConn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            myService = IMyService.Stub.asInterface(service); // 5
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            myService = null;
        }
    };

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

Android高频面试题全解析 文章被收录于专栏

#提供免费售后答疑!!花一杯奶茶的钱获得安卓面试答疑服务,稳赚不赔# Android发展已经很多年,安卓资料网上千千万,本专栏免费提供专栏内容技术答疑!!私聊当天必回。在阅读过程或者其他安卓学习过程有疑问,都非常欢迎私聊交流。

全部评论
爱佬
1 回复 分享
发布于 2022-12-22 17:24 陕西
前来字词!
点赞 回复 分享
发布于 2022-12-22 17:32 湖北

相关推荐

不愿透露姓名的神秘牛友
昨天 17:17
点赞 评论 收藏
分享
Lorn的意义:你这标个前端是想找全栈吗?而且项目确实没什么含金量,技术栈太少了,边沉淀边找吧 现在学院本想就业好一点四年至少得高三模式两年加油吧
点赞 评论 收藏
分享
评论
6
14
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务