车机CarLauncher的Activity多屏模式WindowingMode为WINDOWING_MODE_MULTI_WINDOW疑问解析

news2024/9/20 5:54:14

hi,粉丝朋友们!

   @IntDef(prefix = { "WINDOWING_MODE_" }, value = {
            WINDOWING_MODE_UNDEFINED,
            WINDOWING_MODE_FULLSCREEN,
            WINDOWING_MODE_MULTI_WINDOW,
            WINDOWING_MODE_PINNED,
            WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
            WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
            WINDOWING_MODE_FREEFORM,
    })

今天来给大家介绍一下再Activity中WindowMode相关的多窗口模式,这个模式相对平时比较少见,但是在分屏模式之自由窗口模式,等存在多个窗口场景就很关键了。这一部分确实不是普通模式的场景,而且每一种模式都是比较难的,今天来分析一下WINDOWING_MODE_MULTI_WINDOW模式的一个疑问。
更多framework核心专题参考内容
https://ke.qq.com/course/package/83580?tuin=7d4eb354

疑问背景:

activity的WindowMode被设置成了WINDOWING_MODE_MULTI_WINDOW后,或者其他几个非WINDOWING_MODE_FULLSCREEN的WindowMode,为啥可以同一个画面显示多个Activity,而且多个Activity都是Resume的状态。
比如拿我们CarLauncher的地图画面来说:
在这里插入图片描述

具体可以看对应的am stack list看看Task和Activity相关的情况

test@test:~$ adb shell am stack list
RootTask id=1000098 bounds=[303,57][1200,658] displayId=0 userId=10
 configuration={1.0 310mcc260mnc [en_US] ldltr sw1066dp w1196dp h801dp 120dpi xlrg land car finger qwerty/v/v -nav/h winConfig={ mBounds=Rect(303, 57 - 1200, 658) mAppBounds=Rect(303, 57 - 1200, 658) mMaxBounds=Rect(0, 0 - 1200, 800) mDisplayRotation=ROTATION_0 mWindowingMode=multi-window mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_0} as.2 s.29 fontWeightAdjustment=0}
  taskId=1000098: com.android.car.mapsplaceholder/com.android.car.mapsplaceholder.MapsPlaceholderActivity bounds=[303,57][1200,658] userId=10 visible=true topActivity=ComponentInfo{com.android.car.mapsplaceholder/com.android.car.mapsplaceholder.MapsPlaceholderActivity}

RootTask id=1 bounds=[0,0][1200,800] displayId=0 userId=10
 configuration={1.0 310mcc260mnc [en_US] ldltr sw1066dp w1600dp h1042dp 120dpi xlrg land car finger qwerty/v/v -nav/h winConfig={ mBounds=Rect(0, 0 - 1200, 800) mAppBounds=Rect(0, 0 - 1200, 800) mMaxBounds=Rect(0, 0 - 1200, 800) mDisplayRotation=ROTATION_0 mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=home mAlwaysOnTop=undefined mRotation=ROTATION_0} as.2 s.29 fontWeightAdjustment=0}
  taskId=2: com.android.car.settings/com.android.car.settings.FallbackHome bounds=[0,0][1200,800] userId=0 visible=true topActivity=ComponentInfo{com.android.car.carlauncher/com.android.car.carlauncher.CarLauncher}
  taskId=1000095: com.android.car.carlauncher/com.android.car.carlauncher.CarLauncher bounds=[0,0][1200,800] userId=10 visible=true topActivity=ComponentInfo{com.android.car.carlauncher/com.android.car.carlauncher.CarLauncher}


可以看到其实我们桌面是CarLauncher这个Activity,它是覆盖全屏幕,但是发现居然有个MapsPlaceholderActivity的Activity还在它的上面,显示区域Rect(303, 57 - 1200, 658)就是图片绿色部分,属于地图放置部分

通过dumpsys activity activities可以看出MapsPlaceholderActivity ,CarLauncher两个Activity都属于state=RESUMED状态

大家如果正常理解是不是惊呆了?为啥有两个Activity都是Resumed,正常启动新的Activity时候是不是之前Activity就要Paused,那么这个是在框架怎么实现的不会对之前的Activity进行Paused

解答:

需要解答该问题,其实可以从Activity的是怎么被pause地方进行入手
以前讲解过进入pasue主要pauseBackTasks方法进行

   boolean pauseBackTasks(ActivityRecord resuming) {
      //省略

            leafTask.forAllLeafTaskFragments((taskFrag) -> {
                final ActivityRecord resumedActivity = taskFrag.getResumedActivity();
            
                if (resumedActivity != null && !taskFrag.canBeResumed(resuming)) {
                    if (taskFrag.startPausing(false /* uiSleeping*/, resuming, "pauseBackTasks")) {
                        someActivityPaused[0]++;
                    }
                }
            }, true /* traverseTopToBottom */);
        }, true /* traverseTopToBottom */);
        return someActivityPaused[0] > 0;
    }

在这里插入图片描述

具体可以看下面堆栈:

05-16 22:55:01.987   633  1162 I WindowManager: java.lang.Exception
05-16 22:55:01.987   633  1162 I WindowManager: 	at com.android.server.wm.TaskFragment.resumeTopActivity(TaskFragment.java:1205)
05-16 22:55:01.987   633  1162 I WindowManager: 	at com.android.server.wm.Task.resumeTopActivityInnerLocked(Task.java:5003)
05-16 22:55:01.987   633  1162 I WindowManager: 	at com.android.server.wm.Task.resumeTopActivityUncheckedLocked(Task.java:4938)
05-16 22:55:01.987   633  1162 I WindowManager: 	at com.android.server.wm.RootWindowContainer.resumeFocusedTasksTopActivities(RootWindowContainer.java:2260)
05-16 22:55:01.987   633  1162 I WindowManager: 	at com.android.server.wm.ActivityStarter.startActivityInner(ActivityStarter.java:1935)
05-16 22:55:01.987   633  1162 I WindowManager: 	at com.android.server.wm.ActivityStarter.startActivityUnchecked(ActivityStarter.java:1661)
05-16 22:55:01.987   633  1162 I WindowManager: 	at com.android.server.wm.ActivityStarter.executeRequest(ActivityStarter.java:1216)
05-16 22:55:01.987   633  1162 I WindowManager: 	at com.android.server.wm.ActivityStarter.execute(ActivityStarter.java:702)
05-16 22:55:01.987   633  1162 I WindowManager: 	at com.android.server.wm.ActivityStartController.startActivityInPackage(ActivityStartController.java:283)
05-16 22:55:01.987   633  1162 I WindowManager: 	at com.android.server.wm.ActivityTaskManagerService$LocalService.startActivityInPackage(ActivityTaskManagerService.java:5445)
05-16 22:55:01.987   633  1162 I WindowManager: 	at com.android.server.am.PendingIntentRecord.sendInner(PendingIntentRecord.java:483)
05-16 22:55:01.987   633  1162 I WindowManager: 	at com.android.server.am.PendingIntentRecord.sendWithResult(PendingIntentRecord.java:309)
05-16 22:55:01.987   633  1162 I WindowManager: 	at com.android.server.am.ActivityManagerService.sendIntentSender(ActivityManagerService.java:5384)
05-16 22:55:01.987   633  1162 I WindowManager: 	at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:4402)
05-16 22:55:01.987   633  1162 I WindowManager: 	at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:2638)
05-16 22:55:01.987   633  1162 I WindowManager: 	at android.os.Binder.execTransactInternal(Binder.java:1280)
05-16 22:55:01.987   633  1162 I WindowManager: 	at android.os.Binder.execTransact(Binder.java:1244)

如果是正常模式肯定就会进入Pasue,但是WINDOWING_MODE_MULTI_WINDOW不会进入,这个是为啥?那就要具体分析怎么才可以进入这个taskFrag.startPausing的条件:

  if (resumedActivity != null && !taskFrag.canBeResumed(resuming)) {
                    if (taskFrag.startPausing(false /* uiSleeping*/, resuming, "pauseBackTasks")) {
                        someActivityPaused[0]++;
                    }
                }

这个地方有resumedActivity这个一般就是Carauncher,
resuming就是我们的MapsPlaceholderActivity,
taskFrag就是Carauncher的Task

taskFrag.canBeResumed代表就是这个taskFrag是否可以处于Resumed状态
如果为true则不可以pasue之前activity,如果为false就是要pause前面Activity(CarLauncher)

   boolean canBeResumed(@Nullable ActivityRecord starting) {
        // No need to resume activity in TaskFragment that is not visible.
        return isTopActivityFocusable()
                && getVisibility(starting) == TASK_FRAGMENT_VISIBILITY_VISIBLE;
    }

canBeResumed的判断又是对Task的isTopActivityFocusable()和getVisibility(starting)进行判断,这里isTopActivityFocusable一般都为true,getVisibility是关键,也就是再不进行pause
下面重点看getVisibility实现:

int getVisibility(ActivityRecord starting) {
        //省略部分
        if (parent.asTaskFragment() != null) {
           //省略部分
        final List<TaskFragment> adjacentTaskFragments = new ArrayList<>();
        for (int i = parent.getChildCount() - 1; i >= 0; --i) {
            final WindowContainer other = parent.getChildAt(i);//遍历TaskDisplayarea所有子节点,赋值other
            if (other == null) continue;

            final boolean hasRunningActivities = hasRunningActivity(other);
            if (other == this) {//这里条件是要other和this相等才可以
               //省略部分
                // Should be visible if there is no other fragment occluding it, unless it doesn't
                // have any running activities, not starting one and not home stack.
                //这个地方本身有CarLauncher这个hasRunningActivities满足为true,所以shouldBeVisible为true
                shouldBeVisible = hasRunningActivities
                        || (starting != null && starting.isDescendantOf(this))
                        || isActivityTypeHome();
                break;
            }
			//但是CarLauncher的Task在MapsPlaceholderActivity的底部,所以先遍历到MapsPlaceholderActivity的Task
            final int otherWindowingMode = other.getWindowingMode();
            //关键判断MapsPlaceholderActivity的task的WindowingMode是否属于WINDOWING_MODE_FULLSCREEN,如果属于这里就会return INVISIBLE,这里也就是之前会导致pause的关键,但是因为我们设置成了WINDOWING_MODE_MULTI_WINDOW,所以无法进入这个里面
            if (otherWindowingMode == WINDOWING_MODE_FULLSCREEN) {
                if (isTranslucent(other, starting)) {
                    // Can be visible behind a translucent fullscreen TaskFragment.
                    gotTranslucentFullscreen = true;
                    continue;
                }
                return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
            } else if (otherWindowingMode == WINDOWING_MODE_MULTI_WINDOW
                    && other.matchParentBounds()) {
                    //这里虽然判断了WINDOWING_MODE_MULTI_WINDOW,但是因为MapsPlaceholderActivity的bound区域不是全屏,所以也不会进入这里进行INVISIBLE
                if (isTranslucent(other, starting)) {
                    // Can be visible behind a translucent TaskFragment.
                    gotTranslucentFullscreen = true;
                    continue;
                }
                // Multi-window TaskFragment that matches parent bounds would occlude other children
                return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
            }
           //省略

        if (!shouldBeVisible) {
            return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
        }

        // Lastly - check if there is a translucent fullscreen TaskFragment on top.
        return gotTranslucentFullscreen
                ? TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT
                : TASK_FRAGMENT_VISIBILITY_VISIBLE;
    }

代码较多较复杂,看一两次很难看懂的总结一下:
Visible变量影响关键
1、遍历当前的所有Task,根据先后顺序,一般先遍历的Task肯定是最先的MapsPlaceholderActivity的Task
2、导致会进行Pause关键是如果MapsPlaceholderActivity的Task是WINDOWING_MODE_FULLSCREEN或WINDOWING_MODE_MULTI_WINDOW但是一定要可以覆盖全部下面的Task,说白了就是如果上面Task会把自己覆盖那么就一定会导致INVISIBLE

3、一旦不会覆盖,那么自己Task有正在执行Activity,那么就会返回VISIBLE

具体图如下:

在这里插入图片描述
在这里插入图片描述

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

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

相关文章

nacos注册中心源码分析一之服务注册、服务心跳

源码分析 nacos客户端注册分析 依赖包 <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency>Nacos的客户端是基于SpringBoot的自动装配实现的 看下依…

算法性能分析

一、时间复杂度分析 1.什么是时间复杂度 时间复杂度是一个函数&#xff0c;它定性描述该算法的运行时间。我们在软件开发中&#xff0c;时间复杂度就是用来方便开发者估算出程序运行的答题时间。 那么该如何估计程序运行时间呢&#xff0c;通常会估算算法的操作单元数量来代表…

10个顶级AI艺术生成器

人工智能 (AI) 不仅影响商业和医疗保健等行业。 通过开创人工智能生成艺术的新时代&#xff0c;它还在创意产业中发挥着越来越重要的作用。 人工智能技术和工具通常可供任何人广泛使用&#xff0c;这有助于创造全新一代的艺术家。 我们经常听说人工智能将自动化或接管所有人类…

Java中的正则表达式详解

文章和代码已经归档至【Github仓库&#xff1a;https://github.com/timerring/java-tutorial 】或者【AIShareLab】回复 java 也可获取。 文章目录 正则表达式为什么要学习正则表达式再提出几个问题解决之道-正则表达式正则表达式基本介绍介绍 正则表达式底层实现实例分析 正则…

Word控件Aspose.Words教程:设置图表数据标签的默认选项

Aspose.Words是一种高级Word文档处理API&#xff0c;用于执行各种文档管理和操作任务。API支持生成&#xff0c;修改&#xff0c;转换&#xff0c;呈现和打印文档&#xff0c;而无需在跨平台应用程序中直接使用Microsoft Word。 Aspose API支持流行文件格式处理&#xff0c;并…

新星计划2023【网络应用领域基础】————————Day4

常见的网络基础介绍 前言 我们学习了一些基础的网络协议&#xff0c;以及子网掩码和vlan&#xff0c;同时也做了个简单的单臂路由实验 这篇文章我将仔细的讲解单臂路由的应用和交换机二层接口类型&#xff0c;以及wireshark的教程。 一&#xff0c;交换机二层接口 交换机的二…

Everypixel: AI图片搜索引擎

【产品介绍】 Everypixel是一个基于人工智能的图片搜索引擎。可以搜索超过 50 个图片来源的优质的授权图库版权素材图片&#xff0c;还可以使用免费图案功能&#xff0c;找到适合自己需求的可定制无缝图案。 Everypixel利用深度学习和计算机视觉技术&#xff0c;为客户提供先进…

Taro小程序配置网络请求

目录 1. 创建目录结构2. 全局通用的config的变量配置3. 配置http网络请求4. 使用 1. 创建目录结构 在 src 目录下新建 service 目录&#xff0c;目录下创建 api 和 http 子目录在 src 目录下新建 config 配置文件 2. 全局通用的config的变量配置 在 config 文件中添加一下代…

日本进口Hioki IM3536 LCR测试仪

Hioki IM3536 LCR测试仪 测量频率DC&#xff0c;4Hz~8MHz 测量时间&#xff1a;最快1ms 基本精度&#xff1a;0.05% rdg 1mΩ以上的精度保证范围&#xff0c;也可安心进行低阻测量 可内部发生DC偏压测量 从研发到生产线活跃在各种领域中 测量频率4Hz~8MHz&#xff0c;精度…

【pyq文案】可可爱爱、脑回路清奇の朋友圈文案

1.人每一个身体器官都是无价之宝&#xff0c;全部加起来1个月3000 2.别人出门&#xff1a;辣妹风、复古风、学院风&#xff1b;我出门&#xff1a;打工的勤劳小蜜蜂 3.看见自己就烦&#xff0c;50出&#xff1b;和今天星期四没关系 4.上学时拿钱混日子&#xff0c;上班后拿日…

种子轮、天使轮等相关知识

我们可以通过查询企业的相关工具网站&#xff0c;查看企业是否上市、独角兽、瞪羚企业、上市企业等情况。 转载&#xff1a; https://zhuanlan.zhihu.com/p/565389690 科创板挂牌不属于上市&#xff0c;企业在挂牌之后要经过协会核准后可以进行股份登记挂牌&#xff0c;大概需要…

【Redis】Redis 命令之 Hash

文章目录 ⛄介绍⛄命令⛄RedisTemplate API⛄应用场景 ⛄介绍 Hash类型&#xff0c;也叫散列&#xff0c;其value是一个无序字典&#xff0c;类似于Java中的 HashMap 结构。 String结构是将对象序列化为JSON字符串后存储&#xff0c;当需要修改对象某个字段时很不方便&#xf…

java服务-常用技术-生僻函数、方法、技巧

一、字符串操作 1. 需要转义的字符 java字符串中需要转义的特殊字符1. \n 表示换行&#xff1b;2. \t 表示制表符&#xff0c;相当于Table键&#xff1b;3. \ 表示单引号&#xff1b;4. \" 表示双引号&#xff1b;5. \\ 表示一个斜杠“\”。 2. split第二个参数limit的用…

Dynamics 365 DevOps CI/CD之WebResource

对于D365自身的发布&#xff0c;简单点来说就是Solution的发布&#xff0c;复杂一些会涉及周边集成接口等一系列的发布。如果是单纯的Solution的发布的Azure DevOps商店里有很多工具&#xff0c;比如Power DevOps Tools&#xff0c;这个我之前也有博文转载过相关文章&#xff0…

史上最通俗易懂的EWMA(指数加权移动平均)的参数解释以及程序代码

文章目录 一、EWMA&#xff08;指数加权移动平均&#xff09;是什么&#xff1f;二、详细的参数解释3、使用Python pandas库中的ewm()函数实现指数加权移动平均&#xff08;EWMA&#xff09;的示例代码总结 一、EWMA&#xff08;指数加权移动平均&#xff09;是什么&#xff1f…

抢跑智驾AI芯片「新路径」

“胆量”这个词&#xff0c;被后摩智能创始人兼CEO吴强着重提及。 5月10日&#xff0c;后摩智能发布首款存算一体智驾芯片鸿途™H30&#xff0c;以12nm制程实现最高物理算力 256TOPS&#xff0c;典型功耗 35W&#xff0c;成为国内率先落地存算一体大算力 AI 芯片的公司。即&am…

多线程的最最简单的基本了解

引言&#xff1a; 在学习完常规的语法后&#xff0c;我们将进入下一步的学习&#xff0c;而多线程则是被大多数人认为的下一步的学习目标&#xff0c;因为在有了基础的语法大框架后我们都有了对编程的一个基本的认知&#xff0c;而多线程则是开始有了一定的深度。 一、线程的基…

企业电子招标采购系统源码之传统采购模式面临的挑战

采购类型多 采购制度&#xff1a;采购金额、部门、品类的差异导致管理标准不同。 采购流程&#xff1a;从供应商管理、寻源操作到合同签订、订单执行&#xff0c;业务流程长&#xff0c;审批节点多&#xff0c;传统管理透明度低&#xff0c;联动性差。 供应商管理难 寻源&#…

Aixcoder:AI辅助编程工具

【产品介绍】 aixcoder是一款基于深度学习人工智能技术的AI辅助编程工具。提供了一个由各个领域的专业代码训练出来的“虚拟编程专家”&#xff0c;通过与aixcoder进行结对编程&#xff0c;程序员可以感受到工作效率的显著提升。 借助aixcoder的帮助&#xff0c;程序员可以摆脱…

ChatGPT教程 基于Springboot+Spring+MybatisPlus实现gpt3.5接口开发

⛪ ChatGPT教程: 基于SpringbootSpringMybatisPlus实现gpt3.5接口开发 &#x1f680; 文章介绍: 本文基于SpringBootSpringMybatisPlus实现一个响应快速的gpt接口&#xff0c;可通过与前端整合开发对应的前端页面 &#x1f680; 源码获取: 项目中的资料可以通过文章底部小卡片获…