Android面试一天一题(Day 36:AIDL)

上一章节,我们讲到了Android中的Binder机制,一个Android开发天天用到,但又不明就理的神密存在。这一节接着Binder这个话题,讲一讲AIDL,让大家对Binder机制有一个直观的认识。

面试题:AIDL是什么?你有使用过它吗,它支持哪些数据类型?

AIDL是Android Interface Definition Language的简写,即Android接口定义语言。我们知道Android系统为每一个应用开启一个独立的虚拟机,每个应用都运行在各自进程里(默认情况下),彼此之间相互独立,无法共享内存。当一个应用想要访问另一个应用的数据或调用其方法,就要用到Android系统提供的IPC机制。而AIDL就是Android实现IPC机制的方式之一。

除了AIDL,Android还提供了Messenger来实现跨进程通信,不过Messenger是以单线程串行方式(消息队列)来处理来自不同客户端的访问的,并不适合多线程并发访问。当需要提供跨进程以及多线程并发服务时就需要AIDL上场了。

Messenger实际上也是以AIDL作为其底层结构。

其实要应对AIDL相关的面试题,除了了解清楚它的作用和注意的事项外,最有效的手段莫过于自己动手写几次。这里给大家定两个小目标:

  • 会使用AIDL进行进程间通信;
  • 会手写AIDL的编码,加深对Binder机制的理解。

创建AIDL

我们先实现第一个,在Android Studio中创建一个简单的AIDL项目,实现IPC通信。

Step1. 创建.aidl文件
我们在对应的src的Package下创建一个AIDL文件(Android Studio->File->New->AIDL->AIDL file),创建后Android Studio会自动把这个.aidl文件放到一个aidl的目录下。

Android SDK Tool会根据我们的.aidl文件自动生成一个同名的.java文件,如:AIDLTest/app/build/generated/source/aidl/debug/net/goeasyway/aidltest/IRemoteService.java

basicTypes方法中给我们展示了AIDL支持的基本数据类型,除此之外,AIDL还支持:CharSequence, List & Map(List和Map中的所有元素都必须是AIDL支持的数据类型、其他AIDL生成的接口或您声明的可打包类型。)

Step2. 创建一个Service暴露AIDL接口并实现AIDL的接口函数
如下代码,创建一个Service:

public class RemoteService extends Service {
    public RemoteService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return binder; //暴露给客户端
    }

    // 实现AIDL接口
    private final IRemoteService.Stub binder = new IRemoteService.Stub() {

        @Override
        public int getPid() throws RemoteException {
            return Process.myPid();
        }

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
                               double aDouble, String aString) throws RemoteException {

        }
    };
}

然后在MainActivity通过bindService绑定这个服务,即可以获得AIDL的接口调用的引用。运行前,我们设置一下AndroidManifest.xml文件记这个Service运行在一个单独的进程中:

        <service
            android:name=".RemoteService"
            android:process=":remote"
            android:enabled="true"
            android:exported="true"/>

现在来bindService:

public class MainActivity extends AppCompatActivity {
    private final static String TAG = "MainActivity";
    private IRemoteService remoteService;

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

        Intent intent = new Intent();
        intent.setClass(this, RemoteService.class);
        bindService(intent, connection, Service.BIND_AUTO_CREATE); // 绑定服务
    }


    private ServiceConnection connection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            remoteService = IRemoteService.Stub.asInterface(service); //获取AIDL的接口实现引用
            try {
                Log.i(TAG, "Client pid= " + Process.myPid());
                Log.i(TAG, "RemoteService pid= " + remoteService.getPid());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        public void onServiceDisconnected(ComponentName className) {
            Log.e(TAG, "Service has unexpectedly disconnected");
            remoteService = null;
        }
    };
}

从输出的日志看到Service和Activity运行在两个不同的进程中:

02-05 09:51:40.154 18992-18992/net.goeasyway.aidltest I/MainActivity: Client pid= 18992
02-05 09:51:40.154 18992-18992/net.goeasyway.aidltest I/MainActivity: RemoteService pid= 19022

到这里,我们完成了一个AIDL的范例,有几个地方可能会对我们造成困扰:

  • Stub类:Binder的实现类,服务端需要实现这个类来提供服务。
  • asInterface函数: 一个静态函数,用来将IBinder转换成对应的Binder的引用。先通过queryLocalInterface查询,如果服务端和客户端都是在同一个进程,那么就不需要跨进程了,直接将IRemoteService当做普通的对象来使用,否则会返回远程对象的代理对象(Proxy)。
public static net.goeasyway.aidltest.IRemoteService asInterface(android.os.IBinder obj)
{
    if ((obj==null)) {
        return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin!=null)&&(iin instanceof net.goeasyway.aidltest.IRemoteService))) {
        return ((net.goeasyway.aidltest.IRemoteService)iin);
    }
    return new net.goeasyway.aidltest.IRemoteService.Stub.Proxy(obj);
}

通过IPC传递对象

现在,我们加大一下难度,AIDL的接口使用一个我们自己定义的类为参数(或者返回值)。实现步骤如下:

  • 添加一个自定义对象类,并且要实现Parcelable接口,如MyProcess.java;
  • 在AIDL目录下的相同Pacage下添加一个同名的AIDL文件,如MyProcess.aidl;

注意:通过“Android Studio->File->New->AIDL->AIDL file”不让你创建和MyProcess.java同名的AIDL文件,你可以直接用通过“Android Studio->File->New->File”创建一个MyProcess.aidl。

  • 在AIDL接口类中添加一个接口函数,使用MyProcess做为参数或者返回值;

其他的细节大家可以直接查看Github上的代码:https://github.com/goeasyway/AIDL_Test (或者查看提交的说明找到具体每次的代码区别:https://github.com/goeasyway/AIDL_Test/commits/master

in、out & inout
这节我们看到“MyProcess getProcess(in MyProcess clientProcess);”这个接口的参数有一个“in”修饰符,这也是一个常见的面试题,可以考察一下对方是否真的写过AIDL的代码。

问题:AIDL中的接口函数有时会使用in、out或者inout的参数修饰符,它们各表示什么意思?在什么情况下要使用呢?

in、out和inout表示数据的流向。大家可以把AIDL的客户端和服务端理解成两个进程(其实大多数情况也是这样才会使用AIDL),从客户端流向服务端用in表示,表示这个对象是从客户端中传递到服务端,在服务端修改这个对象不会对客户端输入的对象产生影响。

而out则表示,数据只能从服务端影响客户端,即客户端输入这个参数时,服务端并不能获取到客户端的具体实例中的数据,而是生成一个默认数据,但是服务端对这个默认数据的修改会影响到客户端的这个类对象实例发生相应的改变。

理解了in、out之后,inout自然不需要再解释了。AIDL默认支持的数据类型使用in修饰符,对于我们自定义的Parcelable对象,一般情况下我们也是使用in,如果没有必要,应该尽量避免inout。

Intent也是Parcelable实现
也许你会想到,我们在Activity间可以通过Intent携带参数,其实你去看的源码的话会发现Intent也是一个Parcelable的实现类,而且在系统的工程中也有一个Intent.aidl文件(路径:/frameworks/base/core/java/android/content/Intent.aidl)。所以,它才可以在进程间传递。

注:目前Client端和Server端在同一工程中,如果分开在不同的工程的话,Client端所在的工程要把Server端提供的.aidl复制到同名Pacage的AIDL代码目录下。

手动方式创建AIDL(不依赖AIDL工具,手写远程AIDL的代码完成跨进程通信)

通过AIDL,可以让本地调用远程服务的接口就像调用本地接口那么简单,让用户无需关注内部细节,只需要实现自己的业务逻辑接口,内部复杂的参数序列化发送、接收、客户端调用服务端的逻辑,用户并不需要关心。

AIDL的代码生成器,已经根据.aidl文件自动帮我们生成Proxy、Stub(抽象类)两个类,并且把客户端代理mRemote的transact()过程以及服务器端的onTtransact()过程默认实现好了,我们只需要在服务端继承Stub,实现自己的业务方法即可。

但现在,为了进一步加深对Binder机制的理解,我们来做一个手动实现编写AIDL相关代码的练习。

具体的代码大家可以参考:https://github.com/goeasyway/AIDL_Test/tree/master/app/src/main/java/net/goeasyway/aidltest/diy

这个包里有三个类:IRmote.java为接口,Stub.java为Binder实现类(Service端要实例化它并在onBind返回),Proxy.java为代理类,提供给客户端使用的,通过Binder驱动和服务端通信。

大家可以看到这个练习不需要.aidl文件。

在这几个代码中,大家需要搞清楚这个类(或者接口)的关系:

  • Binder
    Binder本地对象。
  • IBinder
    IBinder是一个接口,它代表了一种跨进程传输的能力。
  • IInterface
    IBinder负责数据传输,那么client与server端的调用契约呢?这里的IInterface代表的就是远程server对象具有什么能力。具体来说,就是aidl里面的接口。
  • Proxy
    代表远程进程的Binder对象的本地代理,继承自IBinder,因而具有跨进程传输的能力。实际上,在跨越进程的时候,Binder驱动会自动完成代理对象和本地对象的转换。
  • Stub
    这个类继承了Binder, 说明它是一个Binder本地对象,它实现了IInterface接口,表明它具有远程Server承诺给Client的能力;Stub是一个抽象类,具体的IInterface的相关实现需要我们手动完成。

如果觉得有点难理解的话,不妨先动手写了再来看。

小结

如何使用AIDL应该是一个高级工程师必备技能,如果你之前不太了解它的话,那么我强烈建议你完成上面的两个小练习,再回过头去看它的解说。

之后,你将不再惧怕和AIDL相关的面试题。

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

推荐阅读更多精彩内容