Android跨进程通信Binder机制与AIDL实例
Posted Tr0e
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android跨进程通信Binder机制与AIDL实例相关的知识,希望对你有一定的参考价值。
文章目录
在操作系统中,进程与进程间的内存和数据都是不共享的。这样做的目的,是为了避免进程间相互操作数据的现象发生,从而引起各自的安全问题。为了实现进程隔离,采用了虚拟地址空间,两个进程各自的虚拟地址不同,从逻辑上来实现彼此间的隔离。两个进程之间要进行通信,就需要采用特殊的通信机制:进程间通信(IPC:Inter-Process Communication,即进程间通信或跨进程通信,简称 IPC)。
进程通信
1.1 进程空间划分
一个进程空间分为 用户空间 & 内核空间(Kernel),即把进程内 用户 & 内核 隔离开来。二者的区别:
- 进程间用户空间的数据不可共享,所以用户空间 = 不可共享空间
- 进程间内核空间的数据可共享,所以内核空间 = 可共享空间,所有进程共用1个内核空间
进程内 用户空间 & 内核空间 进行交互 需通过 系统调用,主要通过函数:
- copy_from_user():将用户空间的数据拷贝到内核空间
- copy_to_user():将内核空间的数据拷贝到用户空间
1.2 跨进程通信IPC
- 进程隔离:为了保证 安全性 & 独立性,一个进程 不能直接操作或者访问另一个进程,即 android 的进程是相互独立、隔离的;
- 跨进程通信(IPC:Inter-Process Communication):即进程间需进行数据交互、通信。
跨进程通信的基本原理:
1.3 Linux跨进程通信
我们知道,Android系统就是基于Linux内核实现的,咱们先简单了解一下Linux系统的IPC方式。虽然不同的资料对各种方式的名称和种类说法不完全相同,但是主要来说有如下6种方式:(1)管道 Pipe;(2)信号Signal;(3)信号量Semaphore;(4)消息队列Message Queue;(5)共享内存Shared Memmory;(6)套接字Socket。读者若想深入了解这些方式,可以自行查阅,这里不展开介绍,只需要知道有这六种方式即可。
1.4 Android进程通信
Android IPC的方式在不同的资料中有不同的说法,但从大方向上看,可以归纳为如下四种(这里仅对各种方式做简单介绍和优劣对比,对具体如何使用,不做讲解):
1、Activity方式
Activity是四大组件中使用最频繁的,咱们先从它说起。使用Activity方式实现,就是使用startActivity()来启动另外一个进程的Activity。我们在使用App的使用,往往会遇到如下几种情形:(1)浏览器中看到一篇比较不错的文章,分享到微信朋友圈或者微博;(2)在某个App中点击某个网址,然后界面跳转到浏览器中进行阅读;(3)使用美团外卖app,看到店家的电话,点击联系商家时,跳转到了电话拨打界面…这样的操作再频繁不过了。这些就是通过startActivity的方式从一个App,跳转到了另外一个App的Activity,从而实现了跨进程通信。
我们知道,在调用startActivity(Intent intent)的时候,intent有两个类型:显式Intent和隐式Intent。显式Intent的使用方式如下,用于进程内组件间通信:
Intent intent = new Intent(this,OtherActivity.class);
startActivity(intent);
隐式intent的使用方式如下,用于IPC:
Intent intent = new Intent();
intent.setAction(Intent.ACTION_CALL);
startActivity(intent); //startActivityForResult()同样,这里不赘述
Intent.ACTION_CALL就是字符串常量“android.intent.action.CALL”,这种方式通过setAction的方式来启动目标app的Activity,上述代码就是启动电话app的拨号界面,有时候还可以带上电话号码等参数。由上可知,Activity实现跨进程通信的方式,适合于不同App之间功能界面的跳转。
2、Content provider(后面简称CP)方式
当我们开发App需要用到联系人,多媒体信息等数据的时候,往往会通过系统提供Uri,采用CP的方式去获取。Android系统中,数据主要存储在自带的SqlLite数据库中。应用要共享SqlLite中的数据给其他App操作(增、删、改、查),就要用到CP,也就是说,CP主要用于跨进程数据库共享。Android系统提供了很多的CP来供其它App使用,如多媒体信息、联系人、日历等。如下图显示了Android系统提供的CP,包名都是以"com.android.providers“开头的:
这些用于共享的数据其实都是存储在系统数据库中的,如下显示了meida CP中的数据库:
App开发者也可以自定义CP,把自己的数据提供给其它app使用,也可以自己定义操作权限,如只允许其它app读取自己的数据,而不允许修改等。CP的使用场景,是提供数据共享。CP本质上还是在操作数据库,数据存储在sdcard中,所以建立连接和操作数据都是耗时操作,所以注意开辟子线程去操作。当数据库中数据有变化时,Content Observer监听到数据库变化也是有一定的滞后。
3、Broadcase方式
Broadcast使用非常简单,注册好广播,添加上action,就可以等着接收其他进程发出的广播。发送和接收广播时,还可以借助Intent来携带数据。但是广播的使用存在很多问题,被很多程序员吐槽,甚至鄙夷,所以选择用广播进行跨进程通信,是下下策。下面盘点一下Broadcast的槽点:
- Broadcast是一种单向的通信方式。当一个程序发送广播后,其他应用只能被动地接收,无法向发送者反馈。
- Broadcast非常消耗系统资源,会导致系统性能下降。
- 速度慢,容易造成系统ANR。且除了Parall Broadcast外,无法保证接收到的时间,甚至不一定能收得到。
- 如果使用Ordered Broadcast,一个Receiver执行时间过长,会影响后面接收者的接收时间,甚至还有可能被中间某个Receiver拦截,导致后面Receiver无法接收到。
- 发送者无法确定谁会接收该广播,而接收者也无发确认是谁发来的广播。
- 如果是静态注册的广播,一个没有开启的进程,都有可能被该广播激活。
总而言之,言而总之,使用Broadcast来实现跨进程通信,是下下之策!
4、Service方式
启动Service的方式有多种,有的用于跨进程通信,有的用于进程内部模块之间的通信,下面仅简单介绍一下跨进程通信的方式。
(1)startService()方式
Intent startIntent = new Intent ();
ComponentName componentName = new ComponentName(string packageName,string serviceClassName);
startIntent.setComponent(componentName );
startService( startIntent);
该方式启动远程Service实现跨进程通信,耦合度比较低,功能及代码结构清晰,但是存在以下缺点:
-
没有好的机制立即返回执行结果,往往Service完成任务后,还需要其他方式向Client端反馈。
-
Service端无法识别Client端是谁,只知道有启动命令,但无法知道是谁下的命令。
-
在已经创建好Service情况下,每次调用startService,就会执行onStartCommand()生命周期方法,相比于bindService,效率低下。
-
如果Client端忘记调用stopService()了,那么该Service会一直运行下去,这也是一个隐患。
所以,针对上述缺点,往往建议startService()方式用于同一个App内,跨进程的方式用后面将要讲到的AIDL来实现。
(2)bindService、Handler、Messager结合
这种方式也可以实现跨进程通信,据说和AIDL具有相同的功效,但比AIDL使用简单,详细可以阅读博文【Android总结篇系列:Android Service】。
(3)AIDL
这种方式也是 bindService() 启动方式的一种使用情况,也是广受程序员们推崇的方式。前面说 startService() 和 Broadcast 如何不好,就是为了衬托 AIDL 如何的好!这是本文的主角,本文后面会专门详细讲解,这里不赘述。
【总结】从如上的介绍来看,其实 Android 中跨进程通信的实现,就是利用四大组件来实现的。对方式的选择,我们总结一下:
- 如果跨进程需要界面上的交互操作,用隐式startActivity()方式实现。
- 如果需要共享数据,用Content Provider方式实现。
- 排除前两种情形,就用AIDL。
- 仅仅为了完成功能,又确实不会用AIDL的,就用Broadcast吧!!!虽然很low,但比实现不了功能还是强多了。
Binder跨进程通信
传统的跨进程通信需拷贝数据2次,但 Binder 机制只需1次,主要是使用到了内存映射,具体下面会详细说明。
跨进程通信的核心原理:内存映射,具体请看文章:操作系统:图文详解 内存映射
对比 Linux (Android基于Linux)上的其他进程通信方式(管道、消息队列、共享内存、信号量、Socket),Binder 机制的优点有:
2.1 Binder简介
Binder 跨进程通信机制 模型 基于 Client - Server 模式:
此处重点讲解 Binder 驱动的作用和 原理:
2.2 Binder驱动
2.3 Binder原理
Client进程、Server进程 & Service Manager 进程之间的交互 都必须通过Binder驱动(使用 open 和 ioctl文件操作函数),而非直接交互。具体原因:
- Client进程、Server进程 & Service Manager进程属于进程空间的用户空间,不可进行进程间交互;
- Binder驱动 属于 进程空间的 内核空间,可进行进程间 & 进程内交互。
所以,原理图可表示为以下(虚线表示并非直接交互—):
Binder驱动 & Service Manager进程 属于 Android基础架构(即系统已经实现好了);而Client 进程 和 Server 进程 属于Android应用层(需要开发者自己实现)。所以,在进行跨进程通信时,开发者只需自定义Client & Server 进程并显式使用上述3个步骤,最终借助 Android的基本架构功能就可完成进程间通信:
AIDL编程Demo
前面我们讲到,为了克服 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 关键文件:
建立两个App,分别为Client端和Server端。
这个比较 好理解,Server端就是包含了Service真正干活的那一端;Client端就是通过远程操控指挥的那一端,分别在不同的App中。如下图所示:
3.1 服务端
1、在Server端main目录下建立aidl文件夹以及.aidl文件,并copy一份到Client端,如图6.1中②处所示结构。注意,Client端和Server端②处是一模一样的。另外,AS中提供了快捷方式创建aidl文件,在main处点击右键 > New > AIDL > AIDL File文件,按照提示给aidl文件命名即可自动创建完成,可以看到文件路径也是该项目的包名。
这里给aidl命名为IDemoService.aidl,这里需要注意的是命名规范,一般都是以“I”开头,表示是一个接口,其内容如下:
//========== IDemoService.aidl========
package com.songwei.aidldemoserver;
// Declare any non-default types here with import statements
interface IDemoService
void setName(String name);
String getName();
2、Server端创建Service文件 AidlService.java,如图6.1中③处所示,代码如下:
package com.songwei.aidldemoserver;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
public class AidlService extends Service
private final static String TAG = "aidlDemo";
public AidlService()
@Override
public void onCreate()
super.onCreate();
Log.i(TAG, "server:[onCreate]");
@Override
public IBinder onBind(Intent intent)
// TODO: Return the communication channel to the service.
Log.i(TAG, "server:[onBind]");
return new MyBinder();
@Override
public boolean onUnbind(Intent intent)
Log.i(TAG, "server:[onUnbind]");
return super.onUnbind(intent);
@Override
public void onDestroy()
super.onDestroy();
Log.i(TAG, "server:[onDestroy]");
class MyBinder extends IDemoService.Stub
private String mName = "";
public void setName(String name) throws RemoteException
Log.i(TAG, "server:[setName]");
mName = name;
@Override
public String getName() throws RemoteException
Log.i(TAG, "server:[getName]");
return mName;
为了下文分析流程及生命周期,在其中各个方法中都添加了Log。同时,在Server端的AndroidManifest.xml文件中添加该Service的注册信息(注意exported属性值,如果有“intent-filter”,则默认值为true,否则为false。所以这里其实可以去掉,因为有“intent-filter”,其默认值就是true):
<service
android:name=".AidlService"
android:exported="true">
<intent-filter>
<action android:name="com.songwei.aidl" />
</intent-filter>
</service>
3、编译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快捷健),你会发现画面变成了下面这个样子:
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: D:\\\\ASWorkspace\\\\testDemo\\\\aidldemoclient\\\\src\\\\main\\\\aidl\\\\com\\\\songwei\\\\aidldemoserver\\\\IDemoService.aidl
*/
package com.songwei.aidldemoserver;
public interface IDemoService extends android.os.IInterface
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.songwei.aidldemoserver.IDemoService
private static final java.lang.String DESCRIPTOR = "com.songwei.aidldemoserver.IDemoService";
/**
* Construct the stub at attach it to the interface.
*/
public Stub()
this.attachInterface(this, DESCRIPTOR);
/**
* Cast an IBinder object into an com.songwei.aidldemoserver.IDemoService interface,
* generating a proxy if needed.
*/
public static com.songwei.aidldemoserver.IDemoService asInterface(android.os.IBinder obj)
if ((obj == null))
return null;
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.songwei.aidldemoserver.IDemoService)))
return ((com.songwei.aidldemoserver.IDemoService) iin);
return new com.songwei.aidldemoserver.IDemoService.Stub.Proxy(obj);
@Override
public android.os.IBinder asBinder()
return this;
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
switch (code)
case INTERFACE_TRANSACTION:
reply.writeString(DESCRIPTOR);
return true;
case TRANSACTION_setName:
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
this.setName(_arg0);
reply.writeNoException();
return true;
case TRANSACTION_getName:
data.enforceInterface(DESCRIPTOR);
java.lang.String _result = this.getName();
reply.writeNoException();
reply.writeString(_result);
return true;
return super.onTransact(code, data, reply, flags);
private static class Proxy implements com.songwei.aidldemoserver.IDemoService
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
mRemote = remote;
@Override
public android.os.IBinder asBinder()
return mRemote;
public java.lang.String getInterfaceDescriptor()
return DESCRIPTOR;
@Override
public void setName(java.lang.String name) throws android.os.RemoteException
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(name);
mRemote.transact(Stub.TRANSACTION_setName, _data, _reply, 0);
_reply.readException();
finally
_reply.recycle();
_data.recycle();
@Override
public java.lang.String getName() throws android.os.RemoteException
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
finally
_reply.recycle();
_data.recycle();
return _result;
static final int TRANSACTION_setName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
public void setName(java.lang.String name) throws android.os.RemoteException;
public java.lang.String getName() throws android.os.RemoteException;
惊不惊喜?意不意外?这下是不是有种似曾相识的赶脚?这就是一个很普通的java中的接口文件而已,结构也非常简单。
神秘的面纱揭开一层了吧!后面在讲完Client端和Server端的连接及通信后,还会继续深入剖析这个文件。
3.2 客户端
Client端通过ClientActivity活动类来连接Server端AidlService并通信。ClientActivity.java 的内容如下,布局文件在此省略,比较简单,就两个按钮,一个用于绑定,一个用于解绑,看Button命名也很容易分辨:
package com.songwei.aidldemoclient;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle以上是关于Android跨进程通信Binder机制与AIDL实例的主要内容,如果未能解决你的问题,请参考以下文章
Android Binder,AIDL跨进程通讯详解与实现,看一遍就懂
Android Studio创建AIDL文件并实现进程间通讯