大佬就是强!意外收获字节跳动内部资料—,该如何高效实用Kotlin

  1. 安全性:Binder 通过在内核层为客户端添加身份标志 UID|PID,来作为身份校验的标志,保障了通信的安全性。 传统 IPC 访问接入点是开放的,无法建立私有通道。比如,命名管道的名称,SystemV 的键值,Socket 的 ip 地址或文件名都是开放的,只要知道这些接入点的程序都可以和对端建立连接,不管怎样都无法阻止恶意程序通过猜测接收方地址获得连接。

除了上面的原因之外,Binder 还拥有许多其他的特性,比如:1).采用引用计数,当某个 Binder 不再被任何客户端引用的时候,会通知它的持有者可以将其释放,这适用于 Android 这种常常因为资源不足而回收资源的应用场景。2).它内部维护了一个线程池;3).可以像触发本地方法一样触发远程的方法。4).支持同步和异步 (oneway) 的触发模型;5).可以使用 AIDL 模板进行描述和开发。

3、Binder 模型,Binder 中的 4 个主要角色


在 Binder 模型中共有 4 个主要角色,它们分别是:Client、Server、Binder 驱动和 ServiceManager. Binder 的整体结构是基于 C|S 结构的,以我们启动 Activity 的过程为例,每个应用都会与 AMS 进行交互,当它们拿到了 AMS 的 Binder 之后就像是拿到了网络接口一样可以进行访问。如果我们将 Binder 和网络的访问过程进行类比,那么 Server 就是服务器,Client 是客户终端,ServiceManager 是域名服务器(DNS),驱动是路由器。其中 Server、Client 和 ServiceManager 运行于用户空间,驱动运行于内核空间

当我们的系统启动的时候,会在启动 SystemServer 进程的时候启动各个服务,也包括上面的 AMS. 它们会被放进一个哈希表中,并且哈希表的键是字符串。这样我们就可以通过服务的字符串名称来找到对应的服务。这些服务就是一个个的 Binder 实体,对于 AMS 而言,也就是 IActivityManager.Stub 实例。这些服务被启动的之后就像网络中的服务器一样一直等待用户的访问。

对于这里的 ServiceManager,它也是一种服务,但是它比较特殊,它会在所有其他的服务之前被注册,并且只被注册一次。它的作用是用来根据字符串的名称从哈希表中查找服务,以及在系统启动的时候向哈希表中注册服务。

所以,我们可以使用上面的这张图来描述整个 Binder 模型:首先,在系统会将应用程序所需的各种服务通过 Binder 驱动注册到系统中(ServiceManager 先被注册,之后其他服务再通过 ServiceManager 进行注册),然后当某个客户端需要使用某个服务的时候,也需要与 Binder 驱动进行交互,Binder 会通过服务的名称到 ServiceManager 中查找指定的服务,并将其返回给客户端程序进行使用。

4、Binder 的原理


上面我们梳理了 Binder 的模型,以及为什么系统设计一套通信机制的原因。那么你是否也好奇神乎其神的 Binder 究竟是怎么实现的呢?这里我们来梳理下 Binder 内部实现的原理。

首先,Binder 的实现过程是非常复杂的,在《Android 系统源码情景分析》一书中有 200 页的篇幅都在讲 Binder. 在这里我们不算详细地讲解它的具体的实现原理,我们只对其中部分内容做简单的分析,并且不希望涉及大量的代码。

4.1 inder 相关的系统源码的结构

然后,我们需要介绍下 Binder 相关的核心类在源码中的位置,

-framework

|–base

|–core

|–java–android–os

|–IInterface.java

|–IBinder.java

|–Parcel.java

|-- IServiceManager.java

|–ServiceManager.java

|–ServiceManagerNative.java

|–Binder.java

|–jni

|–android_os_Parcel.cpp

|–AndroidRuntime.cpp

|–android_util_Binder.cpp

|–native

|–libs–binder

|–IServiceManager.cpp

|–BpBinder.cpp

|–Binder.cpp // Binder 的具体实现

|–IPCThreadState.cpp

|–ProcessState.cpp

|–include–binder // 主要是一些头文件

|–IServiceManager.h

|–IInterface.h

|–cmds–servicemanager

|–service_manager.c // 用来注册服务的 ServiceManager

|–binder.c

-kernel-drivers-staging-android

|–binder.c

|–uapi-binder.h

4.2 Binder 实现过程中至关重要的几个函数

当我们查看 binder.c 的源码的时候,或者查看与 Binder 相关的操作的时候,经常看到几个操作 ioctl, mmap 和 open. 那么这几个操作符是什么含义呢?

首先,open 函数用来打开文件的操作符,在使用的时候需要引入头文件,#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>,其函数定义如下,

int open(const char * pathname, int flags);

int open(const char * pathname, int flags, mode_t mode);

这里的 pathname 表示文件路径;flag 表示打开方式;mode 表示打开的模式和权限等;若所有欲核查的权限都通过了检查则返回 0, 表示成功, 只要有一个权限被禁止则返回-1.

然后是 ioctl 指令,使用的时候需要引入 #include <sys/ioctl.h> 头文件,ioctl 是设备驱动程序中对设备的 I/O 通道进行管理的函数,用于向设备发控制和配置命令。其函数定义如下:

int ioctl(int fd, ind cmd, …);

其中 fd 是用户程序打开设备时使用 open 函数返回的文件标示符,cmd 是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,这个参数的有无和 cmd 的意义相关。

最后是 mmap 函数,它用来实现内存映射。使用的时候需要引入头文件 #include <sys/mman.h>. 与之对应的还有 munmap 函数。它们的函数定义如下,

void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);

int munmap(void* start,size_t length);

这里的参数的含义是:

  1. start:映射区的开始地址,设置为0时表示由系统决定映射区的起始地址;

  2. length:映射区的长度。长度单位是以字节为单位,不足一内存页按一内存页处理;

  3. prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过 o r运算合理地组合在一起;

  4. flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体;

  5. fd:有效的文件描述词。一般是由 open() 函数返回,其值也可以设置为-1,此时需要指定 flags 参数中的 MAP_ANON,表明进行的是匿名映射;

  6. off_toffset:被映射对象内容的起点。

成功执行时,mmap() 返回被映射区的指针,munmap() 返回0。失败时,mmap() 返回 MAP_FAILED[其值为(void *)-1],munmap() 返回 -1.

4.3 ServiceManger 启动

Binder 中的 ServiceManager 并非 Java 层的 ServiceManager,而是 Native 层的。启动 ServiceManager 由 init 进程通过解析 init.rc 文件而创建。启动的时候会找到上述源码目录中的 service_manager.c 文件中,并调用它的 main() 方法,

// platform/framework/native/cmds/servicemanager.c

int main(int argc, char** argv)

{

struct binder_state *bs;

char *driver;

if (argc > 1) {

driver = argv[1];

} else {

driver = “/dev/binder”;

}

// 1. 打开 binder 驱动

bs = binder_open(driver, 128*1024);

// …

// 2. 将当前的 ServiceManger 设置成上下文

if (binder_become_context_manager(bs)) {

return -1;

}

// …

// 3. 启动 binder 循环,进入不断监听状态

binder_loop(bs, svcmgr_handler);

return 0;

}

ServcieManager 启动的过程就是上面三个步骤,无需过多说明。下面我们给出这三个方法具体实现的。在下面的代码中你将看到我们之前介绍的三个函数的实际应用。相应有了前面的铺垫之后你理解起来不成问题 😃

// platform/framework/native/cmds/servicemanager.c

struct binder_state binder_open(const char driver, size_t mapsize)

{

struct binder_state *bs;

struct binder_version vers;

bs = malloc(sizeof(*bs));

if (!bs) {

errno = ENOMEM;

return NULL;

}

// 打开设备驱动

bs->fd = open(driver, O_RDWR | O_CLOEXEC);

if (bs->fd < 0) {

goto fail_open;

}

// 向驱动发送指令,获取binder版本信息

if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) ||

(vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) {

goto fail_open;

}

bs->mapsize = mapsize;

// 通过系统调用,mmap 内存映射,mmap 必须是 page 的整数倍

bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);

if (bs->mapped == MAP_FAILED) {

goto fail_map;

}

return bs;

fail_map:

close(bs->fd);

fail_open:

free(bs);

return NULL;

}

在上面的代码中,先使用 open() 函数打开设备驱动(就是一个打开文件的操作),然后使用 ioctl() 函数向上面的设备驱动发送指令以获取设备信息。最后,通过 mmap() 函数实现内存映射,并将上述的文件描述符传入。这里的 binder_state 是一个结构体,定义如下。其实就是用来描述 binder 的状态。从上面我们也能看到它的三个变量的赋值过程。

// platform/framework/native/cmds/servicemanager.c

struct binder_state

{

int fd;

void *mapped;

size_t mapsize;

};

当然,在上面的代码中,我们又见到了久违的 goto 指令。它们主要用来处理发生一些异常的情况。

打开了驱动之后,注册为上下文的方法更加简单,

// platform/framework/native/cmds/servicemanager.c

int binder_become_context_manager(struct binder_state *bs)

{

return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);

}

复制代码

就是一个 ioctl 函数,使用指令 BINDER_SET_CONTEXT_MGR 将当前的 ServiceManager 注册为上下文。

最后就是启动 Binder 循环了。它的逻辑也没有想象中得复杂,就是启动了 for 循环,

// platform/framework/native/cmds/servicemanager.c

void binder_loop(struct binder_state *bs, binder_handler func)

{

int res;

struct binder_write_read bwr;

uint32_t readbuf[32];

bwr.write_size = 0;

bwr.write_consumed = 0;

bwr.write_buffer = 0;

readbuf[0] = BC_ENTER_LOOPER;

// 将 BC_ENTER_LOOPER 命令发送给 binder 驱动,内部调用 ioctl 函数

binder_write(bs, readbuf, sizeof(uint32_t));

for (;😉 {

bwr.read_size = sizeof(readbuf);

bwr.read_consumed = 0;

bwr.read_buffer = (uintptr_t) readbuf;

// 使用 iotcl 函数读取

res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);

if (res < 0) {

break;

}

// 解析

res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);

if (res == 0) {

break;

}

if (res < 0) {

break;

}

}

}

从上面看出,函数将会在 binder_write() 中将命令发送给 Binder 驱动,以启动循环。其实内部也是调用 ioctl 函数实现的。然后程序会启动一个循环来不断读取、解析。这是服务器很典型的操作了。

当然,我们上面分析的是 ServiceManager 中向 Binder 写命令的过程,而驱动如何解析呢?当然是在驱动中实现了,详细的过程可以查看 Binder 驱动部分的源码。

4.4 Binder 的跨进程通信过程

下面我们以 AMS 作为例子来讲解下 Binder 跨进程通信的实现过程。首先,当我们调用 startActivity() 方法的时候,最终将会进入 ActivityManager 以获取 AMS,

// platform/framework/base/core/java/android/app/ActivityManager.java

final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);

final IActivityManager am = IActivityManager.Stub.asInterface(b);

return am;

这里会使用 ServiceManger 来按名称查找 AMS,查找到 Binder 对象之后将其转换成 AMS 就可以使用了。之前,我们也说过用来查找 AMS 的 SeerviceManager 本身也是一种服务。所以,它这里的方法也是通过 Binder 来实现的。那么,我们就从这里的 getService() 方法入手。

// platform/framework/base/core/java/android/os/ServiceManager.java

public static IBinder getService(String name) {

try {

IBinder service = sCache.get(name);

if (service != null) {

return service;

} else {

return Binder.allowBlocking(rawGetService(name));

}

} catch (RemoteException e) { /* … */ }

return null;

}

这里会先尝试从缓存当中取 Binder,取不到的话就从远程进行获取。这里使用 rawGetService() 方法来从远程获取 Binder,代码如下,

// platform/framework/base/core/java/android/os/ServiceManager.java

private static IBinder rawGetService(String name) throws RemoteException {

final IBinder binder = getIServiceManager().getService(name);

// …

return binder;

}

// platform/framework/base/core/java/android/os/ServiceManager.java

private static IServiceManager getIServiceManager() {

if (sServiceManager != null) {

return sServiceManager;

}

sServiceManager = ServiceManagerNative

.asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));

return sServiceManager;

}

rawGetService() 方法中会使用 ServiceManagerNativegetService() 方法从远程获取 Binder. 这里的 ServiceManagerNative 本质上只是一个代理类,它实际的逻辑是由 BinderInternal.getContextObject() 返回的 Binder 实现的。

也许你已经晕了,怎么那么多 Binder……我来说明下。当要查找 AMS 的时候实际上是一个跨进程的调用过程,也就是实际的查找的逻辑是在另一个进程实现,因此需要 Binder 来通信。而查找 AMS 的远程对象实际上就是我们上面所说的 ServiceManager (Native 层的而不是 Java 层的,Java 层的 ServiceManager 是一个代理类,是用来从远程获取服务的)。

因此,按照上面的描述,BinderInternal.getContextObject() 返回的就应该是远程的 Binder 对象。于是方法进入 Native 层,

// platform/framework/base/core/jni/android_util_Binder.cpp

static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz)

{

sp b = ProcessState::self()->getContextObject(NULL);

return javaObjectForIBinder(env, b);

}

这里的 ProcessState::self() 是否熟悉呢?你是否还记得在上一篇文章中,我们介绍 Android 系统启动过程的时候介绍过它。我们曾经使用它来开启 Binder 的线程池。这里的 self() 方法其实是用来获取一个单例对象的。我们可以直接由 getContextObject() 进入 getStrongProxyForHandle() 方法。从下面的方法中我们可以看出,这里调用了 BpBindercreate() 方法创建了一个 BpBinder 实例并返回,也就是我们的 ServiceManager.

// plaftorm/framework/native/libs/binder/ProcessState.cpp

sp ProcessState::getStrongProxyForHandle(int32_t handle)

{

sp result;

AutoMutex _l(mLock);

handle_entry* e = lookupHandleLocked(handle);

if (e != nullptr) {

IBinder* b = e->binder;

if (b == nullptr || !e->refs->attemptIncWeak(this)) {

// …

// 调用 BpBinder

b = BpBinder::create(handle);

e->binder = b;

if (b) e->refs = b->getWeakRefs();

result = b;

} else {

result.force_set(b);

e->refs->decWeak(this);

}

}

当我们拿到了 ServiceManager 的 Binder 之后就可以调用它的 getService() 方法来获取服务了,

// platform/framework/base/core/java/android/os/ServiceManagerNative.java

public IBinder getService(String name) throws RemoteException {

Parcel data = Parcel.obtain();

Parcel reply = Parcel.obtain();

data.writeInterfaceToken(IServiceManager.descriptor);

data.writeString(name);

mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);

IBinder binder = reply.readStrongBinder();

reply.recycle();

data.recycle();

return binder;

}

这里的 mRemote 就是之前返回的 BpBinder,这里调用它的 transact() 方法,并传入了一个方法标记 GET_SERVICE_TRANSACTION.

// platform/framework/native/libs/binder/BpBinder.cpp

status_t BpBinder::transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)

{

if (mAlive) {

status_t status = IPCThreadState::self()->transact(mHandle, code, data, reply, flags);

if (status == DEAD_OBJECT) mAlive = 0;

return status;

}

return DEAD_OBJECT;

}

显然这里会调用 IPCThreadState 的 self() 方法先获取一个单例的对象,然后调用它的 transact() 方法继续方法的执行。

// platform/framework/native/libs/binder/IPCThreadState.cpp

status_t IPCThreadState::transact(int32_t handle, uint32_t code,

const Parcel& data, Parcel* reply, uint32_t flags)

{

status_t err;

// …

err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, nullptr);

// …

if ((flags & TF_ONE_WAY) == 0) { // OneWay 类型的调用,同步的

// …

if (reply) {

// 等待相应

err = waitForResponse(reply);

} else {

Parcel fakeReply;

err = waitForResponse(&fakeReply);

}

IF_LOG_TRANSACTIONS() {

TextOutput::Bundle _b(alog);

if (reply) alog << indent << *reply << dedent << endl;

else alog << “(none requested)” << endl;

}

} else { // 异步的

err = waitForResponse(nullptr, nullptr);

}

return err;

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

写在最后

在技术领域内,没有任何一门课程可以让你学完后一劳永逸,再好的课程也只能是“师傅领进门,修行靠个人”。“学无止境”这句话,在任何技术领域,都不只是良好的习惯,更是程序员和工程师们不被时代淘汰、获得更好机会和发展的必要前提。

如果你觉得自己学习效率低,缺乏正确的指导,可以一起学习交流!

加入我们吧!群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。

58)]
[外链图片转存中…(img-wLYPScdH-1711859383458)]
[外链图片转存中…(img-GXCDRNbV-1711859383458)]
[外链图片转存中…(img-LFEWDlCX-1711859383459)]
[外链图片转存中…(img-AExizS9n-1711859383459)]
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-AlVEV6ly-1711859383460)]

写在最后

在技术领域内,没有任何一门课程可以让你学完后一劳永逸,再好的课程也只能是“师傅领进门,修行靠个人”。“学无止境”这句话,在任何技术领域,都不只是良好的习惯,更是程序员和工程师们不被时代淘汰、获得更好机会和发展的必要前提。

如果你觉得自己学习效率低,缺乏正确的指导,可以一起学习交流!

加入我们吧!群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

  • 23
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值