目录

Android N引入了Multi-Window,Freeform(自由窗口)模式是其中的一种。自由窗口模式下可以实现窗口的自由缩放、移动。而我们知道每个应用都有一个窗口,所以当所有的应用窗口都没有独占屏幕的时候,就可以同时在屏幕自由的显示所有应用,达到跟PC桌面一样的效果,当然这个自由窗口受限于screen width等窗口width相关的配置,并不等同于开发者里面的强制桌面模式。

如果你想要在大/超大屏幕上使用安卓系统,freeform是比较合适的,虽然兼容适配特性还没有TV,phone等设备好,但实际在系统层面的支持已经有具备了相当的能力。

本文会介绍freeform模式的功能开启方式,在framework的代码实现细节和提供给用户和开发者使用的功能特性。

1. freeform功能开启

官方开启功能的方法是在 N上的使用的,后续的版本加入了复杂的多屏窗口逻辑,开启方法最好是参考AOSP里面开启freeform的模拟器的配置,以下是基于安卓12,参考goldfish的tablet freeform 模拟器配置,配置被WindowManagerService和ActivityTaskManagerService 读取后做实现,具体可以查代码逻辑。

  • services/core/java/com/android/server/wm/WindowManagerService.java

1Settings.Global.putString(resolver, Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, "1"); 2Settings.Global.putString(resolver, Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, "1"); 3Settings.Global.putString(resolver, Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH, "vendor/etc/display_settings_freeform.xml");
  • data/etc/android.software.freeform_window_management.xml

1<?xml version="1.0" encoding="utf-8"?> 2<!-- Copyright (C) 2015 The Android Open Source Project 34 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 78 http://www.apache.org/licenses/LICENSE-2.0 910 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15--> 1617<permissions> 18 <feature name="android.software.freeform_window_management" /> 19</permissions>
  • services/core/java/com/android/server/wm/ActivityTaskManagerService.java

1 public void retrieveSettings(ContentResolver resolver) { 2 Settings.Global.putInt(resolver, DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 1); 3 Settings.Global.putInt(resolver, DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 1);
  • display_settings_freeform.xml

1<?xml version='1.0' encoding='utf-8' standalone='yes' ?> 2<display-settings> 3<!-- Use physical port number instead of local id --> 4<config identifier="1" /> 56<!-- Set windowing mode to WINDOWING_MODE_FREEFORM --> 7<display name="port:0" 8 windowingMode="5" /> 910</display-settings>
  • overlay/frameworks/base/core/res/res/values/config.xml

1 <!-- The device supports freeform window management. Windows have title bars and can be moved 2 and resized. If you set this to true, you also need to add 3 PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT feature to your device specification. 4 The duplication is necessary, because this information is used before the features are 5 available to the system.--> 6 <bool name="config_freeformWindowManagement">true</bool>

device.mk里面修改

1PRODUCT_COPY_FILES += \ 2frameworks/native/data/etc/android.software.freeform_window_management.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.freeform_window_management.xml \ 3device/xxx/display_settings_freeform.xml:$(TARGET_COPY_OUT_VENDOR)/etc/display_settings_freeform.xml \

以上配置方法可以实现在不同设备上实现freeform,不局限于特定配置的应用,也不局限于特定配置的设备,效果如下:

2. freeform功能实现

activity其实提供了自由窗口的启动方式,系统应用可以直接调用,三方应用则可以通过反射方式(如果在高版本不行,建议自行研究黑科技绕过系统反射限制):

1 //freeform模式 2 private static final int WINDOWING_MODE_FREEFORM = 5; 34 //获取屏幕高宽 5 DisplayMetrics metric = new DisplayMetrics(); 6 getWindowManager().getDefaultDisplay().getMetrics(metric); 7 int screenWidth = metric.widthPixels; 8 int screenHeight = metric.heightPixels; 9 10 Intent intent = new Intent(MainActivity.this, FreeformActivity.class); 11 intent.setFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT | Intent.FLAG_ACTIVITY_NEW_TASK); 12 ActivityOptions activityOptions = ActivityOptions.makeBasic(); 13 //设置为freeform模式 14 try { 15 Method method = ActivityOptions.class.getMethod("setLaunchWindowingMode", int.class); 16 method.invoke(activityOptions, WINDOWING_MODE_FREEFORM); 17 } catch (Exception e) { 18 e.printStackTrace(); 19 } 20 //freeform模式下自由窗口的大小 21 int freeformWidth = 400; 22 int freeformHeight = 400; 23 //居中显示 24 int left = screenWidth / 2 - freeformWidth / 2; 25 int top = screenHeight / 2 - freeformHeight / 2; 26 int right = screenWidth / 2 + freeformWidth / 2; 27 int bottom = screenHeight / 2 + freeformHeight / 2; 28 activityOptions.setLaunchBounds(new Rect(left,top,right,bottom)); 29 Bundle bundle = activityOptions.toBundle(); 30 startActivity(intent,bundle); 31这是单个activity实现freeform的方式,如何实现所有activity,需要检查WMS和ATMS代码逻辑。当WMS和ATMS保存freeform的配置的时候,实际是保存到settings数据库了,那数据在哪里使用。 32 33/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java 34 35 void updateFreeformWindowManagement() { 36 ContentResolver resolver = mContext.getContentResolver(); 37 final boolean freeformWindowManagement = mContext.getPackageManager().hasSystemFeature( 38 FEATURE_FREEFORM_WINDOW_MANAGEMENT) || Settings.Global.getInt( 39 resolver, DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0; 4041 if (mAtmService.mSupportsFreeformWindowManagement != freeformWindowManagement) { 42 mAtmService.mSupportsFreeformWindowManagement = freeformWindowManagement; 43 synchronized (mGlobalLock) { 44 // Notify the root window container that the display settings value may change. 45 mRoot.onSettingsRetrieved(); 46 } 47 } 48 }

在当前类freeformWindowManagement/mSupportsFreeformWindowManagement都没有地方使用,只是保存了数据库,让mRoot.onSettingsRetrieved();

  • /frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java

1 void onSettingsRetrieved() { 2 final int numDisplays = mChildren.size(); 3 for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { 4 final DisplayContent displayContent = mChildren.get(displayNdx); 5 final boolean changed = mWmService.mDisplayWindowSettings.updateSettingsForDisplay( 6 displayContent); 7 if (!changed) { 8 continue; 9 } 1011 displayContent.reconfigureDisplayLocked(); 1213 // We need to update global configuration as well if config of default display has 14 // changed. Do it inline because ATMS#retrieveSettings() will soon update the 15 // configuration inline, which will overwrite the new windowing mode. 16 if (displayContent.isDefaultDisplay) { 17 final Configuration newConfig = mWmService.computeNewConfiguration( 18 displayContent.getDisplayId()); 19 mWmService.mAtmService.updateConfigurationLocked(newConfig, null /* starting */, 20 false /* initLocale */); 21 } 22 } 23 }

遍历displaycontent,更新settings的数据,在函数updateSettingsForDisplay执行,而displaycontent就是display在framework层的数据结构,window属于display的下层数据结构,即一个系统可以有很多display(默认只有一个),有很多window都显示在这个display上,window的configuration都依赖于displaycontent。

先看一下mDisplayWindowSettings,他持有的SettingsProvider就是数据提供者,真正实现类是DisplayWindowSettingsProvider,描述如下:

1/** 2 * Implementation of {@link SettingsProvider} that reads the base settings provided in a display 3 * settings file stored in /vendor/etc and then overlays those values with the settings provided in 4 * /data/system. 5 * 6 * @see DisplayWindowSettings 7 */ 8class DisplayWindowSettingsProvider implements SettingsProvider { 9 private static final String TAG = TAG_WITH_CLASS_NAME 10 ? "DisplayWindowSettingsProvider" : TAG_WM; 1112 private static final String DATA_DISPLAY_SETTINGS_FILE_PATH = "system/display_settings.xml"; 13 private static final String VENDOR_DISPLAY_SETTINGS_FILE_PATH = "etc/display_settings.xml";

也就是说对display settings的初始化配置,可以在这两个文件里面修改和覆盖,然后在WMS初始化的时候也可以用代码动态设置,这是大的逻辑,当然具体能支持配置哪些参数,需要检查里面的代码。

updateSettingsForDisplay更新了display 的windowingmode, e.g. if this device is PC and if this device supports freeform。windowingmode即是描述显示模式。

1 /** 2     * Updates settings for the given display after system features are loaded into window manager 3     * service, e.g. if this device is PC and if this device supports freeform. 4     * 5     * @param dc the given display. 6     * @return {@code true} if any settings for this display has changed; {@code false} if nothing 7     * changed. 8     */ 9   boolean updateSettingsForDisplay(DisplayContent dc) { 10       if (dc.getWindowingMode() != getWindowingModeLocked(dc)) { 11           // For the time being the only thing that may change is windowing mode, so just update 12           // that. 13           dc.setWindowingMode(getWindowingModeLocked(dc)); 14           return true; 15       } 16       return false; 17   }

可以看到这个mSupportsFreeformWindowManagement,这里的判断是否支持,是否设置freeform,然后返回结果。这个逻辑是针对所有display的,当然也会针对所有window,activity。

1 private int getWindowingModeLocked(SettingsProvider.SettingsEntry settings, DisplayContent dc) { 2 int windowingMode = settings.mWindowingMode; 3 // This display used to be in freeform, but we don't support freeform anymore, so fall 4 // back to fullscreen. 5 if (windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM 6 && !mService.mAtmService.mSupportsFreeformWindowManagement) { 7 return WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 8 } 9 // No record is present so use default windowing mode policy. 10 if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) { 11 windowingMode = mService.mAtmService.mSupportsFreeformWindowManagement 12 && (mService.mIsPc || dc.forceDesktopMode()) 13 ? WindowConfiguration.WINDOWING_MODE_FREEFORM 14 : WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 15 } 16 return windowingMode; 17 }

对于新建的window,windowstate是一个window的数据结构。系列第一篇写到window在WMS和客户端的创建过程:

1WindowManager: WindowState:Window{18bd0d7 u0 Splash Screen com.android.messaging} 2WindowManager: java.lang.Throwable 3WindowManager: at com.android.server.wm.WindowState.<init>(WindowState.java:751) 4WindowManager: at com.android.server.wm.WindowManagerService.addWindow(WindowManagerService.java:1562) 5WindowManager: at com.android.server.wm.Session.addToDisplay(Session.java:159) 6WindowManager: at android.view.ViewRootImpl.setView(ViewRootImpl.java:923) 7WindowManager: at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:387) 8WindowManager: at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:96) 9WindowManager: at com.android.server.policy.PhoneWindowManager.addSplashScreen(PhoneWindowManager.java:2802) 10WindowManager: at com.android.server.wm.SplashScreenStartingData.createStartingSurface(SplashScreenStartingData.java:56) 11WindowManager: at com.android.server.wm.AppWindowToken$1.run(AppWindowToken.java:2258) 12WindowManager: at android.os.Handler.handleCallback(Handler.java:883) 13WindowManager: at android.os.Handler.dispatchMessage(Handler.java:100) 14WindowManager: at android.os.Looper.loop(Looper.java:221) 15WindowManager: at android.os.HandlerThread.run(HandlerThread.java:67) 16WindowManager: at com.android.server.ServiceThread.run(ServiceThread.java:44) 17 18WindowManager: WindowState:Window{5a02cc1 u0 com.android.messaging/com.android.messaging.ui.conversationlist.ConversationListActivity} 19WindowManager: java.lang.Throwable 20WindowManager: at com.android.server.wm.WindowState.<init>(WindowState.java:751) 21WindowManager: at com.android.server.wm.WindowManagerService.addWindow(WindowManagerService.java:1562) 22WindowManager: at com.android.server.wm.Session.addToDisplay(Session.java:159) 23WindowManager: at android.view.IWindowSession$Stub.onTransact(IWindowSession.java:525) 24WindowManager: at com.android.server.wm.Session.onTransact(Session.java:134) 25WindowManager: at android.os.Binder.execTransactInternal(Binder.java:1021) 26WindowManager: at android.os.Binder.execTransact(Binder.java:994)

window相关的配置保存在Configuration包括res,locale等等

1private final Configuration mTempConfiguration = new Configuration();

而window布局相关的配置在WindowConfiguration

1public final WindowConfiguration windowConfiguration = new WindowConfiguration();

比如WindowingMode

1/** The current windowing mode of the configuration. */ 2private @WindowingMode int mWindowingMode;

上文的reconfigureDisplayLocked也会填充WindowConfiguration的数据

1void reconfigureDisplayLocked() { 2   if (!isReady()) { 3       return; 4   } 5   configureDisplayPolicy(); 6   setLayoutNeeded(); 78   boolean configChanged = updateOrientation(); 9   final Configuration currentDisplayConfig = getConfiguration(); 10   mTmpConfiguration.setTo(currentDisplayConfig); 11   computeScreenConfiguration(mTmpConfiguration); 12   final int changes = currentDisplayConfig.diff(mTmpConfiguration); 13   configChanged |= changes != 0; 1415   if (configChanged) { 16       mWaitingForConfig = true; 17       if (mTransitionController.isShellTransitionsEnabled()) { 18           requestChangeTransitionIfNeeded(changes, null /* displayChange */); 19       } else if (mLastHasContent) { 20           mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */, this); 21       } 22       sendNewConfiguration(); 23   } 2425   mWmService.mWindowPlacerLocked.performSurfacePlacement(); 26}

当取出Configuration使用时,会把mTempConfiguration从全局Configuration来更新

1public Configuration getConfiguration() { 2   // If the process has not registered to any display area to listen to the configuration 3   // change, we can simply return the mFullConfiguration as default. 4   if (!registeredForDisplayAreaConfigChanges()) { 5       return super.getConfiguration(); 6   } 78   // We use the process config this window is associated with as the based global config since 9   // the process can override its config, but isn't part of the window hierarchy. 10   mTempConfiguration.setTo(getProcessGlobalConfiguration()); 11   mTempConfiguration.updateFrom(getMergedOverrideConfiguration()); 12   return mTempConfiguration; 13}

也就是拿的default display的配置

1/** 2 * Current global configuration information. Contains general settings for the entire system, 3 * also corresponds to the merged configuration of the default display. 4 */ 5Configuration getGlobalConfiguration() { 6   // Return default configuration before mRootWindowContainer initialized, which happens 7   // while initializing process record for system, see {@link 8   // ActivityManagerService#setSystemProcess}. 9   return mRootWindowContainer != null ? mRootWindowContainer.getConfiguration() 10           : new Configuration(); 11}

总的来说,windowingmode会保存到WMS和displaycontent,这样创建新的display和window的时候,都会使用配置的windowingmode。

3. freeform功能特性

这里部分介绍一下freeform模式与fullscreen模式相比独有的特性,这些特性部分工作得很好,也可以针对性的进行二次开发,更好的满足需求。

3.1 标题栏DecorCaptionView

我们知道所有的activity都有一个 PhoneWindow ,在创建的时候,会创建 DecorViewDecorView 会判断是否要创立一个 DecorCaptionView。 然后在 DecorView 中动态改变中,有参数变量,经过onWindowSystemUiVisibilityChanged(View的可见性改变) 以及 onConfigurationChanged(各种触发装备改变的条件) 的回调,对DecorView 进行更新是否要新建一个小窗的视图。

  • /frameworks/base/core/java/com/android/internal/policy/DecorView.java、

1private void updateDecorCaptionStatus(Configuration config) { 2   final boolean displayWindowDecor = config.windowConfiguration.hasWindowDecorCaption() 3           && !isFillingScreen(config); 4   if (mDecorCaptionView == null && displayWindowDecor) { 5       // Configuration now requires a caption. 6       final LayoutInflater inflater = mWindow.getLayoutInflater(); 7       mDecorCaptionView = createDecorCaptionView(inflater); 8       if (mDecorCaptionView != null) { 9           if (mDecorCaptionView.getParent() == null) { 10               addView(mDecorCaptionView, 0, 11                       new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); 12           } 13           removeView(mContentRoot); 14           mDecorCaptionView.addView(mContentRoot, 15                   new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT)); 16       } 17   } else if (mDecorCaptionView != null) { 18       // We might have to change the kind of surface before we do anything else. 19       mDecorCaptionView.onConfigurationChanged(displayWindowDecor); 20       enableCaption(displayWindowDecor); 21   } 22}
  • /frameworks/base/core/java/com/android/internal/widget/DecorCaptionView.java

1// True if the window is being dragged. 2   private boolean mDragging = false; 34   private boolean mOverlayWithAppContent = false; 56   private View mCaption; 7   private View mContent; 8   private View mMaximize; 9   private View mClose; 1011   // Fields for detecting drag events. 12   private int mTouchDownX; 13   private int mTouchDownY; 14   private boolean mCheckForDragging; 15   private int mDragSlop;

DecorCaptionView提供了标题,最大化,关闭,移动窗口等功能

1public boolean onSingleTapUp(MotionEvent e) { 2   if (mClickTarget == mMaximize) { 3       toggleFreeformWindowingMode(); 4   } else if (mClickTarget == mClose) { 5       mOwner.dispatchOnWindowDismissed( 6               true /*finishTask*/, false /*suppressWindowTransition*/); 7   } 8   return true; 9}

关闭和最大化功能的实现如上,移动窗口则是如下函数实现

1 case MotionEvent.ACTION_MOVE: 2 if (!mDragging && mCheckForDragging && (fromMouse || passedSlop(x, y))) { 3 mCheckForDragging = false; 4 mDragging = true; 5 startMovingTask(e.getRawX(), e.getRawY()); 6 // After the above call the framework will take over the input. 7 // This handler will receive ACTION_CANCEL soon (possible after a few spurious 8 // ACTION_MOVE events which are safe to ignore). 9 } 10 break;

startMovingTask不单需要使用的DecorCaptionview上,如果当前是小窗模式,同时也没有显示DecorCaptionview,应用的其他view也可能需要移动整个应用,这时候就需要使用这个函数。

1public final boolean startMovingTask(float startX, float startY) { 2   if (ViewDebug.DEBUG_POSITIONING) { 3       Log.d(VIEW_LOG_TAG, "startMovingTask: {" + startX + "," + startY + "}"); 4   } 5   try { 6       return mAttachInfo.mSession.startMovingTask(mAttachInfo.mWindow, startX, startY); 7   } catch (RemoteException e) { 8       Log.e(VIEW_LOG_TAG, "Unable to start moving", e); 9   } 10   return false; 11}

3.2 窗口边框ViewOutlineProvider

目前国内的厂商的小窗口,都是依照DecorCaptionView自定义了一个view,来包裹在外面实现边框等的美化。

比如制作边框圆角,是经过 View 的 setOutlineProvider,提供了一个android.view.ViewOutlineProvider,来设置android.graphics.Outline实现圆角或者其他模式。

1@Retention(RetentionPolicy.SOURCE) 2@IntDef(flag = false, 3         value = { 4           MODE_EMPTY, 5           MODE_ROUND_RECT, 6           MODE_PATH, 7       }) 8public @interface Mode {}

3.3 全局显示策略DisplayPolicy

作为系统里边操控显现,dock栏、状况栏、导航栏的风格的首要类, 在每次制作布局之后,都会走到如下applyPostLayoutPolicyLw 函数,进行显示规则堆叠之后,在导航栏更新透明度的时候,将其标记中不透明的深色背景和淡色背景清空。

1// Check if the freeform window overlaps with the navigation bar area. // 查看自在窗口是否与导航栏区域堆叠。 2if (!mIsFreeformWindowOverlappingWithNavBar && win.inFreeformWindowingMode() 3     && win.mActivityRecord != null && isOverlappingWithNavBar(win)) { // 假如窗口是自在窗口,而且窗口和导航栏堆叠 4      mIsFreeformWindowOverlappingWithNavBar = true; 5}

/frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java

1 /** 2     * @return the current visibility flags with the nav-bar opacity related flags toggled based 3     *         on the nav bar opacity rules chosen by {@link #mNavBarOpacityMode}. 4     */ 5   private int configureNavBarOpacity(int appearance, boolean multiWindowTaskVisible, 6           boolean freeformRootTaskVisible) { 7       final boolean drawBackground = drawsBarBackground(mNavBarBackgroundWindow); 8       if (mNavBarOpacityMode == NAV_BAR_FORCE_TRANSPARENT) { 9           if (drawBackground) { 10               appearance = clearNavBarOpaqueFlag(appearance); 11           } 12       } else if (mNavBarOpacityMode == NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED) { 13           if (multiWindowTaskVisible || freeformRootTaskVisible) { 14               if (mIsFreeformWindowOverlappingWithNavBar) { // 假如自在窗口和导航栏堆叠 15                   appearance = clearNavBarOpaqueFlag(appearance); // 使导航栏变成不透明的纯深色背景和淡色背景。 16               }

更新高度和背景阴影

1 private void updateElevation() { 2       final int windowingMode = 3               getResources().getConfiguration().windowConfiguration.getWindowingMode(); 4       final boolean renderShadowsInCompositor = mWindow.mRenderShadowsInCompositor; 5       // If rendering shadows in the compositor, don't set an elevation on the view // 假如在合成器中烘托暗影,请不要在视图上设置高度 6       if (renderShadowsInCompositor) { 7           return; 8       } 9       float elevation = 0; 10       final boolean wasAdjustedForStack = mElevationAdjustedForStack; 11       // Do not use a shadow when we are in resizing mode (mBackdropFrameRenderer not null) 12       // since the shadow is bound to the content size and not the target size. // 当咱们处于调整巨细办法(mBackdropFrameRenderer不为空)时不要运用暗影,由于暗影绑定到内容巨细而不是方针巨细。 13       if ((windowingMode == WINDOWING_MODE_FREEFORM) && !isResizing()) { 14           elevation = hasWindowFocus() ? 15                   DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP : DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP; // 假如有焦点,则为DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP=20,否则为DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP=5 16           // Add a maximum shadow height value to the top level view. 17           // Note that pinned stack doesn't have focus 18           // so maximum shadow height adjustment isn't needed. // 为顶级视图增加最大暗影高度值。注意,固定仓库没有焦点,因而不需求最大暗影高度调整。 19           // TODO(skuhne): Remove this if clause once b/22668382 got fixed. 20           if (!mAllowUpdateElevation) { 21               elevation = DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP; 22           } 23           // Convert the DP elevation into physical pixels. 24           elevation = dipToPx(elevation); 25           mElevationAdjustedForStack = true; 26       }

3.4 拖拽DragState和窗口层级Dimmer(此部分内容待验证,仅做记录)

借助 Android 拖放框架,您可以向应用中添加交互式拖放功能。通过拖放,用户可以在应用中的 View 之间复制或拖动文本、图片、对象(可以通过 URI 表示的任何内容),也可以使用多窗口模式在应用之间拖动这些内容。

https://developer.android.google.cn/guide/topics/ui/drag-drop?hl=en

DragState 类主要用于管理和跟踪拖拽操作的状态。拖拽操作通常涉及用户在屏幕上拖动某个视图或对象,并在特定位置释放它。以下是 DragState 类的主要作用和功能:

  1. 管理拖拽动作的状态:DragState 类负责跟踪拖拽操作的各个状态,例如开始拖拽、拖拽中、拖拽结束等。它可以存储和更新与拖拽状态相关的信息。

  2. 处理拖拽手势:这个类可能包含了处理用户拖拽手势的逻辑,例如监测用户手指的移动、计算拖拽物体的位置、响应用户的拖拽操作等。

  3. 协调拖拽操作DragState 类可能与其他系统组件(如窗口管理器或视图系统)协同工作,以确保拖拽操作在屏幕上正确执行。它可能需要调整被拖拽物体的位置,更新UI,或者触发其他操作。

  4. 提供拖拽状态信息:这个类通常会提供有关拖拽状态的信息,例如拖拽物体的位置、拖拽的类型、拖拽的源对象等。这些信息可以供其他组件使用。

  5. 处理拖拽的释放:当用户释放拖拽物体时,DragState 类可能需要执行特定的操作,例如将拖拽物体放置在新的位置、触发操作、或者完成拖拽操作。

  6. 支持拖拽的可视化效果:在一些情况下,DragState 类可能需要管理与拖拽相关的可视化效果,例如拖拽物体的影子或者拖拽物体的预览。

1DragState 2   notifyLocationLocked(float, float) 3           WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y); 4   notifyDropLocked(float, float) 5           final WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y); 6WindowManagerService 7   updatePointerIcon(IWindow) 8                       displayContent.getTouchableWinAtPointLocked(mouseX, mouseY); 9   restorePointerIconLocked(DisplayContent, float, float) 10                   displayContent.getTouchableWinAtPointLocked(latestX, latestY); 11

Dimmer 类的作用是为实现窗口容器(WindowContainer)添加“调暗层”(DimLayer)支持,通过在不同的 Z 层级上创建具有不同透明度的黑色层,从而实现调暗的效果。该类主要用于在窗口容器中管理和应用调暗效果。

该类的主要方法和功能如下:

  • dimAbovedimBelow 方法 这些方法用于在指定的窗口容器上方或下方添加调暗层。可以设置调暗层的透明度(alpha)和相对于指定容器的 Z 层级。

  • resetDimStates 该方法标记所有的调暗状态为等待下一次调用 updateDims 时完成。在调用子容器的 prepareSurfaces 之前调用此方法,以允许子容器继续请求保持调暗。

  • updateDims 方法 在调用子容器的 prepareSurfaces 后,通过此方法来更新调暗层的位置和尺寸。根据容器的更新,它可以设置调暗层的位置、大小以及调整动画。

  • stopDim 方法 用于停止调暗效果,可以在不再需要调暗时调用。

  • 内部类 DimState 表示调暗状态的内部类,包含调暗层的 SurfaceControl、调暗状态等信息。

Dimmer 类用于管理和应用调暗效果,使窗口容器在不同 Z 层级上添加透明度变化的调暗层,以达到调暗的效果。 在 Android 系统中用于处理窗口切换、过渡动画以及调整显示效果时很有用, 如果要对小窗进行圆角的处理,这一层也是要处理的, 不然会出现黑色矩形边的问题。

其他需要适配的特性,此外还有有一些其他特性,需要开发者二次开发,才能更符合用户使用习惯。

3.5 窗口尺寸TaskLaunchParamsModifier

修改ActivityOptions里面修改activity显示的区域,最终在TaskLaunchParamsModifier生效,com.android.server.wm.TaskLaunchParamsModifier#onCalculate才是最终控制activity窗口显示各种具体数据的运算逻辑。

3.6 前后台状态ActivityStack

我们知道Android系统对activity的管理非常严格,这种严格有时候让有一些基本能力都具备,既可以理解成安全性考虑,也可以理解成谷歌犯懒,只提供需要的API接口。这种严格体现在activity的前后台状态时,你会发现获得这个状态是非常难的,从系统端到应用端完全不具备这个能力。

3.7 AppTransition

修改窗口弹出来和关闭的动画来适配桌面模式,com.android.server.wm.AppTransition#loadAnimation里面定义了各种应用进出的动画,如果不对其进行适配,窗口模式应用的进出动画会按照原先手机模式来运行,并不匹配。

3.8 内存

比如JVM内存,lmkd参数最好是进行相应的调整,否则运行效率肯定不会让你满意。

总结

freeform模式控制的是窗口,他让应用像运行在桌面一样,显示区域可以自行控制,这本身就是Android系统可以实现的功能,只是窗口的交互逻辑还不完善。实现方案从效率来看也不够优秀,Java开发的图形程序可以满足基本的使用需求,但是如果作为大屏操作系统,他的运行效率、安全性,硬件适配能力是无法逾越的鸿沟,在窗口这部分核心架构使用这么久的情况下,如果没有更复杂的场景,大可能也不会进行修改了,所以在此基础上优化和发展Android的多屏多窗口能力有更多的挑战。