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

  • 20
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值