Android IPC 之获取服务(IBinder)

article/2025/9/2 9:03:08

前言

IPC 系列文章:
建议按顺序阅读。

Android IPC 之Service 还可以这么理解
Android IPC 之Binder基础
Android IPC 之Binder应用
Android IPC 之AIDL应用(上)
Android IPC 之AIDL应用(下)
Android IPC 之Messenger 原理及应用
Android IPC 之服务端回调
Android IPC 之获取服务(IBinder)
Android Binder 原理换个姿势就顿悟了(图文版)

通过前面的文章我们知道,要进行进程通信的核心是能拿到另一个进程暴露出来的IBiner引用。本篇将重点分析获取IBinder的方式及其原理。
通过本篇文章,你将了解到:

1、获取系统服务
2、获取自定义服务
3、两者区别与联系

本篇文章,系统服务、自定义服务里的服务并非单纯是指Service,而是提供某一类功能的"服务"。

1、获取系统服务

简单例子

以手机振动为例:

        Vibrator vibrator = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE);vibrator.vibrate(1000);

调用Context 方法getSystemService(xx),xx表示服务名字,最终返回Vibrator。
拿到Vibrator 引用后就可以调用相应的方法让手机振动。
继续沿着方法调用分析:

#ContextImpl.java@Overridepublic Object getSystemService(String name) {return SystemServiceRegistry.getSystemService(this, name);}#SystemServiceRegistrypublic static Object getSystemService(ContextImpl ctx, String name) {//从map 里获取键值ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);return fetcher != null ? fetcher.getService(ctx) : null;}

这个map从哪里来呢?在SystemServiceRegistry 静态代码块里注册的:

#SystemServiceRegistry.javastatic {...registerService(Context.VIBRATOR_SERVICE, Vibrator.class,new CachedServiceFetcher<Vibrator>() {@Overridepublic Vibrator createService(ContextImpl ctx) {return new SystemVibrator(ctx);}});...}

可以看出返回了SystemVibrator,它是Vibrator(抽象类)的子类。
Vibrator.vibrate(xx)最终调用了如下方法:

#SystemVibrator.javaprivate final IVibratorService mService;public SystemVibrator(Context context) {super(context);//获取服务端提供的接口mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));}public void vibrate(int uid, String opPkg, VibrationEffect effect,String reason, AudioAttributes attributes) {if (mService == null) {Log.w(TAG, "Failed to vibrate; no vibrator service.");return;}try {//真正调用之处mService.vibrate(uid, opPkg, effect, usageForAttributes(attributes), reason, mToken);} catch (RemoteException e) {Log.w(TAG, "Failed to vibrate.", e);}}

了解过AIDL的同学都会知道,熟悉的套路:

  • mService 为服务端提供的接口,客户端调用其提供的方法即可实现相应的功能。
  • 客户端为当前待使用振动服务的App进程,服务端为提供振动服务的进程。

image.png

获取IBinder

振动服务的IBinder是通过:

ServiceManager.getService("vibrator")

获取的。

#ServiceManager.javapublic static IBinder getService(String name) {try {IBinder service = sCache.get(name);if (service != null) {return service;} else {//获取IBinderreturn Binder.allowBlocking(rawGetService(name));}} catch (RemoteException e) {Log.e(TAG, "error in getService", e);}return null;}private static IServiceManager getIServiceManager() {if (sServiceManager != null) {return sServiceManager;}//获取服务端的ServiceManagersServiceManager = ServiceManagerNative.asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));return sServiceManager;}private static IBinder rawGetService(String name) throws RemoteException {...final IBinder binder = getIServiceManager().getService(name);...return binder;}

又是熟悉的套路,IServiceManager 为ServiceManager服务端提供的接口,通过该接口获取振动服务的IBinder引用。
其中BinderInternal.getContextObject()) 获取ServiceManager的IBinder。
此处需要说明一下:

Client 需要从ServiceManager获取震动服务的IBinder,而Client本身需要和ServiceManager通信,要通信那么得有IBinder吧。BinderInternal.getContextObject())就是为了获取ServiceManager的IBinder,该方法从Binder驱动获取了IBinder引用。

注册服务

ServiceManager是如何找到振动服务的呢?
Android 系统启动后,会开启system_server进程,该进程里开启了很多系统服务,包括AMS、WMS、振动服务等。

#SystemServer.javaprivate void startOtherServices() {...VibratorService vibrator = null;...vibrator = new VibratorService(context);//向ServiceManager注册振动服务ServiceManager.addService("vibrator", vibrator);...}

继续来看addService(xx):

#ServiceManager.javapublic static void addService(String name, IBinder service) {addService(name, service, false, IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT);}public static void addService(String name, IBinder service, boolean allowIsolated,int dumpPriority) {try {//IPC 调用注册服务getIServiceManager().addService(name, service, allowIsolated, dumpPriority);} catch (RemoteException e) {Log.e(TAG, "error in addService", e);}}

调用ServiceManager接口添加服务到ServiceManager里。

小结

好了,现在从头到尾再捋一下。

1、ServiceManager 进程启动
2、system_server 进程启动,并将各个服务(包括振动服务)添加到ServiceManager里
3、客户端从ServiceManager里获取振动服务

用图表示:
image.png

其中 Client、ServiceManager、SystemServer 分别运行于三个不同的进程,三者之间通过Binder进行IPC。实线为其调用目的,虚线为其调用手段。

1、SystemServer 通过IPC1 向ServiceManager注册服务的IBinder引用
2、Client想要使用服务(如振动服务),先通过IPC2 向ServiceManager获取
3、Client拿到服务IBinder后,调用服务接口(IPC3),使用服务提供的具体功能

为了减少多次无用IPC调用,因此Client会将拿到的各种服务缓存到数组里,当要查询的服务已经存在,则不用进行IPC2,直接使用IPC3。

系统提供的服务如AMS、WMS、PMS等都将IBinder封装在xxManager(如WindowManager等)里,通过xxManager就可以进行IPC使用具体的服务。

2、获取自定义服务

上面说了系统提供的服务需要注册到ServiceManager里,以便后来者查询使用之。那么我们自己定义的服务该如何使用呢?

Service 的绑定流程

先来看看典型的绑定流程:
服务端代码:

    IStudentServer iStudentServer = IStudentServer.Stub() {@Overridepublic void say(String world) throws RemoteException {Log.d(TAG, "hello " + world);}};@Nullable@Overridepublic IBinder onBind(Intent intent) {return iStudentServer.asBinder();}

客户端代码:

    ServiceConnection serviceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {//重点在service 类型IStudentServer iStudentServer = IStudentServer.Stub.asInterface(service);try {iStudentServer.say("hello");   } catch (Exception e) {}}@Overridepublic void onServiceDisconnected(ComponentName name) {}};private void bindService() {Intent intent = new Intent(MainActivity.this, MyService.class);bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);}

大致阐述上述流程:

1、Service 构造Binder对象,并将IBinder在onBind(xx)传递出去
2、客户端在绑定Service成功后会收到服务端传递过来的IBinder
3、通过该IBinder获取关联的接口操作服务端

可以看出,我们在Service里定义业务逻辑(Server端),并开放了接口,通过Service的绑定功能将接IBinder传递给客户端,这和获取系统服务的逻辑是一样的,核心都是IBinder的传递,接下来从源头入手查看IBinder的传递。

从Context.bindService(xx)开始

由于涉及到的代码较多,此处就不贴完整源码了,重点关注关键之处和IPC 流程,多用图示之。
绑定流程图:
image.png

大致解释上图元素构成:
最顶上方框为类名。
红色表示它们都运行在同一进程,暂且称之为客户端进程。
绿色表示它们都运行在同一进程,暂且称之为系统服务进程。
黄色表示它们都运行在同一进程,暂且称之为服务端进程。

红色箭头表示该调用为进程间调用,用IPC 表示之。其余为本进程内的对象调用。

分别来分析重点1、2、3。
重点1
客户端发起绑定操作,传入ServiceConnection 引用,该引用在ContextImpl.bindServiceCommon(xx)里被封装在ServiceDispatcher里,而ServiceDispatcher又持有InnerConnection引用,InnerConnection 继承自IServiceConnection.Stub 可以跨进程调用。
也就是说,客户端进程留下了一个"桩",等待别的进程调用。

重点2
AMS 收到客户端的绑定指令后,发起绑定操作,通过IPC 调用服务端接口。
最终调用到服务端的onBind(xx)方法,该方法里返回服务端的IBinder引用。

重点3
服务端返回IBinder引用后,委托AMS 发布这个IBinder,IBinder找到对应的客户端进程。而在重点1里客户端已经留下了"桩",此时AMS 顺势找到这个"桩"直接调用ServiceConnection的onServiceConnected(xx),就能将IBinder传递给客户端。

可能比较绕,我们从进程的角度再简化一下:
image.png

可以看出,以上发生了四次IPC 操作(当然里面还涉及到其它的IPC,此处忽略)。IBinder传递要经过两次IPC。

IBinder 传递

上面分析了通过绑定流程返回服务端的IBinder引用。
但是运行的过程中却发现问题:
服务端返回的IBinder是:IStudentServer
而客户端收到的IBinder是:BinderProxy
这个是怎么回事呢?
既然IBinder是通过进程间传递的,看看其是否是支持序列化。

    public interface IBinder {...}public class Binder implements android.os.IBinder {...}

发现它们都没有实现Parcelable 接口。它是怎么支持序列化的呢?
那只能从Parcel本身分析了。
Parcel 除了支持

readInt()
writeInt()
...

等基本数据类型外,还支持

    public final IBinder readStrongBinder() {return nativeReadStrongBinder(mNativePtr);}public final void writeStrongBinder(IBinder val) {nativeWriteStrongBinder(mNativePtr, val);}

顾名思义,应该是专门读写IBinder的方法,也就是说虽然没有实现Parcelable,但是Parcel 内置支持了IBinder。
接着继续查看其native方法,看看有何奥妙之处。

static jobject android_os_Parcel_readStrongBinder(JNIEnv* env, jclass clazz, jlong nativePtr)
{Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);if (parcel != NULL) {return javaObjectForIBinder(env, parcel->readStrongBinder());}return NULL;
}static void android_os_Parcel_writeStrongBinder(JNIEnv* env, jclass clazz, jlong nativePtr, jobject object)
{Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);if (parcel != NULL) {const status_t err = parcel->writeStrongBinder(ibinderForJavaObject(env, object));if (err != NO_ERROR) {signalExceptionForError(env, clazz, err);}}
}

注:方法在/frameworks/core/jni/android_os_Parcel.cpp

先分析写入IBinder的情况:
parcel->writeStrongBinder(xx) 调用了Parcel.cpp里的writeStrongBinder(xx)进而调用flatten_binder(xx)函数

    status_t flatten_binder(const sp<ProcessState>& /*proc*/,const sp<IBinder>& binder, Parcel* out){flat_binder_object obj;...if (binder != NULL) {IBinder *local = binder->localBinder();if (!local) {//本地引用不存在BpBinder *proxy = binder->remoteBinder();if (proxy == NULL) {ALOGE("null proxy");}const int32_t handle = proxy ? proxy->handle() : 0;//type 标记为非本地Binderobj.hdr.type = BINDER_TYPE_HANDLE;obj.binder = 0; /* Don't pass uninitialized stack data to a remote process */obj.handle = handle;obj.cookie = 0;} else {//IBinder为本地的Binder引用,也就是和Server处在同一进程//type 标记为本地Binderobj.hdr.type = BINDER_TYPE_BINDER;obj.binder = reinterpret_cast<uintptr_t>(local->getWeakRefs());obj.cookie = reinterpret_cast<uintptr_t>(local);}} else {...}return finish_flatten_binder(binder, obj, out);}

可以看出,根据传入的IBinder是不是本地Binder然后打上type标记。
再来看看读取IBinder的情况
parcel->readStrongBinder()里最终调用了:

    status_t unflatten_binder(const sp<ProcessState>& proc,const Parcel& in, sp<IBinder>* out){const flat_binder_object* flat = in.readObject(false);if (flat) {//根据Type 标记判断switch (flat->hdr.type) {case BINDER_TYPE_BINDER://本地引用*out = reinterpret_cast<IBinder*>(flat->cookie);return finish_unflatten_binder(NULL, *flat, in);case BINDER_TYPE_HANDLE://非本地引用,获取代理对象*out = proc->getStrongProxyForHandle(flat->handle);return finish_unflatten_binder(static_cast<BpBinder*>(out->get()), *flat, in);}}return BAD_TYPE;}

由此可见,如果是Server端的IBinder与Client端不在同一进程,则会转换为Proxy对象,最终体现在Java层的就是BinderProxy类型。
注:函数在/frameworks/native/libs/binder/Parcel.cpp

综上所述,IBinder跨进程传递时:

  • 如果客户端、服务端同一进程,则服务端回传的IBinder为当前引用
  • 如果客户端、服务端处在不同进程,则服务端回传的IBinder为BinderProxy

3、两者区别与联系

获取系统服务
系统服务会往ServiceManager注册,ServiceManager运行在单独的进程里,客户端进程需要先向ServiceManager里请求IBinder,再使用IBinder获取关联接口进而使用系统服务。
获取自己定义的服务
服务端进程开启后,暴露出IBinder。客户端通过绑定服务端进程里的Service,将IBinder跨进程传递至客户端,客户端再使用IBinder获取关联接口进而使用自定义服务。此过程没有借助于ServiceManager。

不论是哪种方式,核心都需要获得IBinder,IBinder的获取需要IPC。

至此,Android IPC 系列文章已经分析完毕

本文基于Android 10.0。

您若喜欢,请点赞、关注,您的鼓励是我前进的动力

持续更新中,和我一起步步为营系统、深入学习Android


http://chatgpt.dhexx.cn/article/RobHidMn.shtml

相关文章

android ibinder 机制,Android IBinder机制简单介绍

原理简介 我们都知道android 是通过IBinder来实现IPC(Inter Process Communication)进程间通信的。。。 借用一下: 1. Client、Server和Service Manager实现在用户空间中,Binder驱动程序实现在内核空间中 2. Binder驱动程序和Service Manager在Android平台中已经实现,开发者…

sql之嵌套查询中的带exists谓词的子查询

数据库系统概论之嵌套查询中的带exists谓词的子查询 一、exists谓词概述&#xff1a;exists谓词代表存在量词。带有exists谓词的子查询不返回任何数据&#xff0c;只产生逻辑真值“true”或逻辑假值“false”。可以利用exists来判断属性与关系表之间的属于关系&#xff0c;关系…

sql in和exist

前言 最近写SQL的时候要求在排除A表中所存在的B表的某些同属性值的记录。 然后就想到了in和exist&#xff0c;但是其实一直都没有真正的去弄懂两者不同&#xff0c; 于是在网上查询了一些大佬的文章&#xff0c;然后去实践了一番&#xff0c;最后做一个总结 开始啰 1&#xff…

SQL之EXISTS的理解

将外查询表的每一行&#xff0c;代入内查询作为检验&#xff0c;如果内查询返回的结果取非空值&#xff0c;则EXISTS子句返回TRUE&#xff0c;这一行行可作为外查询的结果行&#xff0c;否则不能作为结果。 先上表 过程&#xff1a;外查询Persons表&#xff0c;提取出数据到E…

sql中exists的常用用法

exists中子查询结果集非空&#xff0c;则exists子查询返回true。如果exists子查询结果集为空&#xff0c;则exists子查询返回false。在平常的开发工作中&#xff0c;经常会用到exists&#xff0c;那么它应该如何使用呢&#xff1f; 1&#xff1a;查询兴趣爱好为跳舞的同学姓名及…

MySQL SQL语句EXISTS

MySQL中EXITS语句用于查明表中是否存在特定的行。普遍情况下EXITS与子查询一起使用&#xff0c;并返回与子查询返回的结果相等或匹配的行。如果行在表中存在&#xff0c;则返回true&#xff0c;否则返回false。在MySQL中使用EXISTS是低效的&#xff0c;因为EXISTS对查理表中的每…

SQL语句中EXISTS的用法

SQL萌新一个&#xff0c;在这里记录一下自学过程中遇到的问题。 exists&#xff1a;强调的是&#xff0c;是否有返回集&#xff0c;不需要知道具体返回的是什么 比如这两个表&#xff1a; 输入查询语句&#xff1a; select * from customer c where not exists( select * from…

sql中 exists的用法

现有&#xff1a;班级表&#xff08;A_CLASS&#xff09; 学生表( STUDENT) 注&#xff1a;学生表(STUDENT)的classId关联班级表&#xff08;A_CLASS&#xff09;的主键ID 代码&#xff1a; select * from STUDENT s WHERE exists (select 1 from A_ClASS c where s.CLASS_…

SQL语句中EXISTS的详细用法大全

SQL语句中EXISTS的详细用法大全 前言一、建表1.在MySQL数据库建表语句2.在ORACLE数据库建表语句 二、在SELECT语句中使用EXISTS1.在SQL中使用EXISTS2.在SQL中使用NOT EXISTS3.在SQL中使用多个NOT EXISTS4.在SQL中使用多个EXISTS5.在SQL中使用NOT EXISTS和EXISTS 三、在DELETE语…

DNS服务器解析问题

DNS服务器解析问题 前言 重点&#xff1a; 本文摘选链接&#xff1a;https://www.ancii.com/aazeaa674/ 正文 问题 域名状态异常会导致网站不能访问吗&#xff1f; 刚修改过域名解析&#xff0c;为什么不生效呢&#xff1f; 如何查看解析是否生效呢&#xff1f; 刚在注…

dns服务器怎么设置

dns服务器怎么设置 在修改DNS之前需要先知道你的大DNS服务器地址是什么&#xff0c;那么怎么来查询DNS服务器地址呢&#xff1f;直接按住键盘上的“winR”&#xff0c;调出运行框。在输入框中输入“cmd”&#xff0c;点击“确定”或者回车。在管理员界面中输入命令&#xff1a;…

手机显示DNS服务器异常,手机dns服务器异常怎么设置

手机dns服务器异常怎么设置 内容精选 换一换 华为云帮助中心,为用户提供产品简介、价格说明、购买指南、用户指南、API参考、最佳实践、常见问题、视频帮助等技术文档,帮助您快速上手使用华为云服务。 本章节介绍如何通过控制台重启服务器。重启服务器时,可以批量更换云手机…

小米路由器显示DNS服务器设置错误,小米路由器dns地址怎么设置

小米路由器与日常所有的路由器一样&#xff0c;在默认情况下都是路由器的地址作为无线设备获取的 DNS 地址。如果你想手动修改小米路由器的DNS地址&#xff0c;也是可以的&#xff0c;下面是学习啦小编给大家整理的一些有关小米路由器dns地址设置方法&#xff0c;希望对大家有帮…

如何设置正确的dns服务器地址,dns服务器地址如何设置

dns服务器地址如何设置 (Windows 2000、Windows XP操作系统)&#xff1a; 1、 开机后在桌面上选定图标"网上邻居"&#xff0c; 点击鼠标右键&#xff0c; 在弹出的菜单上选择"属性"项&#xff0c;打开"网络和拔号连接"窗口&#xff0c; 如图 2、…

北京联通dns服务器位置,联通DNS服务器地址怎么设置

联通DNS服务器地址怎么设置 如电脑Win7系统,以下方法设置DNS:您右键点击电脑桌“网络”图标 ,“属性” >选择“控制面板” >在“网络和共享中心”中可看到当前的网络状况,点击左边的“更改适配器设置” >右键单击“本地连接”择“属性” >选择“internet协议版本4(TCP/…

路由器显示DNS服务器设置错误,路由器dns设置错误怎么处理

一般都给家里配上了无线路由器&#xff0c;但是在用了无线路由器后&#xff0c;很容易导致电脑断网&#xff0c;大家一般用软件检测后上面会显示是DNS地址出现了异常&#xff0c;那么应该怎么解决这个问题&#xff0c;不让电脑频繁断网呢?下面是学习啦小编给大家整理的一些有关…

无线路由dns服务器地址,tplink无线路由器怎么设置DNS服务器地址

作为不同网络之间互相连接的枢纽,路由器系统构成了基于TCP/IP 的国际互联网络Internet 的主体脉络,也可以说,路由器构成了Internet的骨架。在设置路由器的时候,很多用户不知道该怎么手动设置路由器的DNS服务器地址,下面我们就来看看详细的设置教程,需要的朋友可以参考下 …

Ros系统配置DNS服务器,ros设置dns服务器

ros设置dns服务器 内容精选 换一换 域名的DNS服务器定义了域名用于解析的权威DNS服务器。通过华为云注册成功的域名默认使用华为云DNS进行解析,详细内容,请参见华为云DNS对用户提供域名服务的DNS是什么?。若您选择非华为云DNS进行域名解析,可以修改域名的DNS服务器。域名注…

无线网首选dns服务器怎么设置,dns服务器设置(192.168.1.1的首选dns)

我们知道影响上网速度的因素有很多&#xff0c;硬件、软件等都是高速上网的基础&#xff0c;每个人都希望自家的网速越来快&#xff0c;在相同的硬件、带宽固定的情况下&#xff0c;如何来择优选择上网参数中的DNS服务器呢&#xff1f; DNS服务是网络参数必不少的一项&#xff…

dhcp服务器修改dns设置方法,dhcp服务器的dns设置方法

dhcp服务器的dns设置方法 内容精选 换一换 文件系统与服务器的连接断开,需要重新挂载。没有配置自动挂载,重启服务器后会自动断开。参考自动挂载文件系统,在云服务器设置重启时进行自动挂载。如果上述方法均不能解决您的疑问,请提交工单寻求更多帮助。 “包年/包月”计费模…