前言

转载请声明,转自【https://www.cnblogs.com/andy-songwei/p/10256379.html】,谢谢!

只要是面试高级工程师岗位,Android跨进程通信就是最受面试官青睐的知识点之一。Android系统的运行由大量相互独立的进程相互协助来完成的,所以Android进程间通信问题,是做好Android开发高级工程师必须要跨过的一道坎。但是,我们是否真的清楚,Android中都有哪些方式实现跨进程通信呢?这些方式都有哪些优缺点?如何选择这些通信方式?Binder是什么?为什么要引入Binder?Binder是这么样实现跨进程通信的?AIDL是什么?AIDL和Binder又有什么关系呢?......

本文将对Android的跨进程通、进程内通信等方面做一些总结,以及比较详细地介绍AIDL的使用,主要内容如下:

其行文脉络大致如下,希望能加深读者对这方面内容的记忆:(1)Android基于Linux系统,所以先说系统进程相关知识和Linux IPC。(2)总结Android的IPC,顺带总结了Android进程内组件之间的通信方式。(3)Android为了克服Linux IPC中的缺点,引入了Binder,所以对Binder做了一些宏观上的介绍。(4)AIDL是实现Binder最常用的工具,所以详细介绍了AIDL相关内容。

一、基础知识简介

在介绍Android跨进程通信之前,笔者先简单啰嗦一下进程隔离、跨进程通信。

1、进程隔离

在操作系统中,进程与进程间的内存和数据都是不共享的。两个进程就好像大海中相互独立的两个岛屿,各自生活在互相平行的两个世界中,互不干扰,各自为政。这样做的目的,是为了避免进程间相互操作数据的现象发生,从而引起各自的安全问题。为了实现进程隔离,采用了虚拟地址空间,两个进程各自的虚拟地址不同,从逻辑上来实现彼此间的隔离。

2、跨进程通信

马克思主义哲学说,人是一切社会关系的总和。任何一个个体都不可能完全隔离于外界,都不可避免地与外界“互通有无”。进程也一样,每一个进程完成的功能有限,就像现在的生成线一样,往往就是只完成某一类功能,而不是把所有事情都给做了,就这样,每个进程就时不时需要与其他进程之间通信了。两个进程之间要进行通信,就需要采用特殊的通信机制:进程间通信(IPC:Inter-Process Communication,即进程间通信或跨进程通信,后文以IPC替代,在此声明)。

二、Linux跨进程通信

我们知道,Android系统就是基于Linux内核实现的,咱们先简单了解一下Linux系统的IPC方式。虽然不同的资料对各种方式的名称和种类说法不完全相同,但是主要来说有如下6种方式:(1)管道 Pipe;(2)信号Signal;(3)信号量Semaphore;(4)消息队列Message Queue;(5)共享内存Shared Memmory;(6)套接字Socket。读者若想深入了解这些方式,可以自行查阅,这里不展开介绍,只需要知道有这六种方式即可。

三、Android跨进程通信

Android IPC的方式在不同的资料中有不同的说法,但从大方向上看,可以归纳为如下四种(这里仅对各种方式做简单介绍和优劣对比,对具体如何使用,不做讲解):

1、Activity方式

Activity是四大组件中使用最频繁的,咱们先从它说起。使用Activity方式实现,就是使用startActivity()来启动另外一个进程的Activity。

(1)场景

我们在使用App的使用,往往会遇到如下几种情形:(1)浏览器中看到一篇比较不错的文章,分享到微信朋友圈或者微博;(2)在某个App中点击某个网址,然后界面跳转到浏览器中进行阅读;(3)使用美团外卖app,看到店家的电话,点击联系商家时,跳转到了电话拨打界面......这样的操作再频繁不过了。这些就是通过startActivity的方式从一个App,跳转到了另外一个App的Activity,从而实现了跨进程通信。

(2)使用

我们知道,在调用startActivity(Intent intent)的时候,intent有两个类型:显式Intent和隐式Intent。

1)显式Intent的使用方式如下,用于进程内组件间通信:

1 Intent intent = new Intent(this,OtherActivity.class);
2 startActivity(intent);

这种方式显式地指定了要跳转的Activtiy的class名称,不知道是不是因为这个原因而被称为显式intent的,笔者没有查证。这种方式用于进程内Activity的跳转,是跨模块间通信,而不是跨进程间通信。

2)隐式intent的使用方式如下,用于IPC:

1 Intent intent = new Intent();
2 intent.setAction(Intent.ACTION_CALL);
3 startActivity(intent);//startActivityForResult()同样,这里不赘述

Intent.ACTION_CALL就是字符串常量“android.intent.action.CALL”,这种方式通过setAction的方式来启动目标app的Activity,上述代码就是启动电话app的拨号界面,有时候还可以带上电话号码等参数。

由上可知,Activity实现跨进程通信的方式,适合于不同App之间功能界面的跳转。

2、Content provider(后面简称CP)方式

(1)场景

当我们开发App需要用到联系人,多媒体信息等数据的时候,往往会通过系统提供Uri,采用CP的方式去获取。Android系统中,数据主要存储在自带的SqlLite数据库中。应用要共享SqlLite中的数据给其他App操作(增、删、改、查),就要用到CP,也就是说,CP主要用于跨进程数据库共享。Android系统提供了很多的CP来供其它App使用,如多媒体信息、联系人、日历等。如下图显示了Android系统提供的CP,包名都是以"com.android.providers“开头的:

这些用于共享的数据其实都是存储在系统数据库中的,如下显示了meida CP中的数据库:

App开发者也可以自定义CP,把自己的数据提供给其它app使用,也可以自己定义操作权限,如只允许其它app读取自己的数据,而不允许修改等。

(2)特点

1)CP的使用场景,是提供数据共享。

2)CP本质上还是在操作数据库,数据存储在sdcard中,所以建立连接和操作数据都是耗时操作,所以注意开辟子线程去操作。

3)当数据库中数据有变化时,Content Observer监听到数据库变化也是有一定的滞后。

3、Broadcase方式

Broadcast使用非常简单,注册好广播,添加上action,就可以等着接收其他进程发出的广播。发送和接收广播时,还可以借助Intent来携带数据。但是广播的使用存在很多问题,被很多程序员吐槽,甚至鄙夷,所以选择用广播进行跨进程通信,是下下策。下面盘点一下Broadcast的槽点:

(1)Broadcast是一种单向的通信方式。当一个程序发送广播后,其他应用只能被动地接收,无法向发送者反馈。

(2)Broadcast非常消耗系统资源,会导致系统性能下降。

(3)速度慢,容易造成系统ANR。且除了Parall Broadcast外,无法保证接收到的时间,甚至不一定能收得到。

(4)如果使用Ordered Broadcast,一个Receiver执行时间过长,会影响后面接收者的接收时间,甚至还有可能被中间某个Receiver拦截,导致后面Receiver无法接收到。

(5)发送者无法确定谁会接收该广播,而接收者也无发确认是谁发来的广播。

(6)如果是静态注册的广播,一个没有开启的进程,都有可能被该广播激活。

......

总而言之,言而总之,使用Broadcast来实现跨进程通信,是下下之策!

4、Service方式

启动Service的方式有多种,有的用于跨进程通信,有的用于进程内部模块之间的通信,下面仅简单介绍一下跨进程通信的方式。

(1)startService()方式

1 Intent startIntent = new Intent ();
2 ComponentName componentName = new ComponentName(string packageName,string serviceClassName);
3 startIntent.setComponent(componentName );
4 startService( startIntent);

该方式启动远程Service实现跨进程通信,耦合度比较低,功能及代码结构清晰,但是存在以下缺点:

1)没有好的机制立即返回执行结果,往往Service完成任务后,还需要其他方式向Client端反馈。

2)Service端无法识别Client端是谁,只知道有启动命令,但无法知道是谁下的命令。

3)在已经创建好Service情况下,每次调用startService,就会执行onStartCommand()生命周期方法,相比于bindService,效率低下。

4)如果Client端忘记调用stopService()了,那么该Service会一直运行下去,这也是一个隐患。

所以,针对上述缺点,往往建议startService()方式用于同一个App内,跨进程的方式用后面将要讲到的AIDL来实现。

(2)bindService、Handler、Messager结合

这种方式也可以实现跨进程通信,据说和AIDL具有相同的功效,但比AIDL使用简单,详细可以阅读博文【Android总结篇系列:Android Service】。但是从笔者工作这些年来看,从没见过谁使用过这种方式,可能是笔者太孤陋寡闻了,这里就不多介绍了。

(3)AIDL

这种方式也是bindService()启动方式的一种使用情况,也是广受程序员们推崇的方式。前面说startService()和Broadcast如何不好,就是为了衬托AIDL如何的好!这是本文的主角,本文后面会专门详细讲解,这里不赘述。

值得注意的是,从Android L开始,系统对Service的隐式启动做了限制,需要至少包含包名或类名,具体可以查看博文【Android 5.0之后隐式声明Intent 启动Service引发的问题】,所以隐式启动Service时需要多注意这一点。

5、总结

从如上的介绍来看,其实Android中跨进程通信的实现,就是利用四大组件来实现的。对方式的选择,我们总结一下:

(1)如果跨进程需要界面上的交互操作,用隐式startActivity()方式实现。

(2)如果需要共享数据,用Content Provider方式实现。

(3)排除前两种情形,就用AIDL。

(4)仅仅为了完成功能,又确实不会用AIDL的,就用Broadcast吧!!!虽然很low,但比实现不了功能还是强多了

四、Android进程内通信

前面总结了几种跨进程通信的方式,这里顺便总结一下Android进程内部组件之间的通信方式。

(1)显示调用startActivity()。可以用Intent携带数据,而且如果用startActivityForResult(),还可以得到目标Activity完成任务后的反馈。

(2)startService/bindService。用于与service通信,其中Intent,回调方法等会携带数据或者对象,选择哪一个需要视情况而定。

(3)Boradcast。对于全局广播,前面已经diss过很多了,这里不多说了,能不用则不用。但是还有一种局部广播——LocalBroadcastManager,只能在app内部使用,也是一种进程内通信的方法,且在性能、安全等方面都要优于全局广播,对于它的使用,可以参考我的另外一篇文章【【朝花夕拾】四大组件之(一)广播篇】。

(4)Handler方式。这种方式在子线程和主线程通信中用得很普遍,跨模块使用也常见到,使用方便。需要注意的是Looper和线程问题。

(5)回调。最普遍的方式了,使用也很方便。一般会结合面向接口编程来实现,如果调用的层次比较深,同一个接口注册的地方太多,可读性会比较差,经常看得头晕。甚至有时候这些注册回调的地方会相互干扰。使用的时候要注意退出该模块的时候,把注册的回调变量置空销毁。

(6)EventBus。使用简单,耦合度低,代码可读性比较好,也受到很多开发者的喜爱。但也有一些缺点:1)需要导入第三方库。2)发送消息出去后,被注册的地方都会收到该消息,如果处理不当,开发者甚至不容易意识到哪些地方收到了消息并做了处理,带来不必要的混乱。3)当注册了EventBus的模块退出后,容易忘记反注册。

(7)SharePreference(简称SP)等数据存储方式。这个就是数据的持久化和模块间共享数据了,除了SP,还有网络存储,Sqilite数据库,文件存储等多种方式,功能可以类比跨进程通信的ContentProvider。

和跨进程通行方式选择一样,组件间的通信方式也要根据具体需要来确定了。从完成功能上讲,前面讲到的跨进程通信的方式,在进程内组件间通信也可以用,但是这样就像用牛刀杀鸡,大炮打蚊子了。出于性能方面的考虑,就不要将跨进程的方式用于进程内模块间通信了。

五、Binder概述

Binder是Android框架中非常重要的一个机制,在Framework中被广泛使用,理解Binder对阅读Framework源码有着很大的帮助。当然,在应用层等层面的跨进程通信中,也被广泛使用。有人说,理解Binder,是跨入高级Android工程师行列的第一步,先不管是不是夸大其词了,但足以说明Binder对于Android有多么重要,也说明理解Binder有一定的难度。

1、Binder是什么

Binder英文单词的意思就是“粘合剂”,把两个物体粘合在一起。说起Binder是什么,在IPC过程中,从不同的角度来看,它可以被定义为一种机制,或者实体类,或者远程代理,甚至是传输对象,咱们这里说的Binder是从宏观定义来看的,是说的Binder机制。在Android中,它是一种高效的IPC方式,是Android所特有的机制。它采用C/S架构模式,基于内存映射(mmap()),在系统内核空间将Client端和Service端两个用户空间的进程联系在一起。

2、Binder的由来

Binder前身是某公司开发的OpenBinder项目,后来该项目的作者加入了Google,同时把该项目带进了Android。从此以后,Binder就成为了Android中主要的跨进程通信机制。

3、Android为什么要引入Binder

前面第二点提到,Linux已经具备了这么多的IPC方式,为什么Android中还要引入Binder呢?原因是从性能、稳定性和安全性三方面考虑,Linux本身的那些方式,往往只能具备一部分因素,无法兼顾。但是Binder却不然,从性能上看,只需要一次数据拷贝,性能上仅次于共享内存;从稳定性上看,基于C/S架构,职责明确、架构清晰,稳定性好;从安全性上看,它为每个APP分配UID,而进程的UID是鉴别进程身份的重要标志。对于更详细的对比,推荐阅读【Android Bander设计与实现 - 设计篇的“引言”部分】,本文重点不是探索Linux,就不赘述了,咱们只需要知道Binder比Linux其它IPC方式更给力就够了。

4、Binder机制原理

Binder机制相关知识点和源码非常庞杂,很多技术书籍往往要花很大篇幅来阐述,比如罗升阳的《Android 系统源码情景分析》就花了100多页来剖析它。俗话说,要想倒出一碗水,你得先有一桶水。很不好意思,笔者现在一碗水都不到,这里就不献丑了,推荐一篇讲得比较好的博文:【写给 Android 应用工程师的 Binder 原理剖析】,看完后收获挺大的。

六、AIDL基本使用

      前面我们讲到,为了克服Linux中IPC各种方式的缺点,在Android中引入了Binder机制。但是当说起Binder在Android中的使用时,几乎所有的资料都是在说AIDL的使用。AIDL的全称是Android Interface Definition Language,即Android接口定义语言,是Binder机制实现Android IPC时使用比较广泛的工具。本节将以一个Demo演示一下AIDL的基本实现步骤。

源码地址:https://pan.baidu.com/s/1CyE_8-T9TDQLVQ1TDAEX2A   提取码:4auk 。

如下两个图展示了该Demo的结构图和AIDL关键文件:

   

图6.1                                                                                     图6.2

1、建立两个App,分别为Client端和Server端。

这个比较 好理解,Server端就是包含了Service真正干活的那一端;Client端就是通过远程操控指挥的那一端,分别在不同的App中。如下图所示:

2、在Server端main目录下建立aidl文件夹以及.aidl文件,并copy一份到Client端,如图6.1中②处所示结构。注意,Client端和Server端②处是一模一样的。另外,AS中提供了快捷方式创建aidl文件,在main处点击右键 > New > AIDL > AIDL File文件,按照提示给aidl文件命名即可自动创建完成,可以看到文件路径也是该项目的包名。

这里给aidl命名为IDemoService.aidl,这里需要注意的是命名规范,一般都是以“I”开头,表示是一个接口,其内容如下:

1 //========== IDemoService.aidl========
2 package com.songwei.aidldemoserver;
3 // Declare any non-default types here with import statements
4 interface IDemoService {
5     void setName(String name);
6     String getName();
7 }

3、Server端创建Service文件 AidlService.java,如图6.1中③处所示,代码如下:

 1 package com.songwei.aidldemoserver;
 2
 3 import android.app.Service;
 4 import android.content.Intent;
 5 import android.os.IBinder;
 6 import android.os.RemoteException;
 7 import android.util.Log;
 8
 9 public class AidlService extends Service {
10     private final static String TAG = "aidlDemo";
11
12     public AidlService() {
13     }
14
15     @Override
16     public void onCreate() {
17         super.onCreate();
18         Log.i(TAG, "server:[onCreate]");
19     }
20
21     @Override
22     public IBinder onBind(Intent intent) {
23         // TODO: Return the communication channel to the service.
24         Log.i(TAG, "server:[onBind]");
25         return new MyBinder();
26     }
27
28     @Override
29     public boolean onUnbind(Intent intent) {
30         Log.i(TAG, "server:[onUnbind]");
31         return super.onUnbind(intent);
32     }
33
34     @Override
35     public void onDestroy() {
36         super.onDestroy();
37         Log.i(TAG, "server:[onDestroy]");
38     }
39
40     class MyBinder extends IDemoService.Stub {
41         private String mName = "";
42
43         public void setName(String name) throws RemoteException{
44             Log.i(TAG, "server:[setName]");
45             mName = name;
46         }
47
48         @Override
49         public String getName() throws RemoteException {
50             Log.i(TAG, "server:[getName]");
51             return mName;
52         }
53     }
54 }

View Code

为了下文分析流程及生命周期,在其中各个方法中都添加了Log。

同时,在Server端的AndroidManifest.xml文件中添加该Service的注册信息。

1 <service
2     android:name=".AidlService"
3     android:exported="true">
4     <intent-filter>
5          <action android:name="com.songwei.aidl" />
6     </intent-filter>
7 </service>

这里有几点需要注意:

(1)exported属性值,如果有“intent-filter”,则默认值为true,否则为false。所以这里其实可以去掉,因为有“intent-filter”,其默认值就是true。

(2)由于笔者在后面启动该service的时候用的action的方式,所以这里就有了“intent-filter”里面的action。如果用其他方式启动,这个service的注册信息就需要相应的改动了,有一定开发经验的读者应该都知道,就不展开讲了,主要是怕读者容易忽略这里,所以特别提醒一下。

4、编译Sever端和Client端App,生成IDemoService.java文件。

当编译的时候,AS会自动为我们生成IDemoService.java文件,如图6.1和图6.2中④处所示。当你打开该文件的时候,是不是看到了如下场景?

惊不惊喜?意不意外?是不是一脸懵逼,大惊,卧槽,这尼玛啥玩意啊?

AIDL是Android接口定义语言,IDemoService.java是一个java中的interface(接口),现在是不是若有所思了呢?AIDL正是定义了IDemoService.java这个接口!!! 这个接口文件就是AIDL帮助咱们生成的Binder相关代码,这些代码就是用来帮助实现Client端和Server端通信的。前面第2步中提到的IDemoService.aidl文件,其作用就是作为原料通过AIDL来生成这些你貌似看不懂的代码的,第3步中的AidlService.java和后续在Client端App连接Server端App的时候,其实这个aidl文件就从来没有出现过,也就是说,它已经没有什么价值了。所以说,AIDL的作用就是用来自动生成Binder相关接口代码的,而不需要开发者手动编写。有些教程中说,可以不使用AIDL而手动编写这份Binder代码,AIDL不是Binder实现通信所必需的,笔者也没有尝试过手动编写,如果读者您想挑战,可以尝试一下!

咱们继续!

打开IDemoService.java文件后,点击主菜单兰Code > Reformat Code (或 Ctrl + Alt +L快捷健),你会发现画面变成了下面这个样子:

  1 /*
  2  * This file is auto-generated.  DO NOT MODIFY.
  3  * Original file: D:\\ASWorkspace\\testDemo\\aidldemoclient\\src\\main\\aidl\\com\\songwei\\aidldemoserver\\IDemoService.aidl
  4  */
  5 package com.songwei.aidldemoserver;
  6
  7 public interface IDemoService extends android.os.IInterface {
  8     /**
  9      * Local-side IPC implementation stub class.
 10      */
 11     public static abstract class Stub extends android.os.Binder implements com.songwei.aidldemoserver.IDemoService {
 12         private static final java.lang.String DESCRIPTOR = "com.songwei.aidldemoserver.IDemoService";
 13
 14         /**
 15          * Construct the stub at attach it to the interface.
 16          */
 17         public Stub() {
 18             this.attachInterface(this, DESCRIPTOR);
 19         }
 20
 21         /**
 22          * Cast an IBinder object into an com.songwei.aidldemoserver.IDemoService interface,
 23          * generating a proxy if needed.
 24          */
 25         public static com.songwei.aidldemoserver.IDemoService asInterface(android.os.IBinder obj) {
 26             if ((obj == null)) {
 27                 return null;
 28             }
 29             android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
 30             if (((iin != null) && (iin instanceof com.songwei.aidldemoserver.IDemoService))) {
 31                 return ((com.songwei.aidldemoserver.IDemoService) iin);
 32             }
 33             return new com.songwei.aidldemoserver.IDemoService.Stub.Proxy(obj);
 34         }
 35
 36         @Override
 37         public android.os.IBinder asBinder() {
 38             return this;
 39         }
 40
 41         @Override
 42         public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
 43             switch (code) {
 44                 case INTERFACE_TRANSACTION: {
 45                     reply.writeString(DESCRIPTOR);
 46                     return true;
 47                 }
 48                 case TRANSACTION_setName: {
 49                     data.enforceInterface(DESCRIPTOR);
 50                     java.lang.String _arg0;
 51                     _arg0 = data.readString();
 52                     this.setName(_arg0);
 53                     reply.writeNoException();
 54                     return true;
 55                 }
 56                 case TRANSACTION_getName: {
 57                     data.enforceInterface(DESCRIPTOR);
 58                     java.lang.String _result = this.getName();
 59                     reply.writeNoException();
 60                     reply.writeString(_result);
 61                     return true;
 62                 }
 63             }
 64             return super.onTransact(code, data, reply, flags);
 65         }
 66
 67         private static class Proxy implements com.songwei.aidldemoserver.IDemoService {
 68             private android.os.IBinder mRemote;
 69
 70             Proxy(android.os.IBinder remote) {
 71                 mRemote = remote;
 72             }
 73
 74             @Override
 75             public android.os.IBinder asBinder() {
 76                 return mRemote;
 77             }
 78
 79             public java.lang.String getInterfaceDescriptor() {
 80                 return DESCRIPTOR;
 81             }
 82
 83             @Override
 84             public void setName(java.lang.String name) throws android.os.RemoteException {
 85                 android.os.Parcel _data = android.os.Parcel.obtain();
 86                 android.os.Parcel _reply = android.os.Parcel.obtain();
 87                 try {
 88                     _data.writeInterfaceToken(DESCRIPTOR);
 89                     _data.writeString(name);
 90                     mRemote.transact(Stub.TRANSACTION_setName, _data, _reply, 0);
 91                     _reply.readException();
 92                 } finally {
 93                     _reply.recycle();
 94                     _data.recycle();
 95                 }
 96             }
 97
 98             @Override
 99             public java.lang.String getName() throws android.os.RemoteException {
100                 android.os.Parcel _data = android.os.Parcel.obtain();
101                 android.os.Parcel _reply = android.os.Parcel.obtain();
102                 java.lang.String _result;
103                 try {
104                     _data.writeInterfaceToken(DESCRIPTOR);
105                     mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
106                     _reply.readException();
107                     _result = _reply.readString();
108                 } finally {
109                     _reply.recycle();
110                     _data.recycle();
111                 }
112                 return _result;
113             }
114         }
115
116         static final int TRANSACTION_setName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
117         static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
118     }
119
120     public void setName(java.lang.String name) throws android.os.RemoteException;
121
122     public java.lang.String getName() throws android.os.RemoteException;
123
124 }

View Code

惊不惊喜?意不意外?这下是不是有种似曾相识的赶脚?这就是一个很普通的java中的接口文件而已,结构也非常简单。

神秘的面纱揭开一层了吧!后面在讲完Client端和Server端的连接及通信后,还会继续深入剖析这个文件。

5、Client端ClientActivity连接Server端AidlService并通信

ClientActivity.java的内容如下,布局文件在此省略,比较简单,就两个按钮,一个用于绑定,一个用于解绑,看Button命名也很容易分辨。

 1 package com.songwei.aidldemoclient;
 2
 3 import android.content.ComponentName;
 4 import android.content.Intent;
 5 import android.content.ServiceConnection;
 6 import android.os.IBinder;
 7 import android.os.RemoteException;
 8 import android.support.v7.app.AppCompatActivity;
 9 import android.os.Bundle;
10 import android.util.Log;
11 import android.view.View;
12 import android.widget.Button;
13
14 import com.songwei.aidldemoserver.IDemoService;
15
16 public class ClientActivity extends AppCompatActivity {
17
18     private final static String TAG = "aidlDemo";
19     private Button mBindBtn, mUnBindBtn;
20     private IDemoService mDemoService;
21     private boolean mIsBinded = false;
22     private ServiceConnection mConn = new ServiceConnection() {
23         //当与远程Service绑定后,会回调该方法。
24         @Override
25         public void onServiceConnected(ComponentName componentName, IBinder binder) {
26             Log.i(TAG, "client:[onServiceConnected]componentName=" + componentName);
27             mIsBinded = true;
28             //得到一个远程Service中的Binder代理,而不是该Binder实例
29             mDemoService = IDemoService.Stub.asInterface(binder);
30             try {
31                 //远程控制设置name值
32                 mDemoService.setName("Andy Song");
33                 //远程获取设置的name值
34                 String myName = mDemoService.getName();
35                 Log.i(TAG, "client:[onServiceConnected]myName=" + myName);
36             } catch (RemoteException e) {
37                 e.printStackTrace();
38             }
39         }
40
41         //该回调方法一般不会调用,如果在解绑的时候,发现该方法没有调用,不要惊慌,因为该方法的调用时机是Service被意外销毁时,比如内存不足时。
42         @Override
43         public void onServiceDisconnected(ComponentName name) {
44             Log.i(TAG, "client:[onServiceDisconnected]");
45             mIsBinded = false;
46             mDemoService = null;
47         }
48     };
49
50     @Override
51     protected void onCreate(Bundle savedInstanceState) {
52         super.onCreate(savedInstanceState);
53         setContentView(R.layout.activity_main);
54         mBindBtn = (Button) findViewById(R.id.btn_bind);
55         mBindBtn.setOnClickListener(new View.OnClickListener() {
56             @Override
57             public void onClick(View v) {
58                 //Android5.0及以后,出于对安全的考虑,Android系统对隐式启动Service做了限制,需要带上包名或者类名,这一点需要注意。
59                 Intent intent = new Intent();
60                 intent.setAction("com.songwei.aidl");
61                 intent.setPackage("com.songwei.aidldemoserver");
62                 bindService(intent, mConn, BIND_AUTO_CREATE);
63             }
64         });
65         mUnBindBtn = (Button) findViewById(R.id.btn_unbind);
66         mUnBindBtn.setOnClickListener(new View.OnClickListener() {
67             @Override
68             public void onClick(View v) {
69                 //解除绑定,当调用unbindService时,一定要判断当前service是否是binded的,如果没有,就会报错。
70                 if (mIsBinded) {
71                     unbindService(mConn);
72                     mDemoService = null;
73                     mIsBinded = false;
74                 }
75             }
76         });
77     }
78 }

View Code

代码中对一些关键和容易忽略的地方做了注释,可以结合起来进行理解。

6、运行

运行的时候,需要先启动Service端进程,才能在Client端中点击“绑定”的时候绑定成功。完成一次“绑定”和“解绑”,得到的log如下所示:

1 01-08 15:29:43.109 13532-13532/com.songwei.aidldemoserver I/aidlDemo: server:[onCreate]
2 01-08 15:29:43.110 13532-13532/com.songwei.aidldemoserver I/aidlDemo: server:[onBind]
3 01-08 15:29:43.113 13299-13299/com.songwei.aidldemoclient I/aidlDemo: client:[onServiceConnected]componentName=ComponentInfo{com.songwei.aidldemoserver/com.songwei.aidldemoserver.AidlService}
4 01-08 15:29:43.114 13532-13547/com.songwei.aidldemoserver I/aidlDemo: server:[setName]
5 01-08 15:29:43.114 13532-13546/com.songwei.aidldemoserver I/aidlDemo: server:[getName]
6 01-08 15:29:43.114 13299-13299/com.songwei.aidldemoclient I/aidlDemo: client:[onServiceConnected]myName=Andy Song
7 01-08 15:36:07.570 13299-13299/com.songwei.aidldemoclient I/aidlDemo: client:[onServiceDisconnected]

可以结合前面的ClientActivity.java和AidlService.java代码中的添加的log,来理解一下这个流程。当然,最好是能够按照上面的步骤,亲自动手实现一遍,比看10遍更有效果。

本节对介绍了AIDL最基本的知识,出于项目性质的原因,可能有些Android开发人员工作了好几年都不一定需要完整写一个AIDL实现两个App通信的功能。笔者就是这样,在前几年的工作中,虽然很早就知道AIDL这个东西,但确实是项目中就没有写过这个功能,直到最近两年。所以即便是读者您有不少开发经验了,也可能和笔者一样是个AIDL的初级开发者,这也是我花这么长的篇幅写这个基础内容的原因。

七、AIDL的深入使用和理解

前面一节讲了AIDL最进本的知识,这一节中将会结合更复杂的场景,更深入地介绍AIDL。(这一节的内容会持续补充完整)

1、Client端是如何实现调用Server端方法的

2、AIDL支持的数据类型

3、AIDL数据类序列化问题

4、AIDL回调的使用

当Server端某个操作执行完后,需要通知Client端自己完成了任务,这个时候回调就带来了很大的便利,和在同一个App中使用回调效果一样。例如在上一节的例子中,Server完成了setName()这个操作(耗时的异步操作更能体现回调的作用)后,要通知Client端自己完成了任务,可以进行下一步的操作了,就是这样一个场景。现在在前面AIDL例子基础上,对回调的使用步骤进行说明。

源码地址链接: https://pan.baidu.com/s/1eI8chrxYTGqSaIaeMZEOYg  提取码: 84cd 。

(1)在Server端IDemoService.aidl同一目录中添加一个新的.aidl接口文件,我这里命名为IDemoCallback.aidl,内容如下:

1 // =====IDemoCallback.aidl======
2 package com.songwei.aidldemoserver;
3 // Declare any non-default types here with import statements
4 interface IDemoCallback {
5     void testCallback(String msg);
6 }

(2)在IDemoService.aidl中添加注册/反注册两个方法

 1 // =======IDemoService.aidl======
 2 package com.songwei.aidldemoserver;
 3
 4 // Declare any non-default types here with import statements
 5 import com.songwei.aidldemoserver.IDemoCallback;
 6
 7 interface IDemoService {
 8
 9     void setName(String name);
10
11     String getName();
12
13     void registerCallback(IDemoCallback cb);
14
15     void unregisterCallback(IDemoCallback cb);
16 }

第13行和第15行为新增的方法。将这两个.aidl文件同步到Client端,使C/S两端的aidl文件完全一样,均为

最好将两个app都编译一遍,这样后面有些地方可以用代码补全,而不用手动书写。当然在AidlService.java中实现接口的时候肯定会报错的,把新增的方法补上就可以了。

(3)在AidlService.java中添加如下加粗部分的代码,

 1 private RemoteCallbackList<IDemoCallback> mCallbacks = new RemoteCallbackList<>();
 2
 3    private void callback(String msg) {
 4         int N = mCallbacks.beginBroadcast();
 5         for (int i = 0; i < N; i++) {
 6             try {
 7                 mCallbacks.getBroadcastItem(i).testCallback(msg);
 8             } catch (RemoteException e) {
 9                 e.printStackTrace();
10             }
11         }
12         mCallbacks.finishBroadcast();
13     }
14
15     class MyBinder extends IDemoService.Stub {
16         private String mName = "";
17
18         public void setName(String name) throws RemoteException {
19             Log.i(TAG, "server:[setName]");
20             mName = name;
21             callback("'Andy song' is setted");
22         }
23
24         @Override
25         public String getName() throws RemoteException {
26             Log.i(TAG, "server:[getName]");
27             return mName;
28         }
29
30         @Override
31         public void registerCallback(IDemoCallback cb) throws RemoteException {
32             Log.i(TAG,"server:[registerCallback]");
33             if(cb != null){
34                 mCallbacks.register(cb);
35             }
36         }
37
38         @Override
39         public void unregisterCallback(IDemoCallback cb) throws RemoteException {
40             Log.i(TAG,"server:[unregisterCallback]");
41             if(cb != null){
42                 mCallbacks.unregister(cb);
43             }
44         }
45
46     }

RemoteCallbackList是系统提供的一个用于存储回调对象的列表,其对象mCallbacks用于存储注册的IDemoCallback对象。通过第3行的callback()方法中的内容,我们可以推测它是采用一种类似于Broadcast的方式来实现回调的。      当setName()方法执行完毕后,callback("'Andy song' is setted");就会把回调信息反馈给Client中注册该回调的地方了。    (4)在ClientActivity.java中注册回调并处理相关逻辑
 1 ......
 2 public void onServiceConnected(ComponentName componentName, IBinder binder) {
 3             //Log.i(TAG, "client:[onServiceConnected]componentName=" + componentName);
 4             mIsBinded = true;
 5             //得到一个远程Service中的Binder代理,而不是该Binder实例
 6             mDemoService = IDemoService.Stub.asInterface(binder);
 7             try {
 8                 mDemoService.registerCallback(mDemoCallback);
 9                 //远程控制设置name值
10                 mDemoService.setName("Andy Song");
11                 //远程获取设置的name值
12                 String myName = mDemoService.getName();
13                 Log.i(TAG, "client:[onServiceConnected]myName=" + myName);
14
15             } catch (RemoteException e) {
16                 e.printStackTrace();
17             }
18 ......
19 private IDemoCallback mDemoCallback = new IDemoCallback.Stub() {
20         @Override
21         public void testCallback(String msg) throws RemoteException {
22             Log.i(TAG, "client:[testCallback] msg=" + msg);
23         }
24     };
25 ......
26 private void unRegisterCallback() {
27         try {
28             mDemoService.unregisterCallback(mDemoCallback);
29         } catch (RemoteException e) {
30             e.printStackTrace();
31         }
32     }
33 ......

在解绑定的地方调用unRegisterCallback()反注册回调即可,这样就完成了代码整个代码的编写。这里需要注意注册回调的时机,一定要在setName()执行前注册,否则Client端收不到回调信息。

(5)运行C/S端,然后“绑定”/“解绑”,就会看到如下log信息:

 1 01-11 14:11:44.714 5903-5903/com.songwei.aidldemoserver I/aidlDemo: server:[onCreate]2 01-11 14:11:44.715 5903-5903/com.songwei.aidldemoserver I/aidlDemo: server:[onBind]3 01-11 14:11:44.720 5903-5916/com.songwei.aidldemoserver I/aidlDemo: server:[registerCallback]4 01-11 14:11:44.720 5903-5916/com.songwei.aidldemoserver I/aidlDemo: server:[setName]5 01-11 14:11:44.721 6572-6572/com.songwei.aidldemoclient I/aidlDemo: client:[testCallback] msg='Andy song' is setted6 01-11 14:11:44.722 5903-5916/com.songwei.aidldemoserver I/aidlDemo: server:[getName]7 01-11 14:11:44.722 6572-6572/com.songwei.aidldemoclient I/aidlDemo: client:[onServiceConnected]myName=Andy Song8 01-11 14:11:58.589 5903-5916/com.songwei.aidldemoserver I/aidlDemo: server:[unregisterCallback]9 01-11 14:11:58.616 5903-5903/com.songwei.aidldemoserver I/aidlDemo: server:[onUnbind]
10 01-11 14:11:58.617 5903-5903/com.songwei.aidldemoserver I/aidlDemo: server:[onDestroy]

第5行就是回调信息,表示回调成功。

结语

本文主要是笔者用来整理跨进程通信的知识点,以及复习近两年才真正会用的AIDL,所以详略上是前半部分略,后半部分详,完全是按照笔者对知识点掌握程度来行文的。读者在阅读中,如果有些部分因为写得太简略而看得不过瘾,只能自己去查更详细的资料了;如果有些部分因为写得太详细而嫌啰嗦,完全可以跳着看;如果有些知识点因为笔者经验和水平问题写得有误或者阐述欠妥,请不吝赐教,万分感激!!!

转载于:https://www.cnblogs.com/andy-songwei/p/10256379.html

【朝花夕拾】Android性能篇之(七)Android跨进程通信篇相关推荐

  1. 【朝花夕拾】Android性能篇之(七)Android跨进程通信篇...

    前言 原文:https://www.cnblogs.com/andy-songwei/p/10256379.html 只要是面试高级工程师岗位,Android跨进程通信就是最受面试官青睐的知识点之一. ...

  2. Android 使用Messenger和Aidl实现跨进程通信

    Android Messenger和Aidl的使用 1.怎么使用多进程 为安卓的四大组件设置process属性值 例如:android:process=":test"或者 andr ...

  3. 【朝花夕拾】Android跨进程通信总结篇

    前言 原文:https://www.cnblogs.com/andy-songwei/p/10256379.html 只要是面试高级工程师岗位,Android跨进程通信就是最受面试官青睐的知识点之一. ...

  4. Android跨进程通信Binder机制与AIDL实例

    文章目录 进程通信 1.1 进程空间划分 1.2 跨进程通信IPC 1.3 Linux跨进程通信 1.4 Android进程通信 Binder跨进程通信 2.1 Binder简介 2.2 Binder ...

  5. 最新android跨进程通信的库,Android随笔之——跨进程通信(一) Activity篇

    在Android应用开发中,我们会碰到跨进程通信的情况,例如:你用QQ通讯录打电话的时候会调用系统的拨号应用.某些新闻客户端可以将新闻分享到QQ.微信等应用,这些都是跨进程通信的情况.简而言之,就是一 ...

  6. 【Binder】Android 跨进程通信原理解析

    前言 在Android开发的过程中,用到跨进程通信的地方非常非常多,我们所使用的Activity.Service等组件都需要和AMS进行跨进程通信,而这种跨进程的通信都是由Binder完成的. 甚至一 ...

  7. Android 跨进程通信大总结

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/111553746 本文出自[赵彦军的博客] 文章目录 1.Android进程 2.修 ...

  8. Android组件化跨进程通信框架Andromeda解析

    关于组件化 随着项目结构越来越庞大,模块与模块间的边界逐渐变得不清晰,代码维护越来越困难,甚至编译速度都成为影响开发效率的瓶颈. 组件化拆分是比较常见的解决方案,一方面解决模块间的耦合关系.将通用模块 ...

  9. Android IPC 进程进程间通信或跨进程通信

    Android IPC 机制 老话长谈,趁现在有时间对IPC做一个具体的总结. IPC是Inter-Process Communication的缩写,含义就是进程间通信或者跨进程通信,是指两个进程之间 ...

最新文章

  1. R语言使用hexSticker包将ggplot2包可视化的结果转换为六角图(六角贴、六角形贴纸、ggplot2 plot to hex sticker)、并自定义设置文本的内容、文本对应的字体
  2. hadoop MapReduce实例解析
  3. C#进阶系列——使用Advanced Installer制作IIS安装包(二:配置安装包依赖项和自定义dll)...
  4. 手把手带你玩转Tensorflow 物体检测 API (1)——运行实例
  5. App的selenium,Appium爬App!
  6. ABAP基础篇-语法-数据类型
  7. 耳目一新的在线答疑服务背后的核心技术
  8. Razor的主版页面框架
  9. docker使用_Docker的基本使用
  10. 深度学习花书-5.4 估计、偏差和方差
  11. 女生体寒的·手脚冰凉的都来看了(男的替老婆收着)
  12. 手把手带你玩摄像头模组
  13. 房产销售数据分析与可视化的设计与实现
  14. Access violation at address 77106D4E in module 'ntdll.dll'. Write of address 004051A5.
  15. osm服务器 显示乱码,怎样获得osm上的行政区划shp文件
  16. 四连通 matlab,针对matlab 四连通,和八连通的详解
  17. JMeter——》调整界面比例、字体大小
  18. VIM 编辑器使用指南
  19. Python之财富自由——天天基金数据爬取并导入excel(详细信息爬取)
  20. 盛世危局下的短视频:抖音们正遭遇优酷、腾讯、爱奇艺们包抄

热门文章

  1. 风一样的HTML5 2D渲染引擎Pixi.js——真正意义的跨平台开发工具
  2. 联想rd650管理口地址_MegaRAID Storage Manager RAID管理工具基本操作
  3. 山头狙击战(二分法)
  4. 善用 final 关键字,提升编码内功的一大捷径
  5. [Buzz.Today]Tinkercad
  6. 云原生k8s之CA证书创建和使用
  7. java表白程序玫瑰花_给爱人的玫瑰花表白程序代码--Java版
  8. 拿两千块钱的薪水要有一万块钱的范儿
  9. 4.BI 探索数据的数据可视化工具
  10. 烫发剂和染发剂中各化学药剂作用的研究(zz)