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

@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对应的窗口属性和视图的刷新。
在这里插入图片描述

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

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

相关文章

在 IntelliJ IDEA 中使用 Terminal 执行 git log 命令后的退出方法

前言 IntelliJ IDEA 是一款广受欢迎的集成开发环境&#xff0c;它内置了强大的终端工具&#xff0c;使得开发者无需离开IDE就能便捷地执行各种命令行操作&#xff0c;包括使用 Git 进行版本控制。在 IDEA 的 Terminal 中执行 git log 命令时&#xff0c;由于该命令会显示项目的…

烫烫烫手的结构体大小计算来咯,很烫哦,慢慢消化。自定义类型(一)

emmm&#xff0c;在这炎热的夏天在宿舍吹着空调写着博客也是一件不错的事呢&#xff0c;今天就来来好好盘一下C语言中的自定义类型。 常常会回顾努力的自己&#xff0c;所以要给自己的努力留下足迹。 为今天努力的自己打个卡&#xff0c;留个痕迹吧 2024.03.29 小闭 目录 …

【一】DDR3基础知识与IMG IP

【一】DDR3基础知识与IMG IP 一、DDR3的基本知识 1、DDR3全称为第三代双倍速率同步动态随机存储器 特点&#xff1a;掉电无法保存数据&#xff0c;需要周期性的刷新&#xff1b;时钟上升沿和下降沿都在传输数据&#xff1b;突发传输&#xff0c;突发长度burtst length一般为…

【C++庖丁解牛】基于红黑树实现的两种常用的关联容器map和set以及multimap

&#x1f341;你好&#xff0c;我是 RO-BERRY &#x1f4d7; 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f384;感谢你的陪伴与支持 &#xff0c;故事既有了开头&#xff0c;就要画上一个完美的句号&#xff0c;让我们一起加油 目录 1. 关联式容器2. 键值对3…

最短路径——Floyd算法、Dijkstra算法(未完...)

这里写目录标题 例题引入&#xff1a; 路径——蓝桥2021省赛题目分析题解&#xff01;&#xff01;&#xff01;求最短路径问题&#xff01;&#xff01;&#xff01;应用场景图的基础Floyd算法Acwing-843.有边数限制的最短路简单的思路讲解 Dijkstra算法 例题引入&#xff1a;…

鸿蒙应用开发与鸿蒙系统开发哪个更有前景?

随后迎来了不少互联网公司与华为鸿蒙原生应用达成了合作&#xff0c;像我们常见的阿里、京东、小红书、得物……等公司&#xff0c;还有一些银行也都与华为鸿蒙达成了合作。使得一时之间市场紧缺鸿蒙开发人才&#xff0c;不少公司不惜重金争抢人才。 据智联招聘的最新数据显示…

最强的营销团队,这样打造!

在瞬息万变的商业环境中&#xff0c;构建无可挑剔的营销团队结构的重要性毋庸置疑。营销团队的力量不仅在于其成员的个人才能&#xff0c;还在于这些才能如何有效地协调在一起。建立完美的营销团队结构类似于拼图。每块拼图都代表了独特的技能和视角&#xff0c;如果放置得当&a…

未来5年|个人电脑“变”AI PC

随着生成式AI热潮达到白热化阶段&#xff0c;笔记本电脑市场正面临一场范式转变。根据Tech Insights预测数据&#xff0c;到2029年&#xff0c;配备专用AI加速芯片&#xff08;即NPU&#xff09;的AI赋能笔记本电脑将在整个笔记本市场占据主导地位&#xff0c;占比高达95%&…

【MySQL】事务是什么?事务的特性又是什么?

文章目录 ✍事务是什么&#xff1f;✍事务的特性&#xff08;四个&#xff09;✍事务并发时出现的问题✍事务的隔离性 ✍事务是什么&#xff1f; 事务是由一个或多个SQL语句构成的&#xff0c;在事务中&#xff0c;这些的SQL不可分割&#xff0c;是一个整体&#xff0c;整个事…

牛客NC30 缺失的第一个正整数【simple map Java,Go,PHP】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/50ec6a5b0e4e45348544348278cdcee5 核心 Map参考答案Java import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回方法规定的值即可…

web前端之罗盘时钟、不一样的补零方式、LED字体、padStart

MENU 效果图htmlJavaScriptstyle 效果图 html <div class"clock"><div class"second-box"></div><div class"minute-box"></div><div class"hour-box"></div><div class"day-box&…

HarmonyOS 应用开发之Stage模型启动FA模型PageAbility

本小节介绍Stage模型的两种应用组件如何启动FA模型的PageAbility组件。 UIAbility启动PageAbility UIAbility启动PageAbility和UIAbility启动UIAbility的方式完全相同。 说明&#xff1a; 需注意FA模型中abilityName由bundleName AbilityName组成&#xff0c;具体见示例。 i…

蓝桥杯第十三届电子类单片机组程序设计

目录 前言 单片机资源数据包_2023 一、第十三届比赛省赛 1.比赛题目 2.赛题解读 二、部分功能实现 1.继电器的开启与关闭 2.长按切换显示状态功能的实现 3.对于温度传感器小数部分的处理 4.其他处理 1&#xff09;关于数码管显示小数的处理 2&#xff09;关于5s后继…

vue3+ts+elementplus写一个登录页面教程

文章目录 前言1. 安装 Vue CLI 和 TypeScript 支持2. 创建登录组件 文章重点内容 前言 前期准备步骤&#xff1a; 创建一个使用 Vue 3 和 TypeScript 的登录页面涉及到多个步骤。以下是一个基本的教程&#xff0c;帮助你从头开始构建这样一个页面&#xff1a; 1. 安装 Vue CL…

Ollama部署在线ai聊天

概述&#xff1a;虽然ollama在Windows方面还有很多bug&#xff0c;但不妨碍它在ai领域上面的成就 第一步&#xff1a;安装Ollama 官网&#xff1a;Download Ollama on Windows 下载安装即可。说明一下ollama的安装位置只能是c盘&#xff0c;好像改不了&#xff0c;但是数据模…

算法学习——LeetCode力扣动态规划篇8

算法学习——LeetCode力扣动态规划篇8 300. 最长递增子序列 300. 最长递增子序列 - 力扣&#xff08;LeetCode&#xff09; 描述 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或不删…

FA模型切换Stage模型之module的切换

从FA模型切换到Stage模型时&#xff0c;开发者需要将config.json文件module标签下的配置迁移到module.json5配置文件module标签下&#xff0c;具体差异见下列表格。 表1 FA模型module标签与Stage模型module标签差异对比 表2 FA模型metaData和Stage中metadata对比 表3 FA模型me…

【UI框架】——保姆式使用教程

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

深度学习500问——Chapter05: 卷积神经网络(CNN)(2)

文章目录 5.6 有哪些池化方法 5.7 1x1卷积作用 5.8 卷积层和池化层有什么区别 5.9 卷积核是否一定越大越好 5.10 每层卷积是否只能用一种尺寸的卷积核 5.11 怎样才能减少卷积层参数量 5.12 在进行卷积操作时&#xff0c;必须同时考虑通道和区域吗 5.13 采用宽卷积的好处有什么 …

多线程JUC 第2季 synchornized和Lock锁(重入,公平)

一 锁 1.1 锁的介绍 synchronized&#xff0c;和lock锁都是一种悲观锁。悲观锁适用于写多场景&#xff0c;乐观锁适用于读多场景&#xff0c;实现策略有&#xff1a;版本号和cas自旋算法。 1.2 类锁和对象锁的使用场景 1.3 任何对象都有一把锁 之所以任何一个对象都有把锁…