当前位置: 首页 > news >正文

Android T 分屏实战:从SystemUI的WindowContainerTransaction到SurfaceFlinger,一次跨进程通信的完整拆解

Android T 分屏机制深度解析:从SystemUI到SurfaceFlinger的全链路实现

当我们在Android设备上流畅地使用分屏功能时,背后隐藏着一套复杂的跨进程协作机制。本文将深入剖析Android T版本中分屏功能从SystemUI发起请求,到最终在SurfaceFlinger完成图层合成的完整技术链路。

1. 分屏机制的架构概览

Android的分屏功能涉及多个系统组件的协同工作,主要包括:

  • SystemUI:作为用户交互的入口,负责分屏请求的发起和界面控制
  • WindowManagerService:管理窗口层级和布局
  • ActivityManagerService:处理任务栈管理
  • SurfaceFlinger:负责最终的图层合成和显示

这些组件通过Binder IPC进行跨进程通信,而WindowContainerTransaction(WCT)则是贯穿整个流程的核心数据结构。

2. SystemUI端的请求发起与处理

2.1 从Launcher到SystemUI的跨进程调用

分屏流程通常从Launcher开始,通过SystemUiProxy发起跨进程调用:

// Launcher端发起分屏请求 mSystemUiProxy.startTasksWithLegacyTransition( taskIds[0], mainOpts.toBundle(), taskIds[1], null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT, splitRatio, adapter );

这个调用会通过Binder到达SystemUI进程的SplitScreenController

// SystemUI端接收请求 public void startTasksWithLegacyTransition(int mainTaskId, Bundle mainOptions, int sideTaskId, Bundle sideOptions, int sidePosition, float splitRatio, RemoteAnimationAdapter adapter) { // 切换到主线程处理 executeRemoteCallWithTaskPermission(mController, "startTasks", (controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition( mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition, splitRatio, adapter ) ); }

2.2 WindowContainerTransaction的构建

StageCoordinator是分屏功能的核心控制器,它负责构建包含所有变更的WindowContainerTransaction

private void startWithLegacyTransition(...) { // 初始化分屏布局 mSplitLayout.init(); // 创建WindowContainerTransaction对象 final WindowContainerTransaction wct = new WindowContainerTransaction(); // 设置分屏位置比例 mSplitLayout.setDivideRatio(splitRatio); // 更新窗口边界 updateWindowBounds(mSplitLayout, wct); // 重新排序任务栈 wct.reorder(mRootTaskInfo.token, true); // 添加任务启动请求 wct.startTask(mainTaskId, mainOptions); wct.startTask(sideTaskId, sideOptions); // 应用事务 mTaskOrganizer.applyTransaction(wct); }

3. WindowContainerTransaction的跨进程传递

3.1 WCT的数据结构解析

WindowContainerTransaction包含两种主要操作类型:

操作类型数据结构用途
Change包含Configuration变更设置窗口边界、配置等
HierarchyOp包含层级操作任务启动、重新排序等

关键的数据结构构建过程:

// 设置窗口边界示例 public WindowContainerTransaction setBounds( @NonNull WindowContainerToken container, @NonNull Rect bounds) { Change chg = getOrCreateChange(container.asBinder()); chg.mConfiguration.windowConfiguration.setBounds(bounds); chg.mConfigSetMask |= ActivityInfo.CONFIG_WINDOW_CONFIGURATION; chg.mWindowSetMask |= WindowConfiguration.WINDOW_CONFIG_BOUNDS; return this; } // 重新排序示例 public WindowContainerTransaction reorder( @NonNull WindowContainerToken child, boolean onTop) { mHierarchyOps.add(HierarchyOp.createForReorder(child.asBinder(), onTop)); return this; }

3.2 分屏边界的计算逻辑

分屏布局的核心是SplitLayout类,它负责计算分割线位置和各区域边界:

public void setDivideRatio(float ratio) { // 计算分割线位置 final int position = isLandscape() ? mRootBounds.left + (int)(mRootBounds.width() * ratio) : mRootBounds.top + (int)(mRootBounds.height() * ratio); // 计算对齐目标 final DividerSnapAlgorithm.SnapTarget snapTarget = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(position); // 设置分割线位置 setDividePosition(snapTarget.position, false); } private void updateBounds(int position) { // 更新分割线边界 mDividerBounds.set(mRootBounds); if (isLandscape(mRootBounds)) { position += mRootBounds.left; mDividerBounds.left = position - mDividerInsets; mDividerBounds.right = mDividerBounds.left + mDividerWindowWidth; mBounds1.right = position; mBounds2.left = mBounds1.right + mDividerSize; } else { position += mRootBounds.top; mDividerBounds.top = position - mDividerInsets; mDividerBounds.bottom = mDividerBounds.top + mDividerWindowWidth; mBounds1.bottom = position; mBounds2.top = mBounds1.bottom + mDividerSize; } }

4. 分屏窗口的特殊处理

4.1 SplitWindowManager与分割线窗口

分屏功能使用特殊的SplitWindowManager来管理分割线窗口,这类窗口不会出现在常规的dumpsys window windows输出中:

void init(SplitLayout splitLayout, InsetsState insetsState) { // 创建SurfaceControlViewHost mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this); // 加载分割线视图 mDividerView = (DividerView) LayoutInflater.from(mContext) .inflate(R.layout.split_divider, null); // 设置窗口参数 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( dividerBounds.width(), dividerBounds.height(), TYPE_DOCK_DIVIDER, FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY, PixelFormat.TRANSLUCENT); // 设置特殊标志 lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; // 添加视图到窗口 mViewHost.setView(mDividerView, lp); }

4.2 分屏任务的层级管理

分屏功能使用特殊的任务栈结构:

#0 Task=4 type=undefined mode=fullscreen #1 Task=6 type=undefined mode=multi-window #0 Task=5 type=undefined mode=multi-window

其中:

  • Task 4是根任务栈
  • Task 5和6分别对应主分屏和次分屏的任务栈

5. SurfaceFlinger端的最终合成

5.1 分屏图层的特殊处理

在SurfaceFlinger中,分屏相关的图层会带有特殊标记:

+ Layer 0x7a8f4d0c00 (Divider) + Region: [0,0][1440,10] + Flags: 0x40000000 (TRUSTED_OVERLAY) + BufferQueue: [1440x10:1440x10,1]

可以通过以下命令查看分屏相关的图层信息:

adb shell dumpsys SurfaceFlinger | grep -A 10 "Divider"

5.2 分屏动画的同步机制

分屏功能使用SyncQueue来确保布局变更和动画的同步:

mSyncQueue.runInSync(t -> { // 显示分割线 setDividerVisibility(true, t); // 更新Surface边界 updateSurfaceBounds(mSplitLayout, t, false); });

这种同步机制避免了分屏过程中可能出现的视觉闪烁或布局不一致问题。

6. 分屏问题的调试技巧

6.1 常用调试命令

  1. 查看窗口层级:

    adb shell dumpsys window windows
  2. 查看分屏任务信息:

    adb shell dumpsys activity containers
  3. 查看SurfaceFlinger图层:

    adb shell dumpsys SurfaceFlinger

6.2 常见问题排查

  • 分屏边界不正确:检查SplitLayoutupdateBounds计算
  • 动画卡顿:检查RemoteAnimationAdapter的实现和同步机制
  • 窗口层级异常:验证WindowContainerTransaction中的reorder操作

7. 车载与手机分屏的差异

在车载系统中,分屏功能需要考虑更多特殊场景:

  1. 屏幕比例:车载屏幕通常更宽,需要调整分割比例算法
  2. 输入焦点:需要更复杂的焦点管理逻辑
  3. 安全限制:某些驾驶模式下可能限制分屏功能

车载分屏的关键修改点通常集中在SplitLayoutStageCoordinator类中。

http://www.jsqmd.com/news/724432/

相关文章:

  • 抖音批量下载神器:10倍效率提升,告别手动保存烦恼
  • EOR公司搞定加拿大雇佣难题:优质海外人力资源服务商盘点 - 品牌2026
  • 【第25篇】A2A 代理部署指南优化版(Python 实现)
  • 小伙伴投稿-什么时候选择吃亏-什么时候选择拒绝
  • 一键搞定完整网页截图:告别滚动拼接的烦恼 [特殊字符]
  • 如何用Sunshine搭建终极家庭游戏串流服务器:5步实现跨设备畅玩3A大作
  • DETR目标检测实战:手把手教你用Transformer实现端到端检测(附COCO数据集配置)
  • 打造专属AI语音助手:小爱音箱智能升级终极方案
  • WarcraftHelper:3个关键优化让经典魔兽争霸3焕发新生
  • PID温控踩坑记:我的STM32F4加热系统如何从‘过冲振荡’到‘平稳如狗’
  • 通过按钮改变背景颜色
  • 嵌入式——认识电子元器件——温度开关系列
  • 气门摇臂轴支座加工工艺及夹具设计CAD图纸
  • 小伙伴投稿-我们来说下海南封关
  • JetBrains IDE试用期重置终极指南:开源免费工具完全解析
  • 3步行动指南:用BetterJoy让Switch手柄在PC上完美工作
  • DeepLake:AI原生数据湖如何统一管理多模态数据与向量化检索
  • MySQL 为什么不推荐使用外键?
  • LOLIN C3 Pico开发板:RISC-V物联网开发实战解析
  • GD32F303CCT6 ADC采样卡在0.4V区间?别慌,一个时钟分频配置就搞定
  • 避开小米刷机坑:详解‘remote not allowed in locked state’与Bootloader解锁的完整流程(2024最新)
  • 小伙伴投稿-我们来说下康养行业-结合AI-
  • 从一次深夜告警说起:手把手复盘Kafka 3.5.1集群SASL认证的完整配置流程与避坑点
  • AlienFX Tools深度解析:Alienware灯光与风扇控制的底层技术实现
  • 还在手动敲日期时间?这个Mac快捷键让你秒变效率达人!
  • 深度强化学习在数学推理中的应用与实践
  • 【AI面试临阵磨枪-32】如何提升工具调用(Function Call)准确率?常见失败场景与解决方法
  • 2026年Q2中国计算机电缆优质厂家首选推荐:艾能电气(安徽)有限公司 - 安互工业信息
  • 手把手教你用CAPL在CANoe里模拟一个完整的LIN从节点(带定时发送)
  • 《Windows Internals》读书笔记 10.4.3:WMI 仓库(Repository)——它到底存了什么,又不存什么?