这不是一篇详细介绍 Binder 实现原理的文章,因为介绍 Binder 的相关文章已经非常多了,比如 :
Android深入浅出之Binder机制
Android Bander设计与实现 - 设计篇
Android Binder机制系列文章
但是作为一个普通的 Android 程序员,尤其是不熟悉 C语言,Linux内核及驱动,JNI 的程序员,面对大量的底层源码分析,还是多次中途放弃。
这次终于一鼓作气看完了 Android Binder机制系列文章,总算搞清了 Binder 的来龙去脉,所以这里整理记录下来。如果你也想彻底搞清 Binder 的设计与实现,那么可以Android Binder机制系列文章为主进行学习,遇到困惑的地方可以参考本文,说不定会有帮助。
2 源码及工具准备
2.1 源码下载
我们的目的只是为了查看 Binder 源码,所以不需要安装 repo,也不用下载整个的 AOSP 项目,只要下载一些我们会用到的源码就好了。
所有的Android的源码都可以在这里下载 https://android.googlesource.com/
下面列出 Binder学习中需要用到的几个包:
- platform/frameworks/native Binder体系的主要源码都在这里了,除了内核中的Binder驱动部分
- kernel/common 内核中的 Binder驱动部分的源码就在这里面了
- platform/frameworks/av 包含MediaPlayerService相关源码
下载方法:点击上面链接,打开后选择任意一个分支,点击tgz链接,就可以下载源码压缩包了。有些源码已经跟很多博客中分析的不一样了,不过改动都不大。一般下载master分支就可以了,不过 kernel/common 的master分支是空的,可选择 android-4.4 分支下载。
下载源码压缩包2.2 源码阅读工具
在阅读 Binder 源码时,经常需要查看各种方法的调用,类的定义等等,需要频繁的查找和跳转,所以一个顺手的工具可以大大提高阅读效率。
推荐使用 Sublime Text 3,mac/windows通吃。装好之后还需要安装 Ctags 插件,用于建立其代码索引,可以方便的跳转查看方法的实现。
常用操作:
- Ctrl + P,输入文件名称,回车打开文件
- Ctrl + P,输入 @+函数名,查找某个函数
3 Binder 概述
3.1 什么是 Binder
在不同的上下文环境中提到 binder,代表的意思也不同:
- 从 Android 系统层面来说,binder 指的一种实现进程间通讯的机制
- 在 Linux 内核中, binder 指的是一个虚拟设备的驱动,可以想象成两个进程间通讯的管道,用户态的进程 A 只需要通过系统调用向 binder 驱动发送命令,binder 驱动就会搞定一切,把命令传送给指定的进程 B,并把处理结果返回给 A。 binder 封装了底层对线程、内存复杂的管理操作,暴露给用户进程的只是几个命令,极大的简化了进程间的通讯。
3.2 Binder 整体架构
Binder 的设计整体上是典型的 C/S 架构,Binder 架构中的4个角色完全可以拿典型的 TCP/IP 网络结构来类比:
- Binder Client:发起服务请求的客户端
- Binder Server:响应服务请求的服务端
- ServiceManager:提供查找服务,添加服务的DNS
- Binder 驱动:定义通讯协议,完成数据的封装和传输的TCP/IP通讯协议
3.3 Binder 典型交互
- Server 将自己注册到 ServiceManager 中
- Client 通过服务名称向 ServiceManager 查找对应服务
- ServiceManager 根据服务名称查找到 Server ,在内核中建立对应的 Binder 实体,并将该实体对应的 Binder 引用返回给Client
- Client 通过 Binder 引用向 Server 发起请求
- Binder 驱动根据 Binder 引用找到对应的 Binder 实体,将客户端的请求发送给 Binder 实体处理
- Binder 驱动将处理后的结果返回给 Client
接下来将对 Binder 架构中的关键环节的流程进行一一个梳理
4 ServiceManager
ServiceManager 作为注册和查找服务的中心,其实本质上也是一个特殊的 server,只是它必须要先于其他的 server 启动,这样其他的 server 在初始化的时候就可以把自己注册到 ServiceManager 中。
通过在 init.rc 添加启动脚本,系统在启动初始化时就会调用 service_manager 的 main 入口函数完成 ServiceManager 的启动和初始化:
platform/native/cmds/servicemanager/service_manager.c
int main()
{
struct binder_state *bs;
bs = binder_open(128*1024); //1.打开 binder 驱动,获取文件描述符
// 略
if (binder_become_context_manager(bs)) { // 2.将 ServiceManager 注册成为“DNS”
ALOGE("cannot become context manager (%s)\n", strerror(errno));
return -1;
}
// 略
binder_loop(bs, svcmgr_handler); // 3.进入循环等待接收客户端发来的请求
return 0;
}
略去无用代码,关键的部分只有三步,注释已经写明,下面看看每一步具体做了什么
4.1 binder_open()
platform/native/cmds/servicemanager/binder.c
struct binder_state *binder_open(size_t mapsize)
{
struct binder_state *bs;
struct binder_version vers;
bs = malloc(sizeof(*bs));
...
bs->fd = open("/dev/binder", O_RDWR | O_CLOEXEC); 1. 打开 binder 设备驱动
...
}
...
bs->mapsize = mapsize;
bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0); // 2.建立内存映射
}
return bs;
}
open 函数是一个系统函数,用于打开第一个参数指明的设备,对应的函数在 binder 驱动* /kernel/common/drivers/android/binder.c*(注意跟 servicemanager 目录下的binder.c区分,后者只是封装了一些 servicemanager 跟 binder 驱动交互的操作)的源码中定义
kernel/common/drivers/android/binder.c
static const struct file_operations binder_fops = {
.owner = THIS_MODULE,
.poll = binder_poll,
.unlocked_ioctl = binder_ioctl,
.compat_ioctl = binder_ioctl,
.mmap = binder_mmap,
.open = binder_open,
.flush = binder_flush,
.release = binder_release,
};
这个结构体定义了open 函数对应的函数指针,所以实际的函数是 binder_open,这里不展开了主要是理清流程。
接下来 mmap 操作完成用户内存到内核内存的映射。
4.2 binder_become_context_manager()
该函数内部通过 ioctl 函数向 binder 驱动发送一个 BINDER_SET_CONTEXT_MGR
的命令,使当前进程成为系统的 ServiceManager。该命令的处理在 * /kernel/common/drivers/android/binder.c* 的 binder_ioctl
函数中,binder 驱动会为建立 ServiceManager 对应的 binder 实体。(注意:这里的实现已经跟多数文章中讲的不同,不再是用一个全局变量保存该 binder 实体,而是保存在 binder_proc->binder_context->binder_context_mgr_node
这个变量中)
4.3 binder_loop()
platform/native/cmds/servicemanager/binder.c
void binder_loop(struct binder_state *bs, binder_handler func)
{
readbuf[0] = BC_ENTER_LOOPER;
binder_write(bs, readbuf, sizeof(uint32_t));
for (;;) {
res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
}
}
主要的操作就只有上面几行代码,其余的都已省略。
binder_write
函数会调用 ioctl 发送 BC_ENTER_LOOPER
命令告诉 binder 驱动当前线程已进入消息循环状态。接下来的死循环,从 binder 驱动读取消息到 &bwr,如果没有消息就会阻塞直到被唤醒,读取到消息后再调用binder_parse
解析 &bwr
中的消息内容。
binder_parse
会调用 func 函数处理请求。func 参数传入的值是一个指向svcmgr_handler
函数的函数指针,所以具体的如添加查找函数的请求处理主要都在 svcmgr_handler
函数中了。
处理完请求回到binder_parse
后调用binder_send_reply
向驱动返回处理的结果(根据请求类型如果不需要回复就没有这一步)
5 获取 ServiceManager
客户端想要获取系统服务,首先就要向 ServiceManager 请求服务端的代理对象,而各类系统服务 也要在系统启动时先注册到 ServiceManager。所以如何获取 ServiceManager 就成为了第一个要解决的问题。
IServiceManager::defaultServiceManager()
就是用来获得一个 ServiceManager 服务代理的函数。
platform/native/libs/binder/IServiceManager.cpp
sp<IServiceManager> defaultServiceManager()
{
if (gDefaultServiceManager != NULL) return gDefaultServiceManager;
{
AutoMutex _l(gDefaultServiceManagerLock);
while (gDefaultServiceManager == NULL) {
gDefaultServiceManager = interface_cast<IServiceManager>(
ProcessState::self()->getContextObject(NULL));
if (gDefaultServiceManager == NULL)
sleep(1);
}
}
return gDefaultServiceManager;
}
一个典型的单例模式,关键就是下面这一句了,信息量很大,一点一点拆开来看
gDefaultServiceManager = interface_cast<IServiceManager>(ProcessState::self()->getContextObject(NULL));```
###5.1 ProcessState::self()
这里又是一个单例模式获取一个 `ProcessState` 对象,在构造函数中,打开了 `/dev/binder` 设备,保存了文件句柄。然后调用mmap()映射内存到当前进程的虚拟地址空间。
###5.2 getContextObject()
实际上调用了 `getStrongProxyForHandle(handle)`,这个函数返回一个 BpBinder(handle),这里 handle 为0,也即 ServiceManager 的句柄值。
###5.3 interface_cast<IServiceManager>
这部分是最暗藏玄机也比较不好理解的部分,前面我们已经获得了一个 BpBinder 对象,而这里最终要返回一个 IServiceManager 对象,然而这两者并没有任何的继承关系,所以肯定不是简单的强转。看这个模板函数的定义。
frameworks/native/include/binder/IInterface.h
template<typename INTERFACE>
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{
return INTERFACE::asInterface(obj);
}
把 `INTERFACE` 替换成 `IServiceManager`,然而 `IServiceManager` 中并没有 `asInterface` 这个方法。注意到 *IServiceManager.h* 中有一句宏调用:
DECLARE_META_INTERFACE(ServiceManager)
而 *IServiceManager.cpp* 中也有一句宏调用:
IMPLEMENT_META_INTERFACE(ServiceManager, "android.os.IServiceManager");
这里的声明和实现里有包括了 asInterface 方法,这两个宏定义在 IInterface.h 中:
frameworks/native/include/binder/IInterface.h
define DECLARE_META_INTERFACE(INTERFACE) \
static const android::String16 descriptor; \
static android::sp<I##INTERFACE> asInterface( \
const android::sp<android::IBinder>& obj); \
virtual const android::String16& getInterfaceDescriptor() const; \
I##INTERFACE(); \
virtual ~I##INTERFACE(); \
define IMPLEMENT_META_INTERFACE(INTERFACE, NAME) \
const android::String16 I##INTERFACE::descriptor(NAME); \
const android::String16& \
I##INTERFACE::getInterfaceDescriptor() const { \
return I##INTERFACE::descriptor; \
} \
android::sp<I##INTERFACE> I##INTERFACE::asInterface( \
const android::sp<android::IBinder>& obj) \
{ \
android::sp<I##INTERFACE> intr; \
if (obj != NULL) { \
intr = static_cast<I##INTERFACE*>( \
obj->queryLocalInterface( \
I##INTERFACE::descriptor).get()); \
if (intr == NULL) { \
intr = new Bp##INTERFACE(obj); \
} \
} \
return intr; \
} \
I##INTERFACE::I##INTERFACE() { } \
I##INTERFACE::~I##INTERFACE() { }
把两个宏展开之后就变成了:
define DECLARE_META_INTERFACE(IServiceManager) \
static const android::String16 descriptor; \
static android::sp<IServiceManager> asInterface( \
const android::sp<android::IBinder>& obj); \
virtual const android::String16& getInterfaceDescriptor() const; \
IServiceManager(); \
virtual ~IServiceManager(); \
define IMPLEMENT_META_INTERFACE(IServiceManager, "android.os.IServiceManager") \
const android::String16 IServiceManager::descriptor("android.os.IServiceManager"); \
const android::String16& \
IServiceManager::getInterfaceDescriptor() const { \
return IServiceManager::descriptor; \
} \
android::sp<IServiceManager> IServiceManager::asInterface( \
const android::sp<android::IBinder>& obj) \
{ \
android::sp<IServiceManager> intr; \
if (obj != NULL) { \
intr = static_cast<IServiceManager*>( \
obj->queryLocalInterface( \
IServiceManager::descriptor).get()); \
if (intr == NULL) { \
intr = new BpServiceManager(obj); \
} \
} \
return intr; \
} \
IServiceManager::IServiceManager() { } \
IServiceManager::~IServiceManager() { }
我们重点关注 `asInterface` 方法,这里传入的 `obj` 参数就是前面得到的 `BpBinder(0)` 对象,`queryLocalInterface` 的默认实现在 *IBinder.cpp* 中,它返回 null,所以这里 `intr == null`,最终就构造了 `BpServiceManager(obj)` 返回,而`BpServiceManager` 正是 `IServiceManager` 的一个实现类,它也是 ServiceManager 在客户端的代理对象。
###5.4 总结
借用 [这篇文章](http://wangkuiwu.github.io/2014/09/01/Binder-Introduce/#anchor2_1_2) 的一张图来梳理一下 Binder 架构中各个类的关系:
![](http:https://img.haomeiwen.com/i1423045/c8c99b7f51653151.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
1. 服务接口定义服务提供的业务逻辑,比如 IServiceManager,定义了 getService,addService 等。
2. 本地服务,也即服务端,是真正实现业务逻辑的对象。
3. 远程服务,也即客户端,或者代理端,比如 BpServiceManager,里面保存了一个 BpBinder(实际在构造函数时保存在父类 `BpRefBase.mRemote` 中,`通过 remote()` 方法获得),作为服务端的代理,主要职责是封装请求,并向 Binder 驱动发起请求,Binder 驱动会负责把请求发送到对应的服务端。这部分后面还会再做讲解
4. Binder 驱动收到代理发起的请求,最终会调用到 BBinder.onTransact 方法,所以本地服务只要实现 onTransact 方法就能处理客户端发来的请求了,这部分后面还会再做讲解。
5. BpInterface<INTERFACE> 和 BnInterface<INTERFACE> 都定义在 Interface.h 中,这里的 INTERFACE 即是服务接口。
# 6 注册服务到ServiceManager
获取到 IServiceManager,就可以调用其 addService 方法,将服务添加到 ServiceManager 了,注意这时候服务扮演的是客户端的角色,ServiceManager 扮演的是服务端的角色。
### 6.1 addService
platform/native/libs/binder/IServiceManager.BpServiceManager
virtual status_t addService(const String16& name, const sp<IBinder>& service,
bool allowIsolated)
{
Parcel data, reply;
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
data.writeString16(name);
data.writeStrongBinder(service);
data.writeInt32(allowIsolated ? 1 : 0);
status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
return err == NO_ERROR ? reply.readExceptionCode() : err;
}
可以看到这里把请求的参数打包到 Parcel 中(关于 Parcel 打包数据的原理这里就不具体讲了,可以参考[这里](http://wangkuiwu.github.io/2014/09/05/BinderCommunication-AddService01)),注意 `data.writeStrongBinder(service);` 这句,这里的 service 是一个 BnXXX,也即一个本地服务对象,在写入的时候会被转换为一个 flat_binder_object 结构体写入,关于这个结构体的说明,参见 [这里](http://wangkuiwu.github.io/2014/09/02/Binder-Datastruct/#anchor1_7)。
接下来`remote()->transact()`,这里的 remote() 前面已经说了,返回之前保存进去的 BpBinder,BpBinder 的 transact 方法最终调用到 `IPCThreadState::self()->transact()`,到这里,实际跟 binder 驱动打交道的类 `IPCThreadState 终于出现了。
### 6.2 IPCThreadState.transact()
`IPCThreadState::self()` 获取调用进程的 IPCThreadState 的单例,如果首次调用,会进行打开binder驱动,内存映射等操作,和前面ServiceManager的启动类似。
精简后保留主要逻辑的 transact() 代码:
status_t IPCThreadState::transact(int32_t handle,
uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags)
{
status_t err = data.errorCheck();
if (err == NO_ERROR) {
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
}
if ((flags & TF_ONE_WAY) == 0) {
if (reply) {
err = waitForResponse(reply);
}
}
return err;
}
可以看到主要涉及两个函数 `writeTransactionData`,`waitForResponse`
### 6.3 writeTransactionData()
这里必须先介绍 IPCThreadState 两个 Parcel 类型的成员变量,mIn 和 mOut,分别用来保存从 binder 驱动读取的数据和即将向 binder 驱动写入的数据。而 `writeTransactionData()` 这个函数的作用就是把数据二次封装到 mOut 中,这里又必须借一张图来说明了:
![](http:https://img.haomeiwen.com/i1423045/c388a9297203228d.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
有效数据对应传入的 data 参数,也就是在6.1节封装的那个 Parcel,而经过`writeTransactionData()` 的再次封装之后, mOut = binder_transaction_data 结构 + BC_TRANSACTION(一个整数值)。后面会看到在发给 binder 驱动之前数据还会经过一层封装到 `binder_write_read` 结构中。
###6.4 waitForResponse()
精简后保留主要逻辑的代码:
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
uint32_t cmd;
int32_t err;
while (1) {
if ((err=talkWithDriver()) < NO_ERROR) break;
if (mIn.dataAvail() == 0) continue;
cmd = (uint32_t)mIn.readInt32();
switch (cmd) {
case BR_TRANSACTION_COMPLETE:
if (!reply && !acquireResult) goto finish;
break;
case BR_REPLY:
{
binder_transaction_data tr;
err = mIn.read(&tr, sizeof(tr));
if (err != NO_ERROR) goto finish;
if (reply) {
if ((tr.flags & TF_STATUS_CODE) == 0) {
reply->ipcSetDataReference(
reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
tr.data_size,
reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
tr.offsets_size/sizeof(binder_size_t),
freeBuffer, this);
} else {
err = *reinterpret_cast<const status_t*>(tr.data.ptr.buffer);
freeBuffer(NULL,
reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
tr.data_size,
reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
tr.offsets_size/sizeof(binder_size_t), this);
}
} else {
freeBuffer(NULL,
reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
tr.data_size,
reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
tr.offsets_size/sizeof(binder_size_t), this);
continue;
}
}
goto finish;
default:
err = executeCommand(cmd);
if (err != NO_ERROR) goto finish;
break;
}
}
finish:
if (err != NO_ERROR) {
if (acquireResult) *acquireResult = err;
if (reply) reply->setError(err);
mLastError = err;
}
return err;
}
这里的逻辑相对复杂,不过整体上看,主要是一个死循环,当满足某些特定条件时才会跳出,下面具体分析死循环里的几个关键步骤。
####6.4.1 第一次调用talkWithDriver()
精简后的代码如下:
status_t IPCThreadState::talkWithDriver(bool doReceive)
{
binder_write_read bwr;
// Is the read buffer empty?
const bool needRead = mIn.dataPosition() >= mIn.dataSize();
// We don't want to write anything if we are still reading
// from data left in the input buffer and the caller
// has requested to read the next data.
const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;
bwr.write_size = outAvail;
bwr.write_buffer = (uintptr_t)mOut.data(); /*****第1步*****/
// This is what we'll read.
if (doReceive && needRead) {
bwr.read_size = mIn.dataCapacity();
bwr.read_buffer = (uintptr_t)mIn.data();
} else {
bwr.read_size = 0;
bwr.read_buffer = 0;
}
// Return immediately if there is nothing to do.
if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;
bwr.write_consumed = 0;
bwr.read_consumed = 0;
status_t err;
do {
#if defined(__ANDROID__) /*****第2步*****/
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
err = NO_ERROR;
else
err = -errno;
#else
err = INVALID_OPERATION;
#endif
} while (err == -EINTR);
if (err >= NO_ERROR) { /*****第3步*****/
if (bwr.write_consumed > 0) {
if (bwr.write_consumed < mOut.dataSize())
mOut.remove(0, bwr.write_consumed);
else
mOut.setDataSize(0);
}
if (bwr.read_consumed > 0) {
mIn.setDataSize(bwr.read_consumed);
mIn.setDataPosition(0);
}
return NO_ERROR;
}
return err;
}
代码看起来很长,但是其实主要就分为一下几步,已在代码中标出:
1. 把数据包装到一个 ` binder_write_read` 结构的变量 `bwr` 中
2. 调用 `ioctl` 向binder驱动发送指令,binder驱动会从 `bwr` 的 `write_buffer` 读取数据,经过处理之后,如果有返回,就写入到 `read_buffer` 中
3. 根据 `bwr` 中的数据更新 `mOut` 和 `mIn` 的数据
**注:**这里的第2步这里只是一笔带过,其实包含了和binder驱动的交互的关键步骤,为了避免打断分析的流程,这里就不深入了,具体可以参考
[Android Binder机制(五) addService详解01之 请求的发送](http://wangkuiwu.github.io/2014/09/05/BinderCommunication-AddService01/),
[Android Binder机制(六) addService详解02之 请求的处理](http://wangkuiwu.github.io/2014/09/05/BinderCommunication-AddService02/),
[Android Binder机制(七) addService详解03之 请求的反馈](http://wangkuiwu.github.com/2014/09/05/BinderCommunication-AddService03/)
这三篇文章。这里我们只需要知道,binder驱动把请求发送给 ServiceManager后,就返回了 BR_NOOP 和 BR_TRANSACTION_COMPLETE 两条指令,注意这里返回时只代表驱动已经把请求发送给 ServiceManager处理,而处理的结果此时还没返回。
####6.4.2 读取 BR_NOOP 和 BR_TRANSACTION_COMPLETE
上一节第一次调用talkWithDriver()后,收到了binder驱动返回的 BR_NOOP 和 BR_TRANSACTION_COMPLETE 两条指令,回到`waitForResponse()`的死循环,接着循环两次从`mIn`中读取两条指令,这两条指令都是什么都不做。
####6.4.3 进入中断等待
读取完两条返回的指令后,再次进入 `waitForResponse()` 的死循环,此时:
bwr.write_size = 0;
bwr.write_buffer = (long unsigned int)mOut.data();
bwr.write_consumed = 0;
bwr.read_size = mIn.dataCapacity(); // 256字节
bwr.read_buffer = (long unsigned int)mIn.data();
bwr.read_consumed = 0;
再次进入 talkWithDriver()->ioctl(),此时因为bwr.write_size = 0 代表没有数据要写入,然后bwr.read_consumed = 0 代表没有数据要读取,此时就会进入中断等待状态,具体查看 binder驱动源码中 binder_thread_read() 方法的实现。
###6.5 总结
概括起来,向binder发起一个请求的过程主要包括:
1. 数据的层层封装,Parcel --> binder_transaction_data --> binder_write_read
2. 通过 IPCThreadState.transact() --> waitForResponse() --> talkWithDriver() --> ioctl() 向binder驱动发起请求
3. binder驱动会按照先写后读的顺序处理请求,唤醒服务端的进程处理请求,同时向客户端返回 BR_TRANSACTION_COMPLETE 代表已经将客户端的请求发送给服务端
4. 如果客户端没有别的请求,就陷入中断等待
5. 服务端处理完请求,又将跟 binder 驱动通讯,binder驱动将唤醒客户端,并把处理结果返回给客户端
#7 The End
关于 binder 机制的整体流程的总结总算写完了,其中还涉及很多细节没有涵盖,不过作为一个备忘,起码以后再看起来,可以很快的理清思路,再需要找到实现细节也不难了。最后还是要感谢 [Android Binder机制系列文章](http://wangkuiwu.github.io/2014/09/01/Binder-Introduce/) 的作者,写的真是不能再详细了!
网友评论