Android T多屏多显——应用双屏间拖拽移动功能(更新中)

功能以及显示效果简介

需求:在双屏显示中,把启动的应用从其中一个屏幕中移动到另一个屏幕中。
操作:通过双指按压应用使其移动,如果移动的距离过小,我们就不移动到另一屏幕,否则移动到另一屏。
请添加图片描述

功能分析

多屏中移动应用至另一屏本质就是Task的移动。
从窗口层级结构的角度来说,就是把Display1中的DefaultTaskDisplayArea上的Task,移动到Display2中的DefaultTaskDisplayArea上。
容器结构简化树状图如下所示:
在这里插入图片描述

窗口层级结构简化树状图如下所示:
在这里插入图片描述

关键代码知识点

移动Task至另一屏幕

代码路径:frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java

    /**
     * Move root task with all its existing content to specified display.
     *
     * @param rootTaskId Id of root task to move.
     * @param displayId  Id of display to move root task to.
     * @param onTop      Indicates whether container should be place on top or on bottom.
     */
    void moveRootTaskToDisplay(int rootTaskId, int displayId, boolean onTop) {
        //根据displayId获取DisplayContent
        final DisplayContent displayContent = getDisplayContentOrCreate(displayId);
        if (displayContent == null) {
            throw new IllegalArgumentException("moveRootTaskToDisplay: Unknown displayId="
                    + displayId);
        }
        //调用moveRootTaskToTaskDisplayArea方法
        moveRootTaskToTaskDisplayArea(rootTaskId, displayContent.getDefaultTaskDisplayArea(),
                onTop);
    }
    

入参说明:
rootTaskId需要移动的Task的Id。可以通过Task中getRootTaskId()方法获取。
displayId需要移动到对应屏幕的Display的Id。可以通过DisplayContent中的getDisplayId()方法获取。
onTop移动后的Task是放在容器顶部还是底部。true表示顶部,false表示底部。
代码解释:
这个方法首先通过getDisplayContentOrCreate方法根据displayId获取DisplayContent,然后调用moveRootTaskToTaskDisplayArea方法进行移动。
其中传递参数displayContent.getDefaultTaskDisplayArea(),表示获取DisplayContent下面的DefaultTaskDisplayArea。

    /**
     * Move root task with all its existing content to specified task display area.
     *
     * @param rootTaskId      Id of root task to move.
     * @param taskDisplayArea The task display area to move root task to.
     * @param onTop           Indicates whether container should be place on top or on bottom.
     */
    void moveRootTaskToTaskDisplayArea(int rootTaskId, TaskDisplayArea taskDisplayArea,
            boolean onTop) {
        //获取Task
        final Task rootTask = getRootTask(rootTaskId);
        if (rootTask == null) {
            throw new IllegalArgumentException("moveRootTaskToTaskDisplayArea: Unknown rootTaskId="
                    + rootTaskId);
        }

        final TaskDisplayArea currentTaskDisplayArea = rootTask.getDisplayArea();
        if (currentTaskDisplayArea == null) {
            throw new IllegalStateException("moveRootTaskToTaskDisplayArea: rootTask=" + rootTask
                    + " is not attached to any task display area.");
        }

        if (taskDisplayArea == null) {
            throw new IllegalArgumentException(
                    "moveRootTaskToTaskDisplayArea: Unknown taskDisplayArea=" + taskDisplayArea);
        }

        if (currentTaskDisplayArea == taskDisplayArea) {
            throw new IllegalArgumentException("Trying to move rootTask=" + rootTask
                    + " to its current taskDisplayArea=" + taskDisplayArea);
        }
        //把获取到的task重新挂载到了新display的taskDisplayArea
        rootTask.reparent(taskDisplayArea, onTop);

        // Resume focusable root task after reparenting to another display area.
        //窗口或任务reparent之后,恢复焦点,激活相关任务的活动,并更新活动的可见性,以确保窗口管理器和用户界面的状态一致和正确。
        rootTask.resumeNextFocusAfterReparent();

        // TODO(multi-display): resize rootTasks properly if moved from split-screen.
    }

根据前面传递的TaskId获取到Task,在通过rootTask.reparent(taskDisplayArea, onTop);方法,把这个Task重新挂载到了新display的taskDisplayArea上。然后使用rootTask.resumeNextFocusAfterReparent();方法更新窗口焦点显示。

  • rootTask.reparent(taskDisplayArea, onTop);
    代码路径:frameworks/base/services/core/java/com/android/server/wm/Task.java

     void reparent(TaskDisplayArea newParent, boolean onTop) {
            if (newParent == null) {
                throw new IllegalArgumentException("Task can't reparent to null " + this);
            }
    
            if (getParent() == newParent) {
                throw new IllegalArgumentException("Task=" + this + " already child of " + newParent);
            }
    
            //通过调用 canBeLaunchedOnDisplay 方法检查任务是否可以在新父区域所在的显示设备上启动。
            if (canBeLaunchedOnDisplay(newParent.getDisplayId())) {
                //实际执行reparent的操作。
                reparent(newParent, onTop ? POSITION_TOP : POSITION_BOTTOM);
                //如果Task是一个叶子Task(即没有子Task的Task)
                if (isLeafTask()) {
                    //调用新父区域的 onLeafTaskMoved 方法来通知新父区域叶子Task已经移动。
                    newParent.onLeafTaskMoved(this, onTop);
                }
            } else {
                Slog.w(TAG, "Task=" + this + " can't reparent to " + newParent);
            }
        }
    

    其中reparent(newParent, onTop ? POSITION_TOP : POSITION_BOTTOM);实际执行reparent的操作。这里根据 onTop 的值来决定任务应该被放置在新父区域的顶部还是底部。我们再看看这方法的具体实现。
    代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java

    void reparent(WindowContainer newParent, int position) {
            if (newParent == null) {
                throw new IllegalArgumentException("reparent: can't reparent to null " + this);
            }
    
            if (newParent == this) {
                throw new IllegalArgumentException("Can not reparent to itself " + this);
            }
    
            final WindowContainer oldParent = mParent;
            if (mParent == newParent) {
                throw new IllegalArgumentException("WC=" + this + " already child of " + mParent);
            }
    
            // Collect before removing child from old parent, because the old parent may be removed if
            // this is the last child in it.
            //记录reparent的容器(this)相关信息,这里的this指的是移动的Task,newParent是新的TaskDisplayArea
            mTransitionController.collectReparentChange(this, newParent);
    
            // The display object before reparenting as that might lead to old parent getting removed
            // from the display if it no longer has any child.
            //获取之前的DisplayContent和新的DisplayContent
            final DisplayContent prevDc = oldParent.getDisplayContent();
            final DisplayContent dc = newParent.getDisplayContent();
    
            //设置 mReparenting 为 true,表示正在执行reparent操作。
            //然后从旧父容器中移除当前容器,并将其添加到新父容器的指定位置。
            //最后,将 mReparenting 设置为 false,表示reparent操作完成。
            mReparenting = true;
            oldParent.removeChild(this);
            newParent.addChild(this, position);
            mReparenting = false;
    
            // Relayout display(s)
            //标记新父容器对应的显示内容为需要布局。
            //如果新父容器和旧父容器的显示内容不同,
            //则触发显示内容改变的通知,并标记旧显示内容也需要布局。
            //最后,调用layoutAndAssignWindowLayersIfNeeded方法确保显示内容按需进行布局和窗口层级的分配。
            dc.setLayoutNeeded();
            if (prevDc != dc) {
                onDisplayChanged(dc);
                prevDc.setLayoutNeeded();
            }
            getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
    
            // Send onParentChanged notification here is we disabled sending it in setParent for
            // reparenting case.
            //处理窗口容器在父容器变更时的各种逻辑
            onParentChanged(newParent, oldParent);
            //处理窗口容器在不同父容器之间同步迁移的逻辑
            onSyncReparent(oldParent, newParent);
        }
    
  • rootTask.resumeNextFocusAfterReparent();
    代码路径:frameworks/base/services/core/java/com/android/server/wm/Task.java

        void resumeNextFocusAfterReparent() {
            //调整焦点
            adjustFocusToNextFocusableTask("reparent", true /* allowFocusSelf */,
                    true /* moveDisplayToTop */);
            //恢复当前焦点任务的顶部活动
            mRootWindowContainer.resumeFocusedTasksTopActivities();
            // Update visibility of activities before notifying WM. This way it won't try to resize
            // windows that are no longer visible.
            //更新activities的可见性
            mRootWindowContainer.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
                    !PRESERVE_WINDOWS);
        }
    

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

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

相关文章

基于Python的微博舆论分析,微博评论情感分析可视化系统

博主介绍:✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇…

ARM/X86+FPGA轨道交通/工程车辆行业的解决方案

深圳推出首条无人驾驶地铁—深圳地铁20号线,可以说是深圳地铁的一次开创性的突破。智能交通不断突破的背后,需要很严格的硬件软件等控制系 统;地铁无人驾驶意味着信号系统、通信系统、综合监控系统、站台屏蔽门工程等项目必须严格执行验收。…

上位机图像处理和嵌入式模块部署(用树莓派4b开发固件)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 嵌入式开发的领域其实很广,有上位机、驱动和bsp移植。较早之前,由于自己曾经在芯片公司上班,所以对uboot、linu…

ARM汇编指令、指令中s后缀的作用、N、Z、C、V位有什么作用、ARM汇编启动代码

ARM汇编指令 学习arm汇编的主要目的是为了编写arm启动代码,启动代码启动以后,引导程序到c语言环境下允许。换句话说启动代码的目的是为了在处理器复位以后搭建c语言最基本的需求。因此启动代码的主要任务有: 初始化异常向量表; 初始化各工作模式的栈指针寄存器; 开启arm…

如何通过Linux pciehp sysfs接口控制PCIe Slot电源状态?-2

NVME SSD电源状态判断 通过pciehp sysfs接口对NVMe SSD所在的PCIe插槽进行Power On/Off操作时,确实会间接影响到NVMe SSD本身的电源状态。因为NVMe SSD是作为PCIe设备连接到特定插槽上的,插槽电源状态的变化通常会直接影响到与其相连的设备。 当对PCIe…

网络篇12 | 链路层 ARP

网络篇12 | 链路层 ARP 01 简介1)工作过程2)ARP缓存2.1 动态ARP表项2.2 静态ARP表项2.3 短静态ARP表项2.4 长静态ARP表项 02 ARP报文格式1)ARP请求报文格式2)ARP响应报文格式3)套一层以太网帧(ARP帧&#x…

史上最全excel导入功能测试用例设计(以项目为例)

web系统关于excel的导入导出功能是很常见的,通常为了提高用户的工作效率,在维护系统中的一些数据的时候,批量导入往往比一个一个添加或者修改快很多。针对导入功能的测试,往往会有很多种情况,现在针对平时项目中遇到的…

灰度部署、滚动部署与蓝绿部署

前言 最近在进行单元化建设方面的的工作,其中涉及服务分组和蓝绿发布相关的概念,在这里总结一下了解到的相关知识。 版本更新策略 功能开关 在应用逻辑里内置功能开关,通过开关的打开关闭来决定执行新旧逻辑,无需路由机制支持…

使用DockerCompose配置基于哨兵模式的redis主从架构集群

文章目录 一、注意事项(坑点!!!)二、配置Redis主从架构集群第一步:创建目录文件结构第二步:编写DockerCompose配置文件第三步:编写redis.conf第四步:启动redis主从集群 三…

如何在树莓派安装Nginx并实现固定公网域名访问本地静态站点

文章目录 1. Nginx安装2. 安装cpolar3.配置域名访问Nginx4. 固定域名访问5. 配置静态站点 安装 Nginx(发音为“engine-x”)可以将您的树莓派变成一个强大的 Web 服务器,可以用于托管网站或 Web 应用程序。相比其他 Web 服务器,Ngi…

古月·ROS2入门21讲——学习笔记(一)核心概念部分1-14讲

讲解视频地址:1.ROS和ROS2是什么_哔哩哔哩_bilibili 笔记分为上篇核心概念部分和下篇常用工具部分 下篇:古月ROS2入门21讲——学习笔记(二)常用工具部分15-21讲-CSDN博客 目录 第一讲:ROS/ROS2是什么 1. ROS的诞生…

Redis入门到通关之Set实现点赞功能

文章目录 set 数据类型介绍不排序实现排序实现 set 数据类型介绍 Redis中的set类型是一组无序的字符串值。 set通过其独特的数据结构和丰富的命令提供了在存储和处理集合元素方面的一些非常有用的功能。下面列出了主要的set类型命令: SADD key member1 [member2]&a…

Python框架之UnitTest

unittest 是python 的单元测试框架,unittest 单元测试提供了创建测试用例,测试套件以及批量执行的方案, unittest 在安装pyhton 以后就直接自带了,直接import unittest 就可以使用,测试人员用UnitTest来做自动化测试&a…

基于SpringBoot + Vue实现的校车调度管理系统设计与实现+毕业论文(12000字)+答辩PPT​(包运行成功)

介绍 本系统包含管理员、驾驶员两个个角色。 管理员角色:驾驶员信息管理、车辆信息管理、借调车辆管理、工作管理、车辆运营管理、报销申请审核。 驾驶员角色:个人信息查看、工作查看、借调车辆申请、车辆使用申请、报销申请提交。 运行环境 jdk1.8 id…

前端开发攻略---在输入框中输入中文但是还没选中的时候,搜索事件依然存在;中文输入法导致的高频事件。

1、演示 解决前 解决后 2、输入框事件介绍 compositionstart事件在用户开始使用输入法输入时触发。这意味着用户正在进行组合输入,比如在中文输入法中,用户可能正在输入一个多个字符的词语。在这个阶段,输入框的内容可能还没有完全确定&#…

消费增值新模式:让每一分钱都更有价值

亲爱的消费者们,大家好!今天我想和大家探讨一种新颖的消费方式——消费增值,它让您的每一次消费都蕴含了额外的价值,让消费变得更加有意义。 在过往的消费观念里,我们往往只是简单地将钱花出去,购买所需的商…

医院一站式后勤管理系统 processApkUpload.upload 任意文件上传漏洞复现

0x01 产品简介 医院一站式后勤管理系统由南京博纳睿通软件科技有限公司开发的一款基于现代医院后勤管理理念的业务系统,产品结合后勤业务管理特点,通过管理平台将后勤管理业务予以系统化、规范化和流程化,从而形成一套构建于平台之上且成熟完善的后勤管理体系,并可在此体系…

mysql的下载、安装

首先进入官网:MySQL 点击“downloads”进入下载界面 2.往下滑动滚轮,点击“mysql community...(公开版)” 3.往下滑,找到并单击“install for Windows” 4.选择版本:初学者可以使用较低版本,较…