赞
踩
从 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、分屏模式下的生命周期
onPause()- onStop()- onMultiWindowModeChanged()- onDestroy()- onCreate()- onStart()- onResume()- onPause()
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 就不会自动重建了。
如果 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);
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); } }
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的多窗口模式发生了变化。
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对应的窗口属性和视图的刷新。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。