第一性:Android 组件模型的根本矛盾
Android 四大组件都面临同一个问题:
应用是沙箱隔离的,但用户期望的功能往往需要跨应用协作。
| 矛盾 | 表现 |
|---|---|
| 进程隔离是强制的 | 每个 App 独立进程,内存不共享 |
| 用户功能需要跨应用 | 音乐播放、后台下载、卡片渲染...... |
| 系统需要统一调度 | 内存不足时,系统要知道哪些后台任务可以杀 |
| 应用需要生命周期管理 | 页面退了,但任务不能停 |
Activity 解决不了的问题:
- Activity 绑定到 UI 线程,页面销毁后任务就断了
- Activity 不能被其他应用直接启动和绑定
- 后台任务(如播放音乐)不应该有可见界面
Service 的本质:无 UI 的、可跨进程调用的、受系统生命周期管理的后台组件
本质:后台任务载体 + 跨进程能力暴露点
Service 的核心设计:
一个没有用户界面的组件,运行在宿主进程的主线程中,可以被系统启动、绑定、管理生命周期,同时通过 Binder 向其他应用暴露能力。
css
┌─────────────────────────────────────────┐
│ 应用 A(宿主) │
│ ┌─────────────────────────────────┐ │
│ │ Service 组件 │ │
│ │ ┌─────────┐ ┌─────────┐ │ │
│ │ │ 后台任务 │ │ Binder │◄─────┼───┼── 其他应用通过 AIDL 调用
│ │ │ 线程 │ │ 接口暴露 │ │ │
│ │ └─────────┘ └─────────┘ │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
▲
│ 系统管理(AMS)
▼
启动、绑定、解绑、销毁
内存紧张时回收优先级
解决的问题
| 问题 | Service 如何解决 |
|---|---|
| 后台任务生命周期 | 页面(Activity)销毁后,Service 可以继续运行 |
| 跨应用能力暴露 | 通过 AIDL/Binder,其他应用可以调用你的功能 |
| 系统统一调度 | Service 有明确的优先级,系统知道什么时候可以杀 |
| 无 UI 的纯逻辑 | 不需要界面,专注做计算、网络、播放等 |
核心场景
1. 后台音乐播放(最经典)
arduino
音乐 App
│
▼ 用户点击播放后退到桌面
Activity 销毁
│
▼ 但音乐不能停
Service 继续运行
│
▼ 系统知道"有前台 Service 在播放"
通知栏显示播放控制,系统不轻易杀这个进程
2. 跨应用能力暴露(快应用框架场景)
scss
┌─────────────────────────────────────────┐
│ 快应用框架(你的应用) │
│ ┌─────────────────────────────────┐ │
│ │ QuickAppService │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ AIDL 接口:IQuickAppRenderer │ │
│ │ │ • renderCard(String json) │ │
│ │ │ • getCardHeight() │ │
│ │ │ • handleClick(int x, int y) │ │
│ │ └─────────────────────────┘ │ │
│ │ ▲ │ │
│ │ │ Binder │ │
│ │ ┌──────┴──────┐ │ │
│ │ │ 渲染引擎线程 │ │ │
│ │ │ (Skia/自研) │ │ │
│ │ └─────────────┘ │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
▲
│ AIDL 调用
│
┌─────────────────────────────────────────┐
│ 小爱同学(第三方应用, 好比说AI chat) │
│ ┌─────────────────────────────────┐ │
│ │ Chat 流 UI │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ 消息气泡区域 │ │ │
│ │ │ ┌─────────────────┐ │ │ │
│ │ │ │ 快应用卡片 │ │ │ │
│ │ │ │ (由你的 Service │ │ │ │
│ │ │ │ 远程渲染) │ │ │ │
│ │ │ └─────────────────┘ │ │ │
│ │ └─────────────────────────┘ │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
为什么这个场景必须用 Service:
| 原因 | 说明 |
|---|---|
| 跨进程 | 小爱同学和快应用框架是两个独立 App,不同进程 |
| 无 UI 侵入 | 快应用框架不需要在小爱同学的进程中显示自己的 Activity |
| 能力复用 | 一套渲染引擎,多个宿主应用复用 |
| 安全隔离 | 快应用的代码运行在框架自己的进程中,不污染宿主 |
类RN框架(如快应用框架)
核心设计
采用 Binder IPC + 系统预装 Service(快应用框架) + Surface 共享渲染:
- S 端 :快应用框架作为系统级 Service,开机预加载,常驻系统进程
- C 端:封装为 AAR SDK,宿主(如小爱同学)通过 SDK 绑定调用
- 渲染结果 :通过 Surface 共享 嵌入宿主 UI
AIDL 接口定义
aidl
// IQuickAppRenderer.aidl
// 第一性:定义跨进程边界的能力契约
// Binder 是 Android IPC 的基石,AIDL 是 Binder 的接口描述语言
interface IQuickAppRenderer {
// 创建卡片,返回 token 用于后续操作
IBinder createCard(in Bundle params, in IBinder hostSurface);
// 更新卡片数据
void updateData(IBinder cardToken, in Bundle data);
// 转发触摸事件
void dispatchTouchEvent(IBinder cardToken, in MotionEvent event);
// 获取卡片期望尺寸
int getCardHeight(in Bundle params);
// 销毁卡片
void destroyCard(IBinder cardToken);
}
Java 实现版
Service 端
java
public class QuickAppService extends Service {
// 第一性:Service 是后台组件,运行在主线程
// 耗时操作必须放到子线程,避免 ANR
private final ExecutorService renderExecutor = Executors.newSingleThreadExecutor();
// 第一性:Binder 是 Android IPC 的核心
// 通过 Stub 实现 AIDL 接口,暴露跨进程能力
private final IQuickAppRenderer.Stub binder = new IQuickAppRenderer.Stub() {
@Override
public IBinder createCard(Bundle params, IBinder hostSurface) throws RemoteException {
// 第一性:跨进程调用时,Binder.getCallingUid() 获取调用方身份
// 这是 Android 安全模型的基础:基于 UID 的权限隔离
int uid = Binder.getCallingUid();
if (!isAuthorized(uid)) {
throw new SecurityException("Unauthorized uid: " + uid);
}
String appId = params.getString("app_id");
if (appId == null) {
throw new IllegalArgumentException("app_id required");
}
// 第一性:Surface 是跨进程共享图形缓冲区的句柄
// 通过 IBinder 传递 Surface,实现宿主 UI 中嵌入远程渲染内容
Surface surface = new Surface(hostSurface);
// 在独立线程中初始化渲染引擎
// 避免阻塞 Binder 线程(Binder 线程池有限,阻塞会导致 IPC 卡顿)
Future<CardInstance> future = renderExecutor.submit(() -> {
QuickAppInstance instance = engine.loadApp(appId);
RenderTarget target = new RenderTarget(surface);
instance.attachToSurface(target);
return new CardInstance(instance, target);
});
try {
CardInstance card = future.get();
activeCards.put(card.token, card);
return card.token;
} catch (Exception e) {
throw new RuntimeException("Failed to create card", e);
}
}
@Override
public void dispatchTouchEvent(IBinder cardToken, MotionEvent event) throws RemoteException {
// 第一性:触摸事件从宿主进程传递到 Service 进程
// 事件坐标系需要转换,因为宿主和 Service 的窗口坐标可能不同
CardInstance card = activeCards.get(cardToken);
if (card == null) return;
card.instance.dispatchTouch(event.getX(), event.getY(), event.getAction());
}
@Override
public void destroyCard(IBinder cardToken) throws RemoteException {
// 第一性:资源必须显式释放
// Android 没有自动 GC 跨进程资源,不释放会导致 Surface 泄漏
CardInstance card = activeCards.remove(cardToken);
if (card != null) {
card.instance.destroy();
card.target.release();
// 第一性:Surface 释放后,宿主窗口会显示空白或黑屏
// 需要宿主在销毁前做好过渡处理
}
}
// ... 其他方法
};
@Override
public IBinder onBind(Intent intent) {
// 第一性:onBind 是 Service 对外暴露能力的入口
// 返回的 IBinder 是 Binder 代理的实体端(Stub)
return binder;
}
@Override
public void onDestroy() {
// 第一性:Service 生命周期由系统管理
// 被杀时必须清理资源,避免内存泄漏
super.onDestroy();
renderExecutor.shutdown();
for (CardInstance card : activeCards.values()) {
card.instance.destroy();
card.target.release();
}
activeCards.clear();
}
}
宿主端(Java)
java
public class QuickAppCardView extends FrameLayout {
private IQuickAppRenderer renderer;
private IBinder cardToken;
private SurfaceView surfaceView;
// 第一性:ServiceConnection 是绑定 Service 的回调契约
// 连接建立后,才能进行跨进程调用
private final ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 第一性:asInterface 将 IBinder 转为 AIDL 接口代理
// 后续调用会通过 Binder 驱动发送到 Service 进程
renderer = IQuickAppRenderer.Stub.asInterface(service);
renderCard();
}
@Override
public void onServiceDisconnected(ComponentName name) {
// 第一性:Service 被杀或崩溃时回调
// 必须处理重连或降级,否则卡片会卡死
renderer = null;
cardToken = null;
}
};
public void attach(String appId) {
Intent intent = new Intent();
intent.setComponent(new ComponentName(
"com.quickapp.framework",
"com.quickapp.framework.QuickAppService"
));
// 第一性:bindService 是异步的
// 连接建立前不能调用 renderer,必须通过 ServiceConnection 回调
getContext().bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
private void renderCard() {
if (renderer == null) return;
Bundle params = new Bundle();
params.putString("app_id", "weather_card");
try {
// 第一性:AIDL 调用是阻塞的(同步 IPC)
// 必须在子线程执行,避免阻塞主线程
int height = renderer.getCardHeight(params);
surfaceView.setLayoutParams(new LayoutParams(MATCH_PARENT, height));
// 第一性:Surface 是跨进程共享的图形缓冲区
// 创建后,Service 会在自己的进程中渲染内容到这块缓冲区
// 宿主窗口会自动显示最新内容(通过 SurfaceFlinger 合成)
cardToken = renderer.createCard(params, surfaceView.getHolder().getSurface());
// 转发触摸事件
surfaceView.setOnTouchListener((v, event) -> {
try {
renderer.dispatchTouchEvent(cardToken, event);
} catch (RemoteException e) {
// 第一性:跨进程调用可能失败(Service 被杀、进程崩溃)
// 必须处理异常,避免宿主崩溃
e.printStackTrace();
}
return true;
});
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void detach() {
if (cardToken != null && renderer != null) {
try {
renderer.destroyCard(cardToken);
} catch (RemoteException e) {
e.printStackTrace();
}
}
getContext().unbindService(connection);
}
}
Kotlin 实现版
Service 端(Kotlin)
kotlin
class QuickAppService : Service() {
// 第一性:Service 运行在主线程,耗时操作必须放到子线程
// Kotlin 使用单线程线程池,保证渲染顺序性
private val renderExecutor = Executors.newSingleThreadExecutor()
private val engine = QuickAppEngine()
// 第一性:维护活跃卡片映射,token -> 卡片实例
// 跨进程资源必须显式管理,Android 不会自动 GC 跨进程对象
private val activeCards = mutableMapOf<IBinder, CardInstance>()
// 第一性:Binder Stub 是 AIDL 接口的实体实现
// 所有跨进程调用最终都会落到这里执行
private val binder = object : IQuickAppRenderer.Stub() {
override fun createCard(params: Bundle?, hostSurface: IBinder?): IBinder {
// 第一性:跨进程安全校验
// Binder.getCallingUid() 获取调用方身份,这是 Android 沙箱的基石
val uid = Binder.getCallingUid()
require(isAuthorized(uid)) { "Unauthorized uid: $uid" }
val appId = params?.getString("app_id")
?: throw IllegalArgumentException("app_id required")
// 第一性:Surface 是跨进程共享图形缓冲区的句柄
// 通过 IBinder 传递,实现"远程渲染、本地显示"
val surface = Surface(hostSurface)
// 第一性:Binder 调用运行在 Binder 线程池
// 阻塞操作会占用 Binder 线程,导致其他 IPC 卡顿
// 所以渲染初始化放到独立线程
val future = renderExecutor.submit<CardInstance> {
val instance = engine.loadApp(appId)
val target = RenderTarget(surface)
instance.attachToSurface(target)
CardInstance(instance, target)
}
return future.get().also { card ->
activeCards[card.token] = card
}
}
override fun dispatchTouchEvent(cardToken: IBinder?, event: MotionEvent?) {
// 第一性:Kotlin 的空安全语法 ?: return
// 避免空指针,比 Java 的 if (x == null) return 更简洁
val token = cardToken ?: return
val motionEvent = event ?: return
activeCards[token]?.instance?.dispatchTouch(
x = motionEvent.x,
y = motionEvent.y,
action = motionEvent.action
)
}
override fun destroyCard(cardToken: IBinder?) {
val token = cardToken ?: return
// 第一性:remove 返回被移除的值,避免二次查找
activeCards.remove(token)?.let { card ->
// 第一性:资源必须显式释放
// Surface 泄漏会导致 GPU 内存持续增长,最终 OOM
card.instance.destroy()
card.target.release()
}
}
// ... 其他方法
}
override fun onBind(intent: Intent): IBinder {
// 第一性:onBind 是 Service 对外暴露能力的唯一入口
// 返回的 IBinder 会被系统封装成 BinderProxy 传递给客户端
return binder
}
override fun onDestroy() {
// 第一性:Service 生命周期由系统管理(AMS 调度)
// 被杀时必须清理资源,否则就是内存泄漏
super.onDestroy()
renderExecutor.shutdown()
activeCards.values.forEach { card ->
card.instance.destroy()
card.target.release()
}
activeCards.clear()
}
data class CardInstance(
val instance: QuickAppInstance,
val target: RenderTarget,
val token: IBinder = target.token
)
}
宿主端(Kotlin + Coroutine)
kotlin
class QuickAppCardView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : FrameLayout(context, attrs) {
// 第一性:AIDL 接口代理,所有调用都会通过 Binder 驱动跨进程
private var renderer: IQuickAppRenderer? = null
private var cardToken: IBinder? = null
private val surfaceView = SurfaceView(context)
// 第一性:ServiceConnection 是异步绑定回调
// bindService 是异步的,连接建立前 renderer 为 null
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
// 第一性:asInterface 将底层 IBinder 转为类型化代理
// 后续调用看起来像本地方法,实际是跨进程 IPC
renderer = IQuickAppRenderer.Stub.asInterface(service)
renderCard()
}
override fun onServiceDisconnected(name: ComponentName?) {
// 第一性:Service 进程被杀或崩溃时回调
// 必须处理,否则后续调用会 Crash
renderer = null
cardToken = null
}
}
init {
addView(surfaceView)
}
fun attach(appId: String) {
val intent = Intent().apply {
component = ComponentName(
"com.quickapp.framework",
"com.quickapp.framework.QuickAppService"
)
}
// 第一性:BIND_AUTO_CREATE 表示如果 Service 未运行则自动创建
// 这是 Android 组件化设计的体现:按需启动,用完释放
context.bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
private fun renderCard() {
val renderer = this.renderer ?: return
val surface = surfaceView.holder.surface
val params = Bundle().apply {
putString("app_id", "weather_card")
}
// 第一性:AIDL 调用是同步阻塞的 IPC
// Kotlin 协程 + withContext(Dispatchers.IO) 避免阻塞主线程
lifecycleScope.launch {
// 获取卡片高度
val height = withContext(Dispatchers.IO) {
renderer.getCardHeight(params)
}
surfaceView.layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT,
height
)
// 创建卡片
// 第一性:Surface 跨进程共享后,Service 在自己的进程渲染
// 内容通过 SurfaceFlinger 合成到宿主窗口,宿主无感知
cardToken = withContext(Dispatchers.IO) {
renderer.createCard(params, surface)
}
// 转发触摸事件
surfaceView.setOnTouchListener { _, event ->
// 第一性:触摸事件从宿主 UI 线程传递到 Service 进程
// 异步发送,避免阻塞宿主渲染
lifecycleScope.launch(Dispatchers.IO) {
try {
cardToken?.let { token ->
renderer.dispatchTouchEvent(token, event)
}
} catch (e: RemoteException) {
// 第一性:跨进程调用随时可能失败
// Service 被杀、进程崩溃、Binder 驱动异常
// 必须捕获,否则宿主 App 会跟着崩溃
Log.e("QuickApp", "IPC failed", e)
}
}
true
}
}
}
fun detach() {
// 第一性:资源释放顺序很重要
// 先通知 Service 销毁卡片,再解绑 Service
// 否则 Service 可能泄露 Surface
cardToken?.let { token ->
try {
renderer?.destroyCard(token)
} catch (e: RemoteException) {
Log.e("QuickApp", "Destroy failed", e)
}
}
// 第一性:unbindService 后,如果没有任何客户端绑定
// 且 Service 不是前台 Service,系统会在空闲时销毁它
context.unbindService(connection)
renderer = null
cardToken = null
}
}
Service 的能力边界与局限性
| 能力 | 说明 | 你的场景 |
|---|---|---|
| 后台运行 | 无 UI 持续执行任务 | 渲染引擎常驻,响应多个宿主 |
| 跨进程调用 | 通过 AIDL/Binder 暴露接口 | 小爱同学调用快应用渲染 |
| 共享 Surface | 通过图形缓冲区跨进程显示 | 远程渲染,本地嵌入 |
| 生命周期管理 | 系统知道你在运行,内存紧张时通知 | 框架被杀前保存状态 |
| 前台保活 | startForeground() 显示通知 |
如果框架需要常驻 |
| 局限性 | 说明 | 解决方案 |
|---|---|---|
| 主线程阻塞 | Service 默认在主线程 | 独立 RenderThread(已做) |
| 内存占用 | 框架进程常驻 | 按需加载/卸载实例 |
| 启动延迟 | bindService 建立连接有延迟 | 预绑定、缓存 Binder 代理 |
| 版本兼容 | Android 8+ 后台启动限制 | 前台 Service 或 JobScheduler |
| 安全隔离 | 恶意应用可能绑定 | 权限校验、签名验证 |
一句话总结
Service 的本质是:无 UI 的、受系统管理的、可跨进程暴露能力的后台组件。它解决了"应用沙箱隔离"和"跨应用协作"的根本矛盾。在你的快应用框架场景中,Service 是渲染能力的"远程出口"------通过 AIDL 暴露渲染接口,让宿主应用在自己的 UI 中嵌入你的卡片,同时保持进程隔离和安全边界。