Android分屏流程分析

news2024/11/24 7:27:50

本文基于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 callbacks
                unregisterOrganizer();
                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.java

private final HashMap<IBinder, TaskOrganizerState> mTaskOrganizerStates = new HashMap<>();

@Override
public void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) {
    
    // 添加到变量 mTaskOrganizersForWindowingMode
    LinkedList<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.java

private 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) {
    
    // 具体要显示的task
    final 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-primary
            final 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) {
    
    // 找到副屏 task
    Task 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;
    }
    
    // 创建具体要显示的task
    final 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.coloradmin.cn/o/457074.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

有道CEO周枫:当我们谈论大模型时,应该关注哪些新能力?

作者&#xff1a;周枫 基于大语言模型技术的ChatGPT推出已经有4个月了&#xff0c;更多同类产品还在快速出现。比如&#xff0c;前天谷歌更新了Bard&#xff0c;将辅助编程能力支持的语言数量扩展到20种。 然而&#xff0c;对大模型技术的重要性也出现了质疑&#xff0c;前段…

开发框架之Furion

目录 概述 框架特点 功能模块 支持平台 运行环境 数据库 应用部署 Nuget框架扩展包 Nuget框架脚手架 FurionEFCore脚手架 FurionEFCore脚手架安装命令 FurionSqlSugar脚手架 FurionSqlSugar脚手架安装命令 使用脚手架 脚手架更新 概述 Furion是一个免费开源的.Ne…

【AI帮我写代码,上班摸鱼不是梦】调教ChatGPT过程全记录,让它帮我写程序!

最近发现磁盘空间严重不足&#xff0c;都弹窗提示我了&#xff1a; 想想看到底哪个文件夹占的空间比较大&#xff0c;好做针对性的删除和清理。奈何Windows系统没有查看文件夹大小的工具&#xff0c;只能鼠标放在某个文件夹上&#xff0c;等提示&#xff1a; AI时代都来临了&am…

PCL 点云变换

文章目录 一、原理简述1、旋转矩阵2、欧氏变换二、主要函数及代码实现1、主要函数2、完整代码3、效果实现参考文献:一、原理简述 两片点云的刚体变换包含旋转和平移,变换矩阵的含义如下: 1、旋转矩阵 绕 x x

Promise异步编程

目录 一、Promise的含义 二、基本用法 三、reject的用法 四、执行顺序 五、 项目中使用promise获取后端数据 六、catch的用法 七、finally的用法 八、Promise.all() 九、Promise.all()有一个是失败 十、Promise.race()全部是成功 十一、Promise.race()有一个是失败 一、…

Java——二叉树中和为某一值的路径(二)

题目链接 牛客网在线oj题——二叉树中和为某一值的路径&#xff08;二&#xff09; 题目描述 输入一颗二叉树的根节点root和一个整数expectNumber&#xff0c;找出二叉树中结点值的和为expectNumber的所有路径。 1.该题路径定义为从树的根结点开始往下一直到叶子结点所经过…

图的相关知识总结

目录 图的概念图的存储结构邻接矩阵邻接表 图的遍历最小生成树Kruskal算法prim算法 最短路径问题单源最短路径--Dijkstra算法-单源最短路径--Bellman-Ford算法多源最短路径--Floyd-Warshall算法 图的概念 图是有顶点集合以及顶点间的关系组成的一种数据结构:G(V,E),其中顶点集…

JS-11A/224时间继电器 JOSEF约瑟 板前、板后接线

系列型号&#xff1a; JS-11A/11集成电路时间继电器&#xff1b;JS-11A/12集成电路时间继电器&#xff1b; JS-11A/13集成电路时间继电器&#xff1b;JS-11A/136集成电路时间继电器&#xff1b; JS-11A/137集成电路时间继电器&#xff1b;JS-11A/22集成电路时间继电器&#…

Java基础(十二)Java比较器

1 Java 比较器 我们知道基本数据类型的数据&#xff08;除boolean类型外&#xff09;需要比较大小的话&#xff0c;直接使用比较运算符即可&#xff0c;但是引用数据类型是不能直接使用比较运算符来比较大小的。那么&#xff0c;如何解决这个问题呢&#xff1f; 在Java中经常…

Jomalone(“独狼”)的Rootkit后门dll分析

从England.sys&#xff08;md5为B5F7DE342B1D661E57BCD14615CADEFA&#xff09;驱动文件中提取了4个dll文件&#xff0c;其中两个64位dll&#xff0c;两个32位dll&#xff0c;主要用于APC注入 样本的基本信息 文件名称: 0x3df60-0x15e00.dll 文件大小: 87.5 KB (89,600 字节) …

Kubeadm方式搭建K8s集群【1.23.0版本】

文章目录 一、初始化准备二、安装kubeadm三、初始化Master集群四、将新的Node节点加入集群五、部署CNI网络插件六、其他配置 Kubernetes1.24(包括1.24)之后不在兼容docker,如果有需要兼容docker的需求&#xff0c;则安装一个 cri-docker的插件&#xff0c;本文使用的是kuberne…

【SWAT水文模型】ArcSWAT输入准备

ArcSWAT输入准备 1 必需的ArcSWAT空间数据集1.1 数字高程模型&#xff08;DEM&#xff09;1.2 土地覆盖/土地利用类型1.3 土壤数据 2 可选的ArcSWAT空间数据集2.1 DEM Mask2.2 Streams2.3 User- Defined Watersheds 3 ArcSWAT表格和文本文件3.1 子流域出口位置表(dBase 表)3.2 …

【MCAL_Uart】-1.1-图文详解Uart串口协议

目录 1 什么是UART 2 UART的电平 3 UART的波特率 4 UART帧格式 4.1 start bit起始位 4.2 data bit数据位 4.3 parity bit奇偶校验位 4.4 stop bit停止位 5 什么是8-N-1 6 UART总线负载率计算 结尾 优质博文推荐阅读&#xff08;单击下方链接&#xff0c;即可跳转&am…

shell脚本基础之详解脚本的控制

详解脚本的控制 信号的处理Linux信号生成信号中断进程暂停进程 捕获信号捕获脚本退出修改或移除捕获 以后台模式运行脚本后台运行脚本运行多个后台脚本 在非控制台下运行脚本作业控制 查看作业重启停止的作业调整谦让度nice命令renice命令 定时运行作业用 at 命令来定时执行作业…

2023移动云大会即将召开,划重点来了

今年由 ChatGPT 引起的AI浪潮下&#xff0c;人们热议其背后的算力问题&#xff0c;毋庸置疑&#xff0c;算力已成为和水电同样重要的资源。作为开发者&#xff0c;如何了解云计算领域有哪些最新发展趋势&#xff1f; 4月25-26日&#xff0c;以“云擎未来&#xff0c;智信天下”…

Scala之集合(3)

目录 WordCount案例&#xff1a; 需求分析与步骤&#xff1a; 拆分&#xff1a; 聚合&#xff1a; 格式转化&#xff1a; 方法1&#xff1a; 方法2&#xff1a; 排序&#xff1a; 方法1&#xff1a; 方法2&#xff1a; 取top3&#xff1a; 整体化简后的代码&#xf…

轻量级服务器nginx:配置虚拟主机的两种方式

虚拟主机是指&#xff0c;在一台服务器中&#xff0c;通过nginx的代理&#xff0c;我们可以访问多个网站。区分不同的网站&#xff0c;可以通过端口、域名两种方式 这里写目录标题 一 端口不同区分不同的虚拟主机二 通过域名区分不同的主机名1.配置域名映射2.显示登录效果 一 …

基于ATECLOUD电源模块及单板性能自动化测试方案

一、背景介绍 客户使用直流电源、交流电源、直流负载、示波器、数据记录仪、功率分析仪、CAN卡、工控机等仪器对电源模块及单板进行功能和性能方面的测试&#xff0c;目前想要能够通过硬件自动化测试分析系统搭配对应仪器实现自动化测试&#xff0c;提升测试效率。 二、用户痛…

Linux网络——NFS共享服务

Linux网络——NFS共享服务 一、NFS共享服务1.NFS网络文件系统2.NFS 架构3.NFS 工作原理4.NFS相关配置文件及其配置作用 二、搭建NFS服务1.服务器安装 nfs-utils、rpcbind 软件包2.服务器启动nfs-utils、rpcbind服务&#xff0c;并设置共享目录3.服务端更改NFS配置&#xff0c;对…

scrapy实践-02

双师demo ptpress.com.cn/shopping/index 解析每一首歌 <ul class"f-hide"><li><a href"/song?id2037945324">芯房</a></li><li><a href"/song?id2037926385">知足</a></li><li>…