Android分屏流程分析

news/2024/4/19 7:04:14/文章来源:https://blog.csdn.net/qq_36063677/article/details/130350002

本文基于Android 11。

SystemUI模块中的Divider管理着所有关于分屏的对象:

  • DividerView(分屏分割线,分屏显示界面)
  • SplitScreenTaskOrganizer(分屏Task组织者,分屏逻辑)

这里重点关注分屏逻辑实现SplitScreenTaskOrganizer。

Devider类实现了DisplayController.OnDisplaysChangedListener,系统启动后回调onDisplayAdded():

// Devider.java
@Override
public void onDisplayAdded(int displayId) {mSplits.init();
}

调用了SplitScreenTaskOrganizer对象的init()方法:

class SplitScreenTaskOrganizer extends TaskOrganizer {void init() throws RemoteException {registerOrganizer(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);registerOrganizer(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);synchronized (this) {try {mPrimary = TaskOrganizer.createRootTask(Display.DEFAULT_DISPLAY,WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);mSecondary = TaskOrganizer.createRootTask(Display.DEFAULT_DISPLAY,WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);} catch (Exception e) {// teardown to prevent callbacksunregisterOrganizer();throw e;}}}
}

SplitScreenTaskOrganizer继承了TaskOrganizer,TaskOrganizer给ActivityTaskManager/WindowManager提供了管理task的接口。

/*** Interface for ActivityTaskManager/WindowManager to delegate control of tasks.*/

TaskOrganizer又被TaskOrganizerController统一管理。

/*** Stores the TaskOrganizers associated with a given windowing mode and* their associated state.*/

回过来看init()方法的实现,主要是两个方法:

  • registerOrganizer()
  • TaskOrganizer.createRootTask()

1.registerOrganizer

registerOrganizer(int windowingMode)方法接收int类型的windowingMode参数,分别传入了WINDOWING_MODE_SPLIT_SCREEN_PRIMARY和WINDOWING_MODE_SPLIT_SCREEN_SECONDARY参数,registerOrganizer()方法在父类TaskOrganizer实现,通过TaskOrganizerController对象注册。

  • WINDOWING_MODE_SPLIT_SCREEN_PRIMARY(分屏中的主屏)
  • WINDOWING_MODE_SPLIT_SCREEN_SECONDARY(分屏中的副屏)

1.1 TaskOrganizerController

// TaskOrganizerController.javaprivate final HashMap<IBinder, TaskOrganizerState> mTaskOrganizerStates = new HashMap<>();@Override
public void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) {// 添加到变量 mTaskOrganizersForWindowingModeLinkedList<IBinder> orgs = mTaskOrganizersForWindowingMode.get(windowingMode);if (orgs == null) {orgs = new LinkedList<>();mTaskOrganizersForWindowingMode.put(windowingMode, orgs);}orgs.add(organizer.asBinder());// 添加到 mTaskOrganizerStates 管理。if (!mTaskOrganizerStates.containsKey(organizer.asBinder())) {mTaskOrganizerStates.put(organizer.asBinder(),new TaskOrganizerState(organizer, uid));}// 更新task状态,通知task对象已经被organized,关联TaskOrganizer对象,task.setTaskOrganizer(ITaskOrganizer organizer)mService.mRootWindowContainer.forAllTasks((task) -> {if (task.getWindowingMode() == windowingMode) {task.updateTaskOrganizerState(true /* forceUpdate */);}});
}

TaskOrganizerController将ITaskOrganizer对象添加到mTaskOrganizerStates管理,mTaskOrganizerStates是一个HashMap,key是Binder对象,value则是内部的代理类TaskOrganizerState,当对应的task状态变化回调时再从这个HashMap取出;更新task状态,通知task对象已经被organized,关联TaskOrganizer对象,task.setTaskOrganizer(ITaskOrganizer organizer)。

1.2 Task

// Task.java
boolean updateTaskOrganizerState(boolean forceUpdate) {// 从TaskOrganizerController.mTaskOrganizersForWindowingMode获取ITaskOrganizer对象final ITaskOrganizer org =mWmService.mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode);final boolean result = setTaskOrganizer(org);mLastTaskOrganizerWindowingMode = windowingMode;return result;
}

setTaskOrganizer()方法关联ITaskOrganizer对象,更新变量mLastTaskOrganizerWindowingMode。

// Task.java
boolean setTaskOrganizer(ITaskOrganizer organizer) {mTaskOrganizer = organizer;
}@Override
boolean isOrganized() {return mTaskOrganizer != null;
}

后续task状态发生变化,通过isOrganized()方法判断是否需要回调通知mTaskOrganizer。

// ActivityStack.java
@Override
void onChildPositionChanged(WindowContainer child) {if (isOrganized()) {mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged(this, false /* force */);}
}

ActivityStack继承Task,在onChildAdded(),onChildRemoved(),positionChildAt()等改变父子层级的方法都会调用onChildPositionChanged(),如果mTaskOrganizer对象不为null,通过mTaskOrganizerController分发事件信息,接着就是mTaskOrganizerController从mTaskOrganizerStates变量中拿到对象的代理对象TaskOrganizerState回调onTaskAppeared(),onTaskVanished(),onTaskInfoChanged()等。

2.TaskOrganizer.createRootTask

TaskOrganizer.createRootTask()方法很简单,在Display.DEFAULT_DISPLAY中创建两个模式为WINDOWING_MODE_SPLIT_SCREEN_PRIMARY和WINDOWING_MODE_SPLIT_SCREEN_SECONDARY的空Task,不显示任何内容,且和其他task不同的是,其mCreatedByOrganizer为true。

如果是WINDOWING_MODE_SPLIT_SCREEN_PRIMARY模式,TaskDisplayArea还会保存其引用到mRootSplitScreenPrimaryTask变量,以便后续开启分屏时找到对应的task。

// TaskDisplayArea.javaprivate ActivityStack mRootSplitScreenPrimaryTask;void addStackReferenceIfNeeded(ActivityStack stack) {if (windowingMode == WINDOWING_MODE_PINNED) {mRootPinnedTask = stack;} else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {mRootSplitScreenPrimaryTask = stack;}
}

之后要开启分屏模式,就是将要分屏的task分别设置为其子task。

3.开启分屏

        ActivityOptions options = ActivityOptions.makeBasic();options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);options.setSplitScreenCreateMode(SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT);Bundle optsBundle = options == null ? null : options.toBundle();ActivityTaskManager.getService().startActivityFromRecents(taskId, optsBundle);

要开启分屏需要在启动时添加ActivityOptions,setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY),通过ActivityTaskManagerService.startActivityFromRecents(int taskId, Bundle bOptions)开启。

分屏逻辑大体实现就是找到对应具体要显示的task,如果没有就重新创建,然后找到一开始TaskOrganizer.createRootTask()创建的mPrimary和mSecondary,通过reparent()或者addChild()设置为对应的父子关系。

3.1主屏初始化

// ActivityTaskManagerService.java
int startActivityFromRecents(int callingPid, int callingUid, int taskId,SafeActivityOptions options) {final ActivityOptions activityOptions = options != null? options.getOptions(this): null;// 主屏task = mRootWindowContainer.anyTaskForId(taskId,MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, activityOptions, ON_TOP);// 副屏return mService.getActivityStartController().startActivityInPackage(task.mCallingUid,callingPid, callingUid, callingPackage, callingFeatureId, intent, null, null,null, 0, 0, options, userId, task, "startActivityFromRecents",false /* validateIncomingUser */, null /* originatingPendingIntent */,false /* allowBackgroundActivityStart */);
}

用户开启分屏时,主屏一般是已经存在的task,不需要重新启动,可以通过mRootWindowContainer.anyTaskForId()找到对应的task。

而副屏一般是用户通过点击图标打开的,需要通过mService.getActivityStartController().startActivityInPackage()通过ActivityStater启动。

// RootWindowContainer.java
Task anyTaskForId(int id, @RootWindowContainer.AnyTaskForIdMatchTaskMode int matchMode,@Nullable ActivityOptions aOptions, boolean onTop) {// 具体要显示的taskfinal PooledPredicate p = PooledLambda.obtainPredicate(Task::isTaskId, PooledLambda.__(Task.class), id);Task task = getTask(p);p.recycle();if (task != null) {if (aOptions != null) {// 主屏task: mode=split-screen-primaryfinal ActivityStack launchStack =getLaunchStack(null, aOptions, task, onTop);if (launchStack != null && task.getStack() != launchStack) {final int reparentMode = onTop? REPARENT_MOVE_STACK_TO_FRONT : REPARENT_LEAVE_STACK_IN_PLACE;task.reparent(launchStack, onTop, reparentMode, ANIMATE, DEFER_RESUME,"anyTaskForId");}}return task;}
}

先通过taskId找到具体要显示的task,然后通过getLaunchStack()方法找到之前创建的主屏task,第2小节中TaskDisplayArea保存的mRootSplitScreenPrimaryTask引用,将其设置为task的父task,task.reparent()。

3.2 副屏初始化

副屏初始化流程和主屏大同小异,一般需要通过ActivityStarter重新创建新task,设置副屏task为其父task。

// TaskDisplayArea.java
ActivityStack createStackUnchecked(int windowingMode, int activityType, int stackId,boolean onTop, ActivityInfo info, Intent intent, boolean createdByOrganizer) {// 找到副屏 taskTask launchRootTask = createdByOrganizer ? null : updateLaunchRootTask(windowingMode);if (launchRootTask != null) {// Since this stack will be put into a root task, its windowingMode will be inherited.windowingMode = WINDOWING_MODE_UNDEFINED;}// 创建具体要显示的taskfinal ActivityStack stack = new ActivityStack(mAtmService, stackId, activityType,info, intent, createdByOrganizer);if (launchRootTask != null) {launchRootTask.addChild(stack, onTop ? POSITION_TOP : POSITION_BOTTOM);if (onTop) {positionStackAtTop((ActivityStack) launchRootTask, false /* includingParents */);}} else {addChild(stack, onTop ? POSITION_TOP : POSITION_BOTTOM);stack.setWindowingMode(windowingMode, true /* creating */);}return stack;
}

对比一下分屏开启前后的window container结构:

  • 开启前:
    分屏开启前
  • 开启后:
    分屏开启后

4.分屏界面显示

在选中主屏task后系统就进入到了分屏界面,前面说过关于主副屏task的状态变化都会回调通知SplitScreenTaskOrganizer,包括上面的设置子task操作(reparent,addChild),回调onTaskInfoChanged(),handleTaskInfoChanged()。

// SplitScreenTaskOrganizer.java
private void handleTaskInfoChanged(RunningTaskInfo info) {final boolean primaryWasEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED;final boolean secondaryWasEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED;if (info.token.asBinder() == mPrimary.token.asBinder()) {mPrimary = info;} else if (info.token.asBinder() == mSecondary.token.asBinder()) {mSecondary = info;}final boolean primaryIsEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED;final boolean secondaryIsEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED;if (primaryIsEmpty || secondaryIsEmpty) {if (mDivider.isDividerVisible()) {mDivider.startDismissSplit();} else if (!primaryIsEmpty && primaryWasEmpty && secondaryWasEmpty) {// 显示分屏界面mDivider.startEnterSplit();}} else if (secondaryImpliesMinimize) {mDivider.ensureMinimizedSplit();} else {mDivider.ensureNormalSplit();}
}

可以看到当主屏被填满时就开始显示分屏界面了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_102946.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Qt如何生成dump文件和pdb文件并进行调试定位

在main文件中增加下面代码用于可生成dump文件 #include "widget.h" #include <QApplication> #include <QDir> #include <QDateTime> #ifdef Q_OS_WIN#include <windows.h>#include <dbghelp.h> #endifstatic LONG WINAPI exceptionC…

简单介绍一下什么是“工作内存”和“主内存”(JMM中的概念)

在学习Java多线程编程里&#xff0c; volatile 关键字保证内存可见性的要点时&#xff0c;看到网上有些资料是这么说的&#xff1a;线程修改一个变量&#xff0c;会把这个变量先从主内存读取到工作内存&#xff1b;然后修改工作内存中的值&#xff0c;最后再写回到主内存。 对…

Spring 循环依赖处理之三级缓存设计

一、思考 1、Spring是如何解决循环依赖问题的? 2、为什么要使用三级缓存?二级缓存能否解决问题? 3、提前暴露对象暴露的是什么? 4、主要源码 二、循环依赖 1、介绍 如上图&#xff0c;创建A之前需要先创建B,创建B之前需要先创建A,造成循环依赖。 由于A没创建完成&am…

一个关于Mybatis和spring的公共组件starter

utils-springboot-starter 介绍使用说明 介绍 一个关于Mybatis和spring的公共组件starter&#xff0c;目前包含以下功能&#xff1a; 接口请求日志SQL执行日志数据自动加解密数据自动脱敏服务治理方面&#xff1a; 接口限流接口熔断降级&#xff1a;CPU、内存、异常数、异常率…

win11 环境下streamlit使用pycharm debug

目录 1. pycharm中配置run 脚本2. streamlit3. 开始debug调试 1. pycharm中配置run 脚本 &#xff08;一&#xff09;点击 Edit Configurations,按图操作. 2. streamlit 1.streamlit 安装在 anaconda 的 base 环境&#xff08;随意哈&#xff0c;安装哪里都可以&#xff0c…

问题定位及解决方案

1.视频沉浸页快速滑动后&#xff0c;必现不能向下划动 复现步骤&#xff1a; 进入视频沉浸页&#xff0c;快速向下划动&#xff0c;滑动到第一页最后一个时&#xff0c;不能再向下划动。 解决步骤&#xff1a; 1.确定请求API&#xff1a; mtop.aliexpress.ugc.feed.video.lis…

PE文件反编译为python脚本流程

1、查壳 DetectltEasy、PeiD查壳 2、脱壳 常见打包工具PyInstaller&#xff0c;脱壳方法 &#xff08;1&#xff09;用pyinstxtractor.py脱壳&#xff0c;用”python pyinstxtractor.py 1.exe“命令&#xff0c;生成“.exe文件名_extracted” &#xff08;2&#xff09;用…

深度学习技巧应用8-各种数据类型的加载与处理,并输入神经网络进行训练

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下深度学习技巧应用8-各种数据类型的加载与处理&#xff0c;并输入神经网络进行训练。在模型训练中&#xff0c;大家往往对各种的数据类型比较难下手&#xff0c;对于非结构化数据已经复杂的数据的要进行特殊处理&…

LeetCode:6390. 滑动子数组的美丽值

&#x1f34e;道阻且长&#xff0c;行则将至。&#x1f353; &#x1f33b;算法&#xff0c;不如说它是一种思考方式&#x1f340; 算法专栏&#xff1a; &#x1f449;&#x1f3fb;123 一、&#x1f331;6390. 滑动子数组的美丽值 题目描述&#xff1a;给你一个长度为 n 的整…

波形生成:均匀和非均匀时间向量

波形生成—— 脉冲、chirp、VCO、正弦函数、周期性/非周期性和调制信号 使用 chirp 生成线性、二次和对数 chirp。使用 square、rectpuls 和 sawtooth 创建方波、矩形波和三角形波。 如需了解此处未显示的其他无线波形生成功能&#xff0c;请参阅无线波形发生器 (Communicat…

完整的生产车间管理流程是怎样的?六大步骤分享

阅读本文您将了解&#xff1a;1.生产车间管理的特征&#xff1b;2.生产车间管理流程具体步骤&#xff1b;3.生产车间管理流程规范的重要性。 一、生产车间管理的特征 车间管理是指对车间所从事的各项生产经营活动进行计划、组织、指挥、协调和控制的一系列管理工作。生产车间…

巧用千寻位置GNSS软件| 电力线勘测如何实现?

正如大家所知&#xff0c;电力线勘测是在做电力线路设计之前对设计线路沿途自然环境进行勘察测量&#xff0c;最后把手簿测量数据在电脑端经过转换输出为电力软件专用格式数据的专用功能。 那么在千寻位置GNSS软件中该如何操作完成电力线的勘察测量呢&#xff1f; 点击【测量】…

SwiftUI 中 TabView 如何原生使用类 UIPageView 的翻页样式?

功能需求 我们知道 TabView 是 SwiftUI 中非常好用的布局组织容器,它可以分类组织视图并依次展示给用户。 从 SwiftUI 2.0 开始(iOS 14.0+),TabView 除了常规的以标签(Tab Label)样式显示外,还可以用类似 UIPageView 的样式分页原生显示视图,显得更加简洁: 如上图所…

AWT-对话框——Dialog以及其子类FileDialog

Dialog: Dialog时Window类的子类&#xff0c;时一个容器类&#xff0c;属于特殊组件。对话框是可以独立存在的顶级窗口&#xff0c;因此用法与普通窗口的用法几乎完全一样&#xff0c;但是使用对话框需要注意以下几点&#xff1a; 对话框通常依赖于其它窗口&#xff0c;就是通…

基于Java+SpringBoot+vue学生学习平台详细设计实现

基于JavaSpringBootvue学生学习平台详细设计实现 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源码联系方式 文章目…

AI Stable Diffusion Prompt参数【二】之 生成效果查验

AI Stable Diffusion Prompt参数【二】之 生成效果查验 效果国漫风生成参数配置prompt&#xff1a;Negative prompt:Model:Steps:Sampler:CFG scale:Clip skip:Model hash:Hires upscale:Hires upscaler:Denoising strength: 全部效果 效果 国漫风生成参数配置 prompt&#xf…

拉格朗日粒子扩散模式FLEXPART

为了高效、精准地治理区域大气污染&#xff0c;需要弄清污染物的来源。拉格朗日粒子扩散模式FLEXPART通过计算点、线、面或体积源释放的大量粒子的轨迹&#xff0c;来描述示踪物在大气中长距离、中尺度的传输、扩散、干湿沉降和辐射衰减等过程。该模式既可以通过时间的前向运算…

汇编与内联 x86-64

机器字长 x86是32位系统 64是64位系统 这里的32和64&#xff0c;指的都是机器字长 机器字长是 能直接进行整数/位运算的大小指针的大小(索引内存的范围) 容易与机器字长混淆的概念&#xff1a;字 字 字存储字长 字是MDR寄存器的位数&#xff0c;代表每个主存存储体中的存储…

[HDU - 4578]Transformation(线段树+多重懒标记)

[HDU - 4578]Transformation&#xff08;线段树多重懒标记&#xff09; 一、问题二、分析1、节点定义2、pushup3、pushdown&#xff08;1&#xff09;每种标记如何下传&#xff1f;赋值乘法加法 &#xff08;2&#xff09;三种标记下传的优先级问题 三、代码 一、问题 二、分析…

C++数据结构:手撕AVL树

目录 一. 什么是AVL树 二. AVL树的节点定义 三. AVL树的插入操作 3.1 寻找插入位置 3.2 更新平衡因子 3.3 AVL树的旋转调整 3.4 AVL树插入操作的整体实现 四. AVL树的检验 附录&#xff1a;AVL树的实现完整代码 AVL树定义代码 -- AVLTree.h AVL树检验代码 -- test.…