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

news2024/11/26 14:50:24

前言

从 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 中重写这个方法就可以实现分屏的监听了。

@Override
 public 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 ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback,
        AutofillManager.AutofillClient, ContentCaptureManager.ContentCaptureClient {
   
    private Window mWindow;//Activity对应的Window
    final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
    private boolean mIsInMultiWindowMode;//当前是否处于多窗口模式
    
    @Deprecated
    public 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 ClientTransactionHandler
        implements 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 ClientTransactionHandler
        implements ActivityThreadInternal {
        
    private Configuration performActivityConfigurationChanged(Activity activity,
            Configuration newConfig, Configuration amOverrideConfig, int displayId) {
         //调用handleWindowingModeChangeIfNeeded
         handleWindowingModeChangeIfNeeded(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 ClientTransactionHandler
        implements 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.coloradmin.cn/o/1557216.html

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

相关文章

ubuntu2204配置zabbix6.4高可用

zabbix6.4-HA 配置keepalived配置haproxy数据库高可用配置zabbix-server配置proxy配置客户端agent 本实验VMware搭建zabbix6.4高可用集群&#xff0c;搭配haproxykeepalived。 master&#xff0c;node节点搭建haproxykeepalibed主备并配置vip地址 三台控制节点搭建数据库高可用…

学生成绩管理系统的设计与实现|Springboot+ Mysql+Java+ B/S结构(可运行源码+数据库+设计文档)

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读100套最新项目持续更新中..... 2024年计算机毕业论文&#xff08;设计&#xff09;学生选题参考合集推荐收藏&#xff08;包含Springboot、jsp、ssmvue等技术项目合集&#xff09; 1. 管理员功…

创建第一个51文件

1.找一个文件目录创建一个main.c 比如我的 F:\my_project\project_of_51\0.first_of_51 什么你不会&#xff1f; 先把这里的文件扩展名打开; 再创建一个文本文件&#xff0c;重新命名为main.c // 修改.c 后弹出一个确认修改的框&#xff0c;选确认即可 2.kei操作: 1&#xf…

腾讯云2核4G5M云服务器性能评测,不买后悔

腾讯云轻量应用服务器2核4G5M配置性能测评&#xff0c;腾讯云轻量2核4G5M带宽服务器支持多少人在线访问&#xff1f;并发数10&#xff0c;支持每天5000IP人数访问&#xff0c;腾讯云百科txybk.com整理2核4G服务器支持多少人同时在线&#xff1f;并发数测试、CPU性能、内存性能、…

LATTICE进阶篇DDR2--(0)获取ddr2 IP核

前言 想要仿真lattice的DDR2由来已久&#xff0c;但苦于对其了解甚少&#xff0c;在查阅过很多资料后&#xff0c;终于对这个IP核的仿真有了一些了解。 现做一些总结&#xff0c;以备不时之需&#xff0c;也让有需要的朋友&#xff0c;少走一些弯路。 环境&#xff1a;win10…

appium 自动化测试之知乎Android客户端

接下来我们研究一下测试知乎Android客户端。自行下载 配置目录如下&#xff1a;把知乎客户端命名为zhihu.apk 编写基于java testng的测试用例&#xff1a; 由于本人是熟悉selenium webdriver&#xff0c;所以在Android对象识别时&#xff0c;采用的策略和selenium webdriver …

python基础——文件操作【文件编码、文件的打开与关闭操作、文件读写操作】

&#x1f4dd;前言&#xff1a; 这篇文章主要讲解一下python中对于文件的基础操作&#xff1a; 1&#xff0c;文件编码 2&#xff0c;文件的打开与关闭操作 3&#xff0c;文件读写操作 &#x1f3ac;个人简介&#xff1a;努力学习ing &#x1f4cb;个人专栏&#xff1a;C语言入…

JavaScript基础练习题之留言板

一、留言板代码实现&#xff1a; <!DOCTYPE htmlPUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns"http://www.w3.org/1999/xhtml"><head><met…

鸿蒙之路由跳转router

router的使用都是基于Entry修饰的组件。 都是基于resources/base/profile/main-page.json中的路由配置来跳转的 router提供下列的几个方法 1.pushUrl -压栈一层盖一层(在鸿蒙中页面栈支持最大数值是32) 2.replaceUrl会替换当前页面&#xff0c;不管是不是同一个页面&#xf…

如何注册Claude3?解决Claude3无海外手机号接收验证码的问题以及如何订阅Claude Pro

原文链接&#xff1a;如何注册 Claude3&#xff1f;解决 Claude3 无海外手机号接收验证码的问题以及如何订阅 Claude Pro 前言 Claude3已经出来有一段时间了&#xff0c;大家有没有体验过呢&#xff1f;不过从目前来看&#xff0c;Anthropic公司总共推出了3个模型&#xff1…

03---java面试八股文——mybatis-------8题

21、MyBatis实现一对一查询 MyBatis 有两种不同的方式加载关联&#xff1a; 嵌套 Select 查询&#xff1a;通过执行另外一个 SQL 映射语句来加载期望的复杂类型。嵌套结果映射&#xff1a;使用嵌套的结果映射来处理连接结果的重复子集。查看mybatis的关联 MyBatis是一种流行的J…

FME学习之旅---day16

我们付出一些成本&#xff0c;时间的或者其他&#xff0c;最终总能收获一些什么。 【FME-HOW-TO系列】24 拓扑相交 本章还是学习SpatialFilter转换器得用法&#xff0c;主要用到的空间关系是相交&#xff0c;即Filter Intersect Candidate。 首先&#xff0c;添加读模块的相…

ssm框架笔记-maven

html是骨头 css使皮肤 js是你能做的动作 MAVEN 依赖管理&#xff1a;1.声明dependenciys标签 2.maven search3。 版本号提取 3.$引用 3.2依赖传递和冲突 依赖传递指的是当一个模块或库 A 依赖于另一个模块或库 B&#xff0c;而 B 又依赖于模块或库 C&#xff0c;那么 A 会间…

基于SSM的戒烟网站(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的戒烟网站&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring SpringMv…

算法打卡day21(开始回溯)

今日任务&#xff1a; 1&#xff09;77.组合 77.组合 题目链接&#xff1a;77. 组合 - 力扣&#xff08;LeetCode&#xff09; 文章讲解&#xff1a;代码随想录 (programmercarl.com) 视频讲解&#xff1a;带你学透回溯算法-组合问题&#xff08;对应力扣题目&#xff1a;77…

第一次给开源项目做贡献,我给 Hutool 改了行注释

大家好&#xff0c;我是松柏。 前两天在修 bug 的时候&#xff0c;写了个indexOf的方法。 这个方法是用来获取一段文本中某个字符串第 n 次出现的索引&#xff0c; 由于第一次写这个方法的时候少考虑了一种边界条件&#xff0c;导致最后查出的数据有时候会不符合预期。 我处…

【跟着CHATGPT学习硬件外设 | 03】UART

本文根据博主设计的Prompt由CHATGPT生成&#xff0c;形成极简外设概念。 &#x1f680; 1. 概念揭秘 UART&#xff0c;全称为"Universal Asynchronous Receiver/Transmitter"&#xff08;通用异步收发器&#xff09;&#xff0c;是一种常用的串行通信协议。UART最早…

AI学习-Pandas数据处理分析

文章目录 1. Pandas概述2. Series用法2.1 Series的创建2.2 Series的取值2.3 Series的相关方法 3. DataFrame用法3.1 DataFrame创建3.2 DataFrame取值3.3 DataFrame相关方法 1. Pandas概述 ​ Pandas 是一个开源的数据分析处理库&#xff0c;它应用在数据科学、统计分析、机器学…

2006-2023年2月各地级市城投债详细数据

2006-2023.2各地级市城投债详细数据 1、时间&#xff1a;2006-2023.2 2、来源&#xff1a;深圳证券交易所和上海证券交易所官网、人民银行、证券监督管理委员会等金融监管机构等官网 3、指标&#xff1a;省份、城市、证券代码、证券简称、债券简称、证券全称、债券初始面值单…

应急响应靶机训练-Linux2题解

前言 接上文&#xff0c;应急响应靶机训练Linux2 靶机地址&#xff1a;应急响应靶机-Linux(2) 题解 登录虚拟机&#xff1a; 修改面板密码 提交攻击者IP 答案&#xff1a;192.168.20.1 查看宝塔日志即可 用的net直接是网关 提交攻击者修改的管理员密码(明文) 答案&…