上一篇我们讲到:
system_server
↓
SystemServer
↓
AMS / PMS / WMS / PowerManagerService / ...
大量 Android Framework 核心服务,其实都运行在:
system_server
进程里。
而我们自己的 App 却运行在:
App Process
于是一个问题自然出现了:
App Process
PowerManager
│
│ ???
↓
system_server
PowerManagerService
它们明明是:
两个独立的 Linux Process。
为什么 App 还能像调用普通接口一样:
powerManager.xxx()
最终让:
PowerManagerService
真正执行逻辑?
先不要急着进入 Binder。
我们先从 Android App 开发中最熟悉的:
Interface
Contract
开始。
这一篇只用一个例子:
userService.getUser(1)
然后一步一步升级:
直接调用
↓
Interface
↓
Contract
↓
跨进程
↓
Proxy
↓
Stub
↓
Binder
↓
AIDL
看看:
一个最普通的接口调用,是怎么一步一步演进成跨进程调用的。
一、第一版:直接调用实现类
先写最简单的代码。
data class User(
val id: Int,
val name: String
)
然后:
class UserServiceImpl {
fun getUser(id: Int): User {
return User(
id = id,
name = "Tom"
)
}
}
调用:
val userService =
UserServiceImpl()
val user =
userService.getUser(1)
现在:
userService
实际就是:
UserServiceImpl 对象
所以:
userService.getUser(1)
真实调用链非常简单:
Caller
↓
UserServiceImpl
↓
getUser(1)
↓
返回 User
没有接口。
没有代理。
没有 IPC。
就是:
直接拿实现对象,然后直接调用方法。
二、直接依赖实现类有什么问题?
现在调用方明确知道:
UserServiceImpl
也就是说:
Caller
↓
直接依赖具体实现
假设以后:
UserServiceImpl
要换成:
RemoteUserService
MockUserService
CacheUserService
调用方可能也要跟着调整。
所以通常会做第一次抽象:
Interface
三、第二版:增加 Interface
定义:
interface UserService {
fun getUser(id: Int): User
}
实现类:
class UserServiceImpl :
UserService {
override fun getUser(id: Int): User {
return User(
id = id,
name = "Tom"
)
}
}
调用方:
val userService: UserService =
UserServiceImpl()
val user =
userService.getUser(1)
注意:
userService.getUser(1)
这一行基本没变。
但:
userService
的类型已经变成:
UserService
四、现在这一行代码到底调用了谁?
这是理解后面所有内容的关键。
编译时:
userService
↓
UserService Interface
但运行时:
userService
↓
实际指向 UserServiceImpl 对象
所以:
userService.getUser(1)
真正执行:
Caller
↓
UserService
↓
运行时找到 UserServiceImpl
↓
UserServiceImpl.getUser(1)
最终干活的:
还是
UserServiceImpl。
所以 Interface 没有改变:
真正由谁执行业务
它改变的是:
调用方依赖谁
以前:
Caller
↓
UserServiceImpl
现在:
Caller
↓
UserService
↓
UserServiceImpl
也就是:
调用方只依赖能力定义,不依赖具体实现。
五、这就是最基础的面向接口编程
可以直接记:
Interface
=
定义“能做什么”
Impl
=
决定“具体怎么做”
调用方只关心:
userService.getUser(1)
至于后面到底是:
数据库
网络
Mock
缓存
都不重要。
这个思想接下来不会变。
六、第三版:Interface 变成模块 Contract
现在项目大了。
我们把 User 功能拆成:
app
user-contract
user-impl
结构可以理解:
user-contract
↑ ↑
│ │
app user-impl
app 不直接依赖:
user-impl
只依赖:
user-contract
七、Contract 模块只定义能力
前面我们只有一个用户模块:
user
现在把它拆成两个模块:
user-contract
user-impl
它们的职责完全不同:
user-contract
↓
定义“用户模块能提供什么能力”
user-impl
↓
负责“这些能力具体怎么实现”
目录大概可以这样:
user-contract
└── src/main/java/com/example/user/contract
├── User.kt
└── UserContract.kt
user-impl
└── src/main/java/com/example/user/impl
└── UserContractImpl.kt
先看:
user-contract
它只保存对外需要暴露的内容。
比如先定义模块之间需要传递的数据:
package com.example.user.contract
data class User(
val id: Int,
val name: String
)
然后定义用户模块能够提供的能力:
package com.example.user.contract
interface UserContract {
fun getUser(id: Int): User
}
这里的意思非常简单:
外部模块
│
│ userId
▼
UserContract
│
│ User
▼
外部模块
UserContract 只是在声明:
给我一个
userId,我可以给你一个User。
但是:
User 到底从哪里来?
Contract 完全不知道。
它可能来自:
网络
数据库
缓存
文件
本地内存
这些都属于实现细节。
所以:
user-contract
里面不会出现:
Retrofit
Room
Repository
ViewModel
具体业务逻辑
它只负责定义协议。
真正的实现放到:
user-impl
例如:
package com.example.user.impl
import com.example.user.contract.User
import com.example.user.contract.UserContract
class UserContractImpl : UserContract {
override fun getUser(id: Int): User {
// 真正项目中这里可能调用:
// Repository
// Retrofit
// Room
// Cache
// 等等
return User(
id = id,
name = "Tom"
)
}
}
这时候:
UserContract
负责:
定义能力
而:
UserContractImpl
负责:
实现能力
关系就是:
UserContract
▲
│ implements
│
UserContractImpl
注意:
user-impl 需要依赖 user-contract。
所以:
// user-impl/build.gradle.kts
dependencies {
implementation(project(":user-contract"))
}
因为:
UserContractImpl
需要实现:
UserContract
并且使用:
User
因此整个依赖关系是:
user-impl
│
▼
user-contract
而调用方,比如:
app
真正应该依赖的是:
user-contract
例如:
// app/build.gradle.kts
dependencies {
implementation(project(":user-contract"))
}
这样 app 里面的业务代码只需要认识:
UserContract
而不需要认识:
UserContractImpl
例如我们在 MainActivity 中使用它:
package com.example.app
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.example.user.contract.UserContract
class MainActivity : AppCompatActivity() {
private lateinit var userContract: UserContract
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val textView = TextView(this)
setContentView(textView)
val user = userContract.getUser(1)
textView.text = """
id = ${user.id}
name = ${user.name}
""".trimIndent()
}
}
注意这里:
private lateinit var userContract: UserContract
app 持有的是:
UserContract
而不是:
UserContractImpl
真正调用时也是:
val user = userContract.getUser(1)
调用方只关心:
我能调用 getUser()
而完全不关心:
getUser() 内部到底怎么实现
所以对于 app 来说,它看到的只有:
app
│
▼
UserContract
真正的实现则在另一边:
UserContract
▲
│
UserContractImpl
合在一起就是:
UserContract
▲ ▲
│ │
│ │
app user-impl
或者从调用链来看:
app
│
│ getUser()
▼
UserContract
▲
│ implements
│
UserContractImpl
这就是我们真正想要的结构:
调用方依赖能力定义,而不是依赖具体实现。
不过你应该马上能发现一个问题。
刚才代码里我们写了:
private lateinit var userContract: UserContract
但是:
userContract 到底是谁赋值的?
如果我们直接这样写:
val userContract: UserContract = UserContractImpl()
那么 app 又必须:
import com.example.user.impl.UserContractImpl
并且重新依赖:
implementation(project(":user-impl"))
这样就又回去了:
app
↓
user-impl
↓
user-contract
那前面拆 Contract 的意义就被削弱了。
所以现在真正的问题变成了:
app 只认识 UserContract
但是运行时:
UserContract
↓
到底由哪个 UserContractImpl 来实现?
也就是说,我们现在已经解决了:
“调用方应该依赖谁?”
答案是:
Contract
但还没有解决:
“Contract 的具体实现从哪里来?”
这个问题,才是后面整个架构继续演进的关键。
八、App 怎么调用 Contract?
比如通过 DI:
class HomeViewModel(
private val userService: UserContract
) {
fun loadUser() {
val user =
userService.getUser(1)
}
}
调用仍然是:
userService.getUser(1)
没有变化。
九、Contract 以后,这一行实际调用谁?
假设 DI 最终提供:
UserContractImpl
那么运行时:
App Module
HomeViewModel
↓
UserContract
↓
实际对象 UserContractImpl
↓
getUser(1)
↓
user-impl Module
所以:
模块虽然分开了,但运行时仍然是普通方法调用。
这一点特别重要。
十、跨模块 ≠ 跨进程
现在:
app
user-contract
user-impl
虽然是三个 Gradle Module。
但是最终运行时完全可以全部位于:
同一个 App Process
因此:
App Module
↓
UserContract
↓
UserContractImpl
仍然是:
同一个 ART 进程里的对象调用。
所以:
模块边界
只是:
代码和依赖边界。
而:
进程边界
是:
操作系统级运行边界。
这两者不是一回事。
十一、到这里其实已经很接近 Binder 的设计思想了
现在:
Client
↓
Contract
↓
Impl
调用方根本不在意:
Impl
怎么实现。
它只管:
userService.getUser(1)
那么我们自然可以继续问:
如果 Impl 不仅跑到另一个 Module,而是直接跑到另一个 Process,会发生什么?
这时候事情才真正开始变化。
十二、第四版:把 UserContractImpl 放到另一个进程
原来:
Process A
Caller
↓
UserContract
↓
UserContractImpl
现在改成:
Process A
Caller
↓
UserContract
=====================
进程边界
=====================
Process B
UserContractImpl
然后再看以前这一行:
val userService: UserContract =
UserContractImpl()
现在还能这么写吗?
答案:
不能。
十三、为什么跨进程以后不能直接拿 Impl?
因为:
Process A
和:
Process B
拥有彼此独立的:
虚拟地址空间
Heap
Stack
Java Object
UserContractImpl 对象实际存在:
Process B Heap
Process A 不能直接拿着:
Process B
里面一个 Java 对象的引用。
所以:
Process A
UserContract
↓
UserContractImpl
原来这条直接连接:
断掉了。
十四、注意:坏掉的不是 Contract 思想
这里非常容易误解。
不是:
Interface 跨进程以后没用了。
也不是:
Contract 设计错了。
真正失效的是:
“调用方直接拿 Impl 对象,然后直接调用方法”这件事。
我们依然非常希望:
userService.getUser(1)
这样写。
因为这才是最自然的业务接口。
所以问题变成:
既然 Process A 拿不到 Process B 的真实 Impl,那我能不能在 Process A 放一个假的 Impl?
可以。
于是:
Proxy
出现了。
十五、第五版:加入 Proxy
在 Process A 中写:
class UserServiceProxy :
UserContract {
override fun getUser(
id: Int
): User {
TODO("把请求发送给 Process B")
}
}
注意:
UserServiceProxy
同样实现:
UserContract
所以调用方仍然可以:
val userService: UserContract =
UserServiceProxy()
val user =
userService.getUser(1)
业务代码:
userService.getUser(1)
仍然没有改变。
十六、但这一行现在真正调用的是谁?
以前:
userService
↓
UserContractImpl
现在:
userService
↓
UserServiceProxy
所以:
userService.getUser(1)
真实调用链已经变成:
Caller
↓
UserContract
↓
实际对象 UserServiceProxy
↓
Proxy.getUser(1)
注意:
Proxy 不是真正的业务实现。
它不会真的去:
查数据库
请求网络
读取缓存
它只是:
远端 Impl 在当前进程中的替身。
十七、Proxy 为什么一定要实现同一个 Contract?
因为我们不希望调用方知道:
当前实现到底在本地
还是在另一个进程
调用方只依赖:
UserContract
所以:
本地实现
可以是:
UserContractImpl
而:
远程实现
在 Client 这边则可以表现成:
UserServiceProxy
两者都:
implements UserContract
于是:
Client
↓
UserContract
下面到底接:
Impl
还是:
Proxy
Client 不需要知道。
这就是代理模式非常关键的地方。
十八、那 Proxy.getUser(1) 到底干什么?
我们继续实现:
class UserServiceProxy(
private val ipc: Ipc
) : UserContract {
override fun getUser(
id: Int
): User {
val request =
Request(
method = "getUser",
args = listOf(id)
)
val response =
ipc.send(request)
return response.toUser()
}
}
现在:
userService.getUser(1)
实际:
Caller
↓
UserContract
↓
UserServiceProxy
↓
Proxy.getUser(1)
↓
构造 Request
Request:
method = getUser
id = 1
然后:
ipc.send(request)
十九、这里发生了一个非常重要的变化
最开始:
getUser(1)
是:
方法调用
现在 Proxy 把它转换成:
一份数据
例如:
我要调用的方法:
getUser
参数:
id = 1
为什么?
因为:
Java Method 本身不能直接穿过 Linux Process Boundary。
跨进程之前,必须先把:
“我要调用什么”
和:
“参数是什么”
描述出来。
所以:
方法调用
↓
Proxy
↓
变成可传输数据
这是理解 Binder 的关键一步。
二十、Request 到 Process B 以后怎么办?
假设:
ipc.send()
已经把请求送过去。
Process B 收到:
method = getUser
id = 1
但这只是:
数据
真正业务实现仍然是:
UserContractImpl.getUser(1)
所以现在还需要有人做:
数据
↓
重新变成方法调用
于是:
Stub
出现了。
二十一、第六版:加入 Stub
我们自己写一个最简单的:
class UserServiceStub(
private val impl: UserContract
) {
fun onRequest(
request: Request
): Response {
return when (
request.method
) {
"getUser" -> {
val id =
request.args[0] as Int
val user =
impl.getUser(id)
Response(user)
}
else -> {
error("Unknown method")
}
}
}
}
它干的事情其实非常直接。
收到:
method = getUser
id = 1
以后:
识别方法
↓
取出参数
↓
impl.getUser(1)
最终进入:
UserContractImpl.getUser(1)
二十二、现在完整调用链终于跑通了
Process A:
Caller
↓
UserContract
↓
UserServiceProxy
↓
Proxy.getUser(1)
↓
把调用转换成 Request
↓
method = getUser
id = 1
↓
IPC
跨过:
=====================
进程边界
=====================
到 Process B:
Stub
↓
识别 getUser
↓
读取 id = 1
↓
UserContractImpl
↓
getUser(1)
↓
返回 User
现在终于真正做到:
userService.getUser(1)
虽然真正 Impl:
根本不在当前 Process
调用还是完成了。
二十三、Proxy 和 Stub 到底分别是什么?
现在就不需要背定义了。
Proxy
做:
方法调用
↓
转换成远程请求
所以:
Proxy = 远程对象在 Client 进程里的本地替身。
Stub
做:
远程请求
↓
重新转换成方法调用
所以:
Stub = Server 侧接收和分发远程调用的入口。
两边刚好相反:
Interface Call
↓
Proxy
↓
IPC
↓
Stub
↓
Interface Call
二十四、返回值也必须跨进程
Server:
UserContractImpl.getUser(1)
返回:
User(
id = 1,
name = "Tom"
)
但这个:
User Object
存在:
Process B
不能直接把对象引用塞给:
Process A
所以返回值同样需要:
User
↓
转换成数据
↓
IPC
↓
Process A
↓
重新得到 User
所以远程调用完整其实是:
Request
↓
Server
↓
Response
二十五、到这里我们已经自己“发明”出 RPC 了
现在整个模型:
Client
↓
Interface
↓
Proxy
↓
Request
↓
IPC
↓
Stub
↓
Impl
已经是:
RPC
也就是:
Remote Procedure Call
远程过程调用。
什么意思?
真正的方法:
getUser()
在:
Process B
但 Process A 写起来仍然像:
userService.getUser(1)
本地方法调用。
二十六、现在问题只剩一个:IPC 到底怎么实现?
前面我们故意写了一个不存在的抽象:
ipc.send(request)
但真正 Android 中:
谁负责把数据从 Process A 运到 Process B?
答案之一就是:
Binder
所以我们刚才自己设计出来的:
Interface
↓
Proxy
↓
Request
↓
IPC
↓
Stub
↓
Impl
到了 Android Binder 世界,会进一步变成:
AIDL Interface
↓
Proxy
↓
Parcel
↓
Binder
↓
Stub
↓
Impl
二十七、Request 在 Binder 里会变成什么?
刚才我们的 Request:
method = getUser
id = 1
到了 Binder 世界,可以拆成两部分:
Transaction Code
+
Parcel
Transaction Code:
告诉 Server
我要调用哪个方法
例如:
TRANSACTION_getUser
Parcel:
装具体参数
例如:
id = 1
所以:
getUser(1)
会被 Proxy 转换成:
Method:
TRANSACTION_getUser
Data:
Parcel {
id = 1
}
二十八、Parcel 可以理解成 Binder 的参数箱
例如:
getUser(1)
Proxy 可以做:
Parcel
↓
writeInt(1)
Server 收到以后:
Parcel
↓
readInt()
↓
id = 1
返回值也一样:
User
↓
写入 Reply Parcel
↓
跨进程
↓
Client 从 Reply Parcel 读取
所以:
Parcel 负责承载 Binder RPC 的参数和返回值。
二十九、Binder 真正负责什么?
现在:
Proxy
已经把:
方法
参数
转换好了。
真正需要跨越:
Process A
和:
Process B
的,就是:
Binder
于是:
Process A
Proxy
↓
Parcel
↓
Binder
经过:
=====================
进程边界
=====================
进入:
Process B
Stub
↓
Impl
所以可以非常清楚地区分:
Interface / Contract 解决代码边界。
而:
Binder 解决进程边界。
三十、为什么还需要 AIDL?
现在假设:
UserService
有几十个方法:
getUser()
deleteUser()
updateUser()
getUserList()
login()
logout()
updateAvatar()
...
那么每个方法我们都要自己写:
Proxy
Request / Parcel
Transaction Code
Stub
参数解析
返回值解析
显然很麻烦。
于是 Android 提供:
AIDL
三十一、AIDL 是什么?
AIDL 全称:
Android Interface Definition Language
可以写:
interface IUserService {
User getUser(int id);
}
我们只负责:
定义跨进程接口。
AIDL 工具会帮我们生成大量:
Proxy
Stub
Transaction Code
Parcel 读写
transact
onTransact
相关样板代码。
三十二、所以 AIDL 可以怎么理解?
对有模块化经验的人来说,可以先理解成:
AIDL = 跨进程版本的 Contract 描述。
普通模块:
interface UserContract {
fun getUser(id: Int): User
}
跨进程:
interface IUserService {
User getUser(int id);
}
核心思想都一样:
先定义能力,再隐藏具体实现。
区别是:
Contract
背后的 Impl 通常:
就在同一个 Process
而:
AIDL Interface
背后的 Impl:
可以在另一个 Process
因此需要:
Proxy
Binder
Stub
来搭桥。
三十三、AIDL 生成的 Proxy,本质就是刚才我们手写的 Proxy
概念代码:
User getUser(int id) {
Parcel data =
Parcel.obtain();
Parcel reply =
Parcel.obtain();
data.writeInt(id);
remote.transact(
TRANSACTION_getUser,
data,
reply,
0
);
return readUser(reply);
}
核心仍然:
getUser(1)
↓
把参数写进去
↓
发送远程调用
↓
读取返回值
没有什么魔法。
三十四、AIDL 生成的 Stub 也一样
Server:
Stub.onTransact()
可以概念化理解:
switch (code) {
case TRANSACTION_getUser:
int id =
data.readInt();
User user =
getUser(id);
writeUser(
reply,
user
);
break;
}
还是:
远程数据
↓
识别方法
↓
读取参数
↓
执行接口
↓
写返回值
所以:
AIDL 本质就是把我们刚才手写的 Proxy / Stub 自动化了。
三十五、现在四次升级就非常清楚了
第一阶段:直接实现
Caller
↓
UserServiceImpl
调用:
userService.getUser(1)
实际:
UserServiceImpl.getUser(1)
第二阶段:Interface
Caller
↓
UserService
↓
UserServiceImpl
调用仍然:
userService.getUser(1)
实际仍然:
UserServiceImpl.getUser(1)
变化:
调用方不依赖具体实现类型。
第三阶段:模块 Contract
App Module
↓
UserContract
↓
User Impl Module
↓
UserContractImpl
调用仍然:
userService.getUser(1)
实际仍然:
UserContractImpl.getUser(1)
变化:
接口成为模块之间的能力边界。
但:
仍然是同进程直接调用
第四阶段:跨进程
Client Process
↓
IUserService
↓
Proxy
↓
Parcel
↓
Binder
↓
Stub
↓
UserServiceImpl
↓
Server Process
调用仍然:
userService.getUser(1)
但实际:
Proxy.getUser(1)
↓
Binder RPC
↓
UserServiceImpl.getUser(1)
变化:
Interface 和 Impl 之间出现了 Linux Process Boundary。
三十六、真正变化的是“Interface 到 Impl 的距离”
最开始:
Caller
↓
Impl
距离:
一个对象。
后来:
Caller
↓
Interface
↓
Impl
距离:
一个抽象层。
再后来:
App Module
↓
Contract
↓
Impl Module
距离:
一个模块边界。
最后:
Client Process
↓
Interface
↓
Proxy
↓
Binder
↓
Stub
↓
Server Process
距离:
一个操作系统进程边界。
所以:
边界越远,需要的基础设施越多。
三十七、但 userService.getUser(1) 为什么可以一直不变?
这其实就是整篇最有意思的地方。
第一版:
userService.getUser(1)
第二版:
userService.getUser(1)
模块化:
userService.getUser(1)
跨进程:
userService.getUser(1)
调用方式几乎没变。
但是下面已经从:
直接调用 Impl
一路演进成:
Proxy
↓
Parcel
↓
Binder
↓
Stub
↓
远端 Impl
这就是:
接口抽象真正厉害的地方。
调用方只关心:
我需要什么能力?
而不用关心:
实现在哪个类?
在哪个模块?
甚至在哪个进程?
三十八、Proxy 本质也是一个“实现类”
这个非常值得记。
假设:
UserContract
现在有两个实现:
UserContractImpl
和:
UserServiceProxy
它们都:
implements UserContract
但区别:
UserContractImpl
↓
自己真正干活
而:
UserServiceProxy
↓
自己不干业务
↓
把工作转给远端 Impl
所以:
Proxy 本质上也是 Interface 的一个实现。
只不过它实现 UserContract 的方式不是“自己完成业务”,而是“帮你找到真正干活的人”。
只是:
它的实现方式是“远程转发”。
三十八(append)、我们可以用“海外代购”来理解。
假设张三想买一套韩国化妆品。
真正能够提供商品的是:
韩国商家
我们把它理解成:
UserContractImpl
韩国商家真正负责:
收订单
↓
找商品
↓
扣库存
↓
打包
↓
发货
也就是说:
真正的业务
是在它那里完成的。
但是现在有个问题:
张三在中国
韩国商家在韩国
张三没办法直接跑到韩国商家那里调用:
买这个商品
怎么办?
中间出现一个:
海外代购
张三只需要告诉代购:
商品:A
数量:2
规格:XX
收货地址:XX
代购收到这些信息之后:
张三
↓
海外代购
↓
把订单传给韩国商家
↓
韩国商家真正处理订单
这里的海外代购,就非常像:
UserServiceProxy
关键就在这里:
为什么代购也必须“懂购买协议”?
因为张三本来想调用的是:
购买商品
那么代购至少也得能够接收:
商品是什么
数量是多少
规格是什么
钱是多少
也就是说,代购必须和真正的商家遵守同一套:
购买协议
这套协议就相当于:
UserContract
例如:
interface BuyContract {
fun buy(
productId: String,
count: Int
): Product
}
真正商家:
class KoreaShop : BuyContract {
override fun buy(
productId: String,
count: Int
): Product {
// 真正扣库存、生成订单、发货
return Product(...)
}
}
代购:
class OverseasProxy : BuyContract {
override fun buy(
productId: String,
count: Int
): Product {
// 自己没有库存
// 自己也不真正发货
// 把 productId、count
// 转发给韩国商家
...
}
}
于是对于张三来说:
BuyContract
▲
│
├── KoreaShop
│
└── OverseasProxy
它们两个都是:
BuyContract
但是一个:
KoreaShop
↓
真正干活
另一个:
OverseasProxy
↓
帮你传话
↓
让真正的人干活
换回 Android IPC:
张三
=
客户端 App
BuyContract
=
UserContract
海外代购
=UserServiceProxy
韩国真正商家
=
UserContractImpl
所以整个过程就是:
客户端
│
│ getUser(1)
▼
UserServiceProxy
│
│ 把方法名 + 参数打包
▼
Binder IPC
│
▼
远端进程
│
▼
UserContractImpl
│
│ 真正执行 getUser(1)
▼
返回 User
当然,真正的 Binder 中间还会多一个 Stub:
客户端
↓
Proxy
↓
Binder
↓
Stub
↓
Impl
所以更完整地说:
app
│
│ getUser(1)
▼
UserServiceProxy
│
│ IPC
▼
Binder
│
▼
UserServiceStub
│
▼
UserContractImpl
│
│ 真正干活
▼
User
但站在客户端角度,它根本不需要知道这些。
客户端手里拿到的可能只是:
val userService: UserContract
然后正常调用:
val user = userService.getUser(1)
客户端甚至不需要知道:
这个 UserContract
到底是 UserContractImpl
还是 UserServiceProxy
因为:
UserContractImpl
▲
│
│ implements
│
UserContract
UserServiceProxy
▲
│
│ implements
│
UserContract
两者对调用方来说:
长得一样
↓
都提供 UserContract 的能力
只是内部实现完全不同:
UserContractImpl
↓
自己做
UserServiceProxy
↓
让别人做
所以 Proxy 最核心的一句话就是:
我看起来像真正的服务,我也实现同一个 Contract,但真正的业务不是我做的,我只是替你把调用送到真正的服务那里。
这也解释了为什么一定要让 Proxy:
implements UserContract
因为只有这样,调用方才能完全不用改变原来的调用方式:
userContract.getUser(1)
本地实现也这么调:
UserContract
↓
UserContractImpl
远程调用仍然这么调:
UserContract
↓
Proxy
↓
IPC
↓
Stub
↓
UserContractImpl
调用方式没变,只是实现方式从“本地直接执行”,变成了“Proxy 远程转发”。
这就是 Proxy 最有意思的地方。
三十九、Stub 则负责把远程调用重新接回 Impl
Proxy:
接口调用
↓
变成远程消息
Stub:
远程消息
↓
重新变成接口调用
所以:
Client
↓
Interface
↓
Proxy
↓
IPC
↓
Stub
↓
Interface
↓
Impl
其实整条链头尾:
仍然都是 Interface。
IPC 只是中间桥梁。
四十、现在映射回真实 Android Framework
回到上一篇留下的问题:
App Process
PowerManager
│
│ ???
↓
system_server
PowerManagerService
现在:
???
已经可以先展开成:
PowerManager
↓
IPowerManager
↓
Proxy
↓
Binder
↓
Stub
↓
PowerManagerService
所以:
App Process
PowerManager
↓
IPowerManager.Proxy
↓
Binder
=====================
进程边界
=====================
IPowerManager.Stub
↓
PowerManagerService
system_server
是不是和我们刚才的:
UserContract
↓
Proxy
↓
Binder
↓
Stub
↓
UserContractImpl
完全是同一套思想?
四十一、WindowManager 也是一样
App Process
WindowManager
↓
IWindowManager
↓
Proxy
↓
Binder
=====================
Stub
↓
WindowManagerService
system_server
AMS:
App
↓
IActivityManager
↓
Binder
↓
ActivityManagerService
ATMS:
App
↓
IActivityTaskManager
↓
Binder
↓
ActivityTaskManagerService
所以:
Android Framework 并没有凭空发明一种完全陌生的设计思想。
它只是把我们平时熟悉的:
Interface
↓
Impl
扩展到了:
跨进程
这个级别。
四十二、三个边界现在可以连起来了
类之间
Interface
↓
Impl
解决:
调用方和具体实现类解耦。
模块之间
Contract
↓
Module Impl
解决:
模块调用方和模块内部实现解耦。
进程之间
AIDL Interface
↓
Proxy
↓
Binder
↓
Stub
↓
Remote Impl
解决:
调用方和另一个 Linux Process 中的实现建立接口调用关系。
四十三、所以 Interface、Contract、AIDL 不是三个毫无关系的东西
更好的理解应该是:
Interface
↓
最基础的接口抽象
扩大到模块:
Interface
↓
Contract
再扩大到进程:
Interface
↓
AIDL Interface
↓
Binder
它们背后的思维方式始终是:
面向接口,隐藏实现。
四十四、但 Binder 和它们负责的事情不同
这里最后一定要区分。
Interface
负责:
定义能力。
Contract
负责:
定义模块之间的能力边界。
AIDL
负责:
描述跨进程接口,并生成大量 Binder 样板代码。
而:
Binder
才是真正:
把调用跨过 Linux Process Boundary 的 IPC 基础设施。
所以:
AIDL
≠
Binder
就像:
接口定义
≠
通信网络
那 Binder 更像什么?
Binder 就像中韩之间那套可靠的国际物流 + 通信体系。
代购负责:
把张三的需求整理好
Binder 负责:
把这份需求真正跨国送过去
韩国那边的接单窗口 Stub:
收到需求
↓
看懂需求
↓
交给真正商家处理
所以整个过程:
张三
↓
代购 Proxy
↓
【Binder 跨进程运输】
↓
接单窗口 Stub
↓
真正商家 Impl
现阶段直接记住:
Binder 本质 = Android 的跨进程 RPC 机制。
再说得更形象一点:
Binder 是“桥”,Proxy 和 Stub 是桥两边的翻译员,Impl 才是真正干活的人。
四十五、为什么 Binder 看起来复杂?
因为平时:
Interface
↓
Impl
中间什么都没有。
而跨进程:
Interface
↓
Proxy
↓
Parcel
↓
Binder
↓
Stub
↓
Impl
突然多出很多东西。
但是现在每一层为什么存在已经很清楚:
Proxy
=
让调用方继续像调用接口一样使用远端对象
Parcel
=
让参数和返回值能够被传输
Binder
=
真正跨越 Linux Process
Stub
=
把远程请求重新恢复成方法调用
AIDL
=
自动生成大量重复样板代码
所以 Binder 并不是:
“Android 故意搞得很复杂。”
而是:
跨进程以后,原来一个简单 Interface → Impl 调用所隐含的事情,都必须显式解决。
四十六、这一篇最核心的一张图
调用方看到的世界
userService.getUser(1)
│
↓
Interface
│
┌────────────┴────────────┐
│ │
同进程 跨进程
│ │
↓ ↓
Impl Proxy
│
↓
Parcel
│
↓
Binder
│
Linux Process
Boundary
│
↓
Stub
│
↓
Impl
调用方:
永远只想看到 Interface。
至于实现:
就在本地
还是:
在另一个 Module
甚至:
在另一个 Linux Process
都可以隐藏在接口后面。
四十七、最后重新看一次 userService.getUser(1)
最开始:
userService.getUser(1)
代表:
直接调用 Impl
增加 Interface:
userService.getUser(1)
代表:
Interface
↓
Impl
模块化:
userService.getUser(1)
代表:
Contract
↓
Module Impl
跨进程:
userService.getUser(1)
代表:
AIDL Interface
↓
Proxy
↓
Binder
↓
Stub
↓
Remote Impl
表面:
这一行代码几乎没变。
下面:
已经从一个普通对象调用,演进成了跨 Linux Process 的 RPC。
这就是这篇真正想讲清楚的东西。
四十八、一句话总结
从:
Interface
到:
Contract
再到:
AIDL + Binder
并不是三套完全割裂的思想。
核心一直都是:
调用方依赖能力定义,而不是具体实现。
真正变化的是:
Interface
和
Impl
之间的距离越来越远。
从:
同一个类调用
到:
模块边界
再到:
Linux Process Boundary
于是中间才逐渐需要:
Proxy
Parcel
Binder
Stub
所以以后再看到:
Proxy
Stub
AIDL
Parcel
Binder
不要先把它们当成几个陌生的系统名词。
只需要先想:
Interface
↓
Impl
然后问一句:
如果 Impl 跑到另一个进程里了,我还想保持同样的接口调用方式,该怎么办?
Proxy、Stub、Parcel、Binder、AIDL:
就是这个问题一步一步推出来的答案。
下一篇:
《Android 系统层扫盲 08:Binder 到底怎么跨进程?从 PowerManager 一路跑到 system_server》
这一篇我们解决的是:
为什么跨进程以后会自然出现 Proxy、Stub、AIDL 和 IPC。
下一篇则正式解决:
Android 到底是怎么把这套设计真正实现出来的?
我们会沿着真实调用:
PowerManager
↓
IPowerManager.Proxy
↓
Parcel
↓
transact()
↓
Java Binder
↓
JNI
↓
Native Binder
↓
Linux Binder Driver
↓
system_server Binder Thread
↓
IPowerManager.Stub
↓
PowerManagerService
完整跑一次。
也就是说:
这一篇先理解设计思想,第八篇再理解 Binder 的真正实现链路。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/wulong756273/article/details/165240313



