Android 12系统源码_多窗口模式(一)和多窗口模式相关方法的调用顺序

前言

从 Android 7.0 开始,Google 推出了一个名为“多窗口模式”的新功能,允许在设备屏幕上同时显示多个应用,多窗口模式允许多个应用同时共享同一屏幕,多窗口模式(Multi Window Supports)目前支持以下三种配置:

  • 分屏模式:让系统可以左右或上下并排显示应用。
    在这里插入图片描述

  • 画中画模式:在应用中用小窗口叠加显示其他应用
    在这里插入图片描述

  • 自由窗口模式:在可移动且可调整大小的单独窗口中显示各个应用。

一、分屏模式的适配

1、我们如何才能让自己的 APP 支持分屏模式呢?

若项目的targetSDKVersion 大于等于24,那么可以在AndroidManifest.xml 文件的Application 或Activity 节点通过设置android:resizeableActivity=[“true” | “false”] 来控制整个 APP 或某个 Activity 是否支持分屏。该属性的默认值是true ,也就是说,如果不设置该属性,在支持分屏的设备上,默认是可以分屏的。

若项目的targetSDKVersion 小于24,那么运行在支持分屏的设备上,默认可以分屏。这时如果需要禁止分屏,需要在AndroidManifest.xml 文件的Application 或Activity 节点设置android:screenOrientation 属性来控制整个 APP 或 某个 Activity 的屏幕方向,从而控制整个 APP 或某个 Activity 禁止分屏。

2、分屏模式的监听

能不能在代码中监听 APP 是否进入分屏模式呢?答案是能。由于 APP 在分屏模式发生改变时会执行onMultiWindowModeChanged 方法,因此我们在 Activity 中重写这个方法就可以实现分屏的监听了。

@Overridepublic void onMultiWindowModeChanged(boolean isInMultiWindowMode) {super.onMultiWindowModeChanged(isInMultiWindowMode);// 判断当前是否为分屏模式if (isInMultiWindowMode) {// 已进入分屏模式} else {// 未进入分屏模式}}

3、分屏模式下的生命周期

  • 进入分屏模式时,Activity 的生命周期:

onPause()- onStop()- onMultiWindowModeChanged()- onDestroy()- onCreate()- onStart()- onResume()- onPause()

  • 退出分屏模式时,Activity 的生命周期:

onStop()- onDestroy()- onCreate()- onStart()- onResume()- onPause()- onMultiWindowModeChanged()- onResume()

可以看出,在进入分屏模式时,Activity 先执行onMultiWindowModeChanged 方法,再重建自己。在退出分屏模式时,Activity 先重建自己,再执行onMultiWindowModeChanged 方法。这样会有一个问题,我们的 APP 进入分屏模式时,在onMultiWindowModeChanged 方法中如果有对 UI 等的操作,经过之后的自动重建就没有效果了。为了防止这种情况,需要在AndroidManifest.xml 的Activity 节点设置以下属性:

android:configChanges=“screenSize|smallestScreenSize|screenLayout|orientation”

设置了这个属性,在进入分屏模式时,Activity 就不会自动重建了。

  • 分屏模式下打开 Activity

如果 APP 在分屏模式下打开 Activity 时,为 Intent 设置了Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT 和Intent.FLAG_ACTIVITY_NEW_TASK 标志,那么新打开的 Activity 将显示在当前 APP 的另一侧。例如下面的代码:

	Intent intent = new Intent(this, NewActivity.class);intent.setFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT|Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(intent);

二、多窗口模式在Activity和ActivityThread类中的主要调用回溯

1、结合前面的分析,可以发现onMultiWindowModeChanged是一个很重要的方法,让我们来看下这个方法是什么时候被系统调用的。

base/core/java/android/app/Activity.java

public class Activity extends ContextThemeWrapperimplements LayoutInflater.Factory2,Window.Callback, KeyEvent.Callback,OnCreateContextMenuListener, ComponentCallbacks2,Window.OnWindowDismissedCallback,AutofillManager.AutofillClient, ContentCaptureManager.ContentCaptureClient {private Window mWindow;//Activity对应的Windowfinal FragmentController mFragments = FragmentController.createController(new HostCallbacks());private boolean mIsInMultiWindowMode;//当前是否处于多窗口模式@Deprecatedpublic void onMultiWindowModeChanged(boolean isInMultiWindowMode) {}public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) {onMultiWindowModeChanged(isInMultiWindowMode);}final void dispatchMultiWindowModeChanged(boolean isInMultiWindowMode,Configuration newConfig) {if (DEBUG_LIFECYCLE) Slog.v(TAG,"dispatchMultiWindowModeChanged " + this + ": " + isInMultiWindowMode+ " " + newConfig);mFragments.dispatchMultiWindowModeChanged(isInMultiWindowMode, newConfig);if (mWindow != null) {mWindow.onMultiWindowModeChanged();}mIsInMultiWindowMode = isInMultiWindowMode;onMultiWindowModeChanged(isInMultiWindowMode, newConfig);}
}

onMultiWindowModeChanged方法在Activity中被初次调用,是在dispatchMultiWindowModeChanged方法中。

2、而Activity的dispatchMultiWindowModeChanged方法初次被调用,是在ActivityThread类中。

base/core/java/android/app/ActivityThread.java

public final class ActivityThread extends ClientTransactionHandlerimplements ActivityThreadInternal {private final Map<IBinder, Integer> mLastReportedWindowingMode = Collections.synchronizedMap(new ArrayMap<>());//启动Activity的核心方法private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {...代码省略...Activity activity = null;java.lang.ClassLoader cl = appContext.getClassLoader();//通过反射创建Activity实例对象activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);...代码省略...//创建activity对应的配置信息对象Configuration config = new Configuration(mConfigurationController.getCompatConfiguration());...代码省略...//将窗口模式信息以activity的token为key,存放到Map缓存中mLastReportedWindowingMode.put(activity.getActivityToken(),config.windowConfiguration.getWindowingMode());...代码省略...}//销毁Activity的核心方法void performDestroyActivity(ActivityClientRecord r, boolean finishing,int configChanges, boolean getNonConfigInstance, String reason) {...代码省略...//从map缓存中移除activity对应的窗口模式信息mLastReportedWindowingMode.remove(r.activity.getActivityToken());...代码省略...}/*** 有必要的话将会调用窗口模式发生变化的回调方式*/private void handleWindowingModeChangeIfNeeded(Activity activity,Configuration newConfiguration) {final int newWindowingMode = newConfiguration.windowConfiguration.getWindowingMode();final IBinder token = activity.getActivityToken();final int oldWindowingMode = mLastReportedWindowingMode.getOrDefault(token,WINDOWING_MODE_UNDEFINED);//窗口模式没有发生变化、直接返回if (oldWindowingMode == newWindowingMode) return;// PiP callback is sent before the MW one.if (newWindowingMode == WINDOWING_MODE_PINNED) {//触发画中画模式变化回调方法activity.dispatchPictureInPictureModeChanged(true, newConfiguration);} else if (oldWindowingMode == WINDOWING_MODE_PINNED) {//触发画中画模式变化回调方法activity.dispatchPictureInPictureModeChanged(false, newConfiguration);}final boolean wasInMultiWindowMode = WindowConfiguration.inMultiWindowMode(oldWindowingMode);final boolean nowInMultiWindowMode = WindowConfiguration.inMultiWindowMode(newWindowingMode);if (wasInMultiWindowMode != nowInMultiWindowMode) {//如果旧的窗口模式和新的窗口模式,二者有其一不是多窗口模式,触发多窗口模式变化回调方法activity.dispatchMultiWindowModeChanged(nowInMultiWindowMode, newConfiguration);}//更新Map缓存中Activity对应的窗口模式信息mLastReportedWindowingMode.put(token, newWindowingMode);}
}    
  • ActivityThread在启动Activity的时候,会将activity对应的窗口模式信息缓存到集合中
  • 在handleWindowingModeChangeIfNeeded方法被调用的时候,会回调activity对应的回调方法,并更新activity对应的集合中的窗口模式信息
  • ActivityThread在销毁Activity的时候,会将activity对应的窗口模式信息从集合中移除

3、继续来看下在ActivityThread中handleWindowingModeChangeIfNeeded方法是如何被层层调用的。

public final class ActivityThread extends ClientTransactionHandlerimplements ActivityThreadInternal {private Configuration performActivityConfigurationChanged(Activity activity,Configuration newConfig, Configuration amOverrideConfig, int displayId) {//调用handleWindowingModeChangeIfNeededhandleWindowingModeChangeIfNeeded(activity, newConfig);...代码省略...}private Configuration performConfigurationChangedForActivity(ActivityClientRecord r,Configuration newBaseConfig, int displayId) {r.tmpConfig.setTo(newBaseConfig);if (r.overrideConfig != null) {r.tmpConfig.updateFrom(r.overrideConfig);}//调用performActivityConfigurationChanged方法final Configuration reportedConfig = performActivityConfigurationChanged(r.activity,r.tmpConfig, r.overrideConfig, displayId);freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));return reportedConfig;}public void handleActivityConfigurationChanged(ActivityClientRecord r,@NonNull Configuration overrideConfig, int displayId) {...代码省略...// Perform updates.r.overrideConfig = overrideConfig;final ViewRootImpl viewRoot = r.activity.mDecor != null? r.activity.mDecor.getViewRootImpl() : null;//调用performConfigurationChangedForActivity方法final Configuration reportedConfig = performConfigurationChangedForActivity(r,mConfigurationController.getCompatConfiguration(),movedToDifferentDisplay ? displayId : r.activity.getDisplayId());// Notify the ViewRootImpl instance about configuration changes. It may have initiated this// update to make sure that resources are updated before updating itself.if (viewRoot != null) {if (movedToDifferentDisplay) {viewRoot.onMovedToDisplay(displayId, reportedConfig);}//调用viewRootImpl的updateConfiguration方法,这里会触发Activity页面View内容的刷新变化viewRoot.updateConfiguration(displayId);}mSomeActivitiesChanged = true;}}

4、结合前面的分析,这里对Activity和多窗口模式相关方法的调用顺序做个简单梳理。
在这里插入图片描述
简单总结一下,系统是在ActivityThread的handleWindowingModeChangeIfNeeded方法中触发Activity的dispatchMultiWindowModeChanged,而该方法进一步调用Activity的onMultiWindowModeChanged来告知应用开发人员,Activity的多窗口模式发生了变化。

三、多窗口模式在ViewRootImp类中的主要调用

1、重新来看下ActivityThread的handleActivityConfigurationChanged方法,这次我们主要关注和ViewRootImpl的关系。

public final class ActivityThread extends ClientTransactionHandlerimplements ActivityThreadInternal {public void handleActivityConfigurationChanged(ActivityClientRecord r,@NonNull Configuration overrideConfig, int displayId) {...代码省略...// Perform updates.r.overrideConfig = overrideConfig;final ViewRootImpl viewRoot = r.activity.mDecor != null? r.activity.mDecor.getViewRootImpl() : null;//调用performConfigurationChangedForActivity方法,该方法最终会触发Activity的onMultiWindowModeChanged方法final Configuration reportedConfig = performConfigurationChangedForActivity(r,mConfigurationController.getCompatConfiguration(),movedToDifferentDisplay ? displayId : r.activity.getDisplayId());// Notify the ViewRootImpl instance about configuration changes. It may have initiated this// update to make sure that resources are updated before updating itself.if (viewRoot != null) {if (movedToDifferentDisplay) {viewRoot.onMovedToDisplay(displayId, reportedConfig);}//调用viewRootImpl的updateConfiguration方法,这里会触发Activity页面View内容的刷新变化viewRoot.updateConfiguration(displayId);}mSomeActivitiesChanged = true;}}

在调用performConfigurationChangedForActivity方法,该方法触发Activity的onMultiWindowModeChanged方法之后,会分别调用Activity对应的ViewRootImpl的onMovedToDisplay方法和updateConfiguration方法。

2、来看下ViewRootImpl的onMovedToDisplay方法和updateConfiguration方法。

public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,AttachedSurfaceControl {public void onMovedToDisplay(int displayId, Configuration config) {if (mDisplay.getDisplayId() == displayId) {return;}// Get new instance of display based on current display adjustments. It may be updated later// if moving between the displays also involved a configuration change.updateInternalDisplay(displayId, mView.getResources());mImeFocusController.onMovedToDisplay();mAttachInfo.mDisplayState = mDisplay.getState();// Internal state updated, now notify the view hierarchy.mView.dispatchMovedToDisplay(mDisplay, config);}public void updateConfiguration(int newDisplayId) {if (mView == null) {return;}// At this point the resources have been updated to// have the most recent config, whatever that is.  Use// the one in them which may be newer.final Resources localResources = mView.getResources();final Configuration config = localResources.getConfiguration();// Handle move to display.if (newDisplayId != INVALID_DISPLAY) {onMovedToDisplay(newDisplayId, config);}// Handle configuration change.if (mForceNextConfigUpdate || mLastConfigurationFromResources.diff(config) != 0) {// Update the display with new DisplayAdjustments.updateInternalDisplay(mDisplay.getDisplayId(), localResources);final int lastLayoutDirection = mLastConfigurationFromResources.getLayoutDirection();final int currentLayoutDirection = config.getLayoutDirection();mLastConfigurationFromResources.setTo(config);if (lastLayoutDirection != currentLayoutDirection&& mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {mView.setLayoutDirection(currentLayoutDirection);}mView.dispatchConfigurationChanged(config);// We could have gotten this {@link Configuration} update after we called// {@link #performTraversals} with an older {@link Configuration}. As a result, our// window frame may be stale. We must ensure the next pass of {@link #performTraversals}// catches this.mForceNextWindowRelayout = true;requestLayout();//调用requestLayout触发窗口属性和视图的刷新}updateForceDarkMode();}}

3、ViewRootImpl的updateConfiguration方法会继续调用requestLayout方法,该方法会触发Activity对应的窗口属性和视图的刷新。
在这里插入图片描述

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

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

相关文章

我的C++奇迹之旅相遇:支持函数重载的原理

文章目录 &#x1f4dd;前言&#x1f320; C支持函数重载的原理&#xff1a;名字修饰(name Mangling)&#x1f309;不同编译器不同函数名修饰规则 &#x1f320;Windows下名字修饰规则&#x1f6a9;总结 &#x1f4dd;前言 函数重载概念 函数重载&#xff1a;是函数的一种特殊…

大模型面试准备(九):简单透彻理解MoE

节前&#xff0c;我们组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学&#xff0c;针对大模型技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何备战、面试常考点分享等热门话题进行了深入的讨论。 合集在这…

Linux(CentOS7)安装 MySQL8

目录 下载 上传 解压 创建配置文件 初始化 MySQL 服务 启动 MySQL 服务 连接 MySQL 创建软链接 下载 官方地址&#xff1a; MySQL :: Download MySQL Community Serverhttps://dev.mysql.com/downloads/mysql/选择版本前需先看一下服务器的 glibc 版本 ldd --versio…

js录制本地摄像头下载mp4和转file文件流

前端获取本地摄像头和麦克风并录制为mp4导出其实很简单&#xff0c;只是可能你不太了解相关的知识点&#xff0c;我已经在项目中实战过。 前端获取本地摄像头麦克风&#xff0c;并录制视频 export class VideoRecording { // 录视频mediaRecorder: MediaRecorder | null;strea…

浏览器工作原理与实践--垃圾回收:垃圾数据是如何自动回收的

在上一篇文章中&#xff0c;我们提到了JavaScript中的数据是如何存储的&#xff0c;并通过例子分析了原始数据类型是存储在栈空间中的&#xff0c;引用类型的数据是存储在堆空间中的。通过这种分配方式&#xff0c;我们解决了数据的内存分配的问题。 不过有些数据被使用之后&am…

【PCB专题】案例:Allegro怎么1:1在纸上打印出PCB板

首先我们要知道为什么我们需要1:1打印出PCB板? 为什么需要1:1打印 一般我们要1:1打印出来这个功能是在新画的器件封装验证、首板结构配合检查、多个板卡互连验证等情况下使用: 在新画了一个器件封装时,如果我们手上有实物,那么通过1:1打印出来后可以与实物器件交叉对比…

Web框架开发-Django-Form组件归类

一、Form类 创建Form类时,注意涉及到【字段】和【插件】,字段用于对用户请求数据的验证,插件用于生成HTML; 1、Django内置字段如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 …

CVE-2023-38408漏洞修复 - 升级openssl和openssh

CVE-2023-38408 OpenSSH 代码问题漏洞修复 - 升级openssl和openssh ※ 重要说明&#xff1a; 1、升级后会导致无法用ssh远程登录&#xff0c;提示“Permission denied, please try again.” 2、解决方案请查看本章节【三、解决升级后无法用ssh远程登录】 目录 CVE-2023-38408 O…

使用Docker搭建NZBGet

NZBGet是一款高效的NZB下载客户端&#xff0c;它支持使用NZB文件来自动下载网络上的文件&#xff0c;特别是BT种子文件。NZBGet能够与多个索引器和下载管理器&#xff08;如Sonarr、Radarr、SABnzbd等&#xff09;集成&#xff0c;提供稳定、快速且易于管理的下载体验。 使用D…

代码随想录算法训练营第39天| 738.单调递增的数字、968.监控二叉树

738.单调递增的数字 题目链接&#xff1a;单调递增的数字 题目描述&#xff1a;当且仅当每个相邻位数上的数字 x 和 y 满足 x < y 时&#xff0c;我们称这个整数是单调递增的。 给定一个整数 n &#xff0c;返回 小于或等于 n 的最大数字&#xff0c;且数字呈 单调递增 。 解…

WebScraper网页数据爬取可视化工具使用(无需编码)

前言 Web Scraper 是一个浏览器扩展&#xff0c;可以实现无需编码即可爬取网页上的数据。只需按照规则进行配置&#xff0c;即可实现一键爬取导出数据。 安装 进入Google应用商店安装此插件&#xff0c;安装步骤如下&#xff1a; 进入Google应用商店需要外网VPN才能访问&…

C#基础复习

【namespace】 命名空间 。net有众多类&#xff0c;全放一起&#xff0c;无法快速检索到需要的类。 所以用【点】来区分&#xff0c;注意【点】不是包含关系。 解决类重名问题时&#xff0c;要用完全限定名来区分。【完整命名空间路径】 配合引用&#xff1a; 【字段】 类内部…

Android 中 调试和减少内存错误

Android 中 调试和减少内存错误 ASan 概述 官网连接&#xff1a; https://developer.android.com/ndk/guides/asan?hlzh-cn ASan API 27开始HWASan&#xff08;替换AScan&#xff09; 从 NDK r21 和 Android 10&#xff08;API 级别 29&#xff09;开始适用于 64 位 Arm 设…

微服务demo(三)nacosfeign

一、feign使用 1、集成方法 1.1、pom consumer添加依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId><version>2.2.6.RELEASE</version></dependency&…

WEB APIS知识点案例总结

随机点名案例 业务分析: 点击开始按钮随机抽取数组中的一个数据,放到页面中点击结束按钮删除数组当前抽取的一个数据当抽取到最后一个数据的时候,两个按钮同时禁用(只剩最后一个数据不用抽了) 核心:利用定时器快速展示,停止定时器结束展示 <!DOCTYPE html> <html…

智慧公厕产品的特点、应用场景

随着城市化进程的加速和智能科技的不断发展&#xff0c;智慧公厕作为城市管理的重要组成部分&#xff0c;逐渐成为了现代城市的一道靓丽风景线。它的特点和应用场景备受人们关注和喜爱。 智慧公厕的特点有哪些呢&#xff1f;首先&#xff0c;它智能化的设备和感应技术为其特点…

华为昇腾认证考试内容有哪些

华为昇腾认证考试的内容主要包括理论知识和实践操作两部分。 在理论知识部分&#xff0c;考生需要掌握昇腾计算的基础知识&#xff0c;包括昇腾计算平台的架构、性能特点、应用场景等。此外&#xff0c;还需要深入理解昇腾AI框架、算子开发、模型优化等相关技术原理和应用方法…

《操作系统导论》第14章读书笔记:插叙:内存操作API

《操作系统导论》第14章读书笔记&#xff1a;插叙&#xff1a;内存操作API —— 杭州 2024-03-30 夜 文章目录 《操作系统导论》第14章读书笔记&#xff1a;插叙&#xff1a;内存操作API1.内存类型1.1.栈内存&#xff1a;它的申请和释放操作是编译器来隐式管理的&#xff0c;所…

Xcode删除原本的Git,再添加新的git

本文参考&#xff1a;Xcode怎么删除原本git,在重新设置新的git地址_ios xcode 删除原本git-CSDN博客 开发中会有一个问题。Xcode项目A 提交到Git服务器server1&#xff0c;此时项目A内部已经存在一个Git文件&#xff0c;与server1相关联。 此时你想将项目A提交到 另一个Git…

前端实现菜单搜索搜索(功能模版)

目录 前言正文 前言 总体界面如下所示&#xff1a; 正文 <template><div class"avue-searchs"click.self"handleEsc"><div class"avue-searchs__title">菜单搜索</div><div class"avue-searchs__content"…