Android 12体系源码_多屏幕(四)自由窗口模式

打印 上一主题 下一主题

主题 1580|帖子 1580|积分 4740

一、小窗模式

1.1 小窗功能的开启方式



  • 开发者模式下开启小窗功能

  • adb 手动开启
  1. adb shell settings put global enable_freeform_support  1
  2. adb shell settings put global force_resizable_activities  1
复制代码
1.2 源码设置



  • copy file
  1. # add for freedom
  2. PRODUCT_COPY_FILES += \
  3.    frameworks/native/data/etc/android.software.freeform_window_management.xml:$(TARGET_COPY_OUT_SYSTEM)/etc/permissions/android.software.freeform_window_management.xml
复制代码


  • overlay
  1.     <!-- add for freeform -->
  2.     <bool name="config_freeformWindowManagement">true</bool>
复制代码
1.3 小窗的启动方式

重要的启动方式,一个是多使命里面,点击应用图标,选择小窗模式,另一个是通过三方应用启动,比如侧边栏,关照栏等待。


  • 三方应用通过ActivityOptions 启动
  1.     public void startFreeFormActivity(View view) {
  2.         Intent intent = new Intent(this, FreeFormActivity.class);
  3.         ActivityOptions options = ActivityOptions.makeBasic();
  4.         options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
  5.         startActivity(intent, options.toBundle());
  6.     }
复制代码


  • 多使命启动
    通过长按应用图标,选择小窗模式,ActivityOptions 会设置 ActivityOptions#setLaunchWindowingMode 为 WINDOWING_MODE_FREEFORM,然后通过 ActivityManager#startActivity 启动 Activity。
   frameworks/base/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
  1. public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
  2.     int startActivityFromRecents(int callingPid, int callingUid, int taskId,
  3.             SafeActivityOptions options) {
  4.             ...代码省略...        
  5.     }
  6. }
复制代码
1.4 应用兼容

应用需要设置 android:resizeableActivity=“true”,应用安装过程中会解析AndroidManifest.xml,并设置 PackageParser.ActivityInfo 的 privateFlags,在启动应用的时候,会根据 privateFlags 的值来判断是否支持小窗。
  1.         if (sa.hasValueOrEmpty(R.styleable.AndroidManifestApplication_resizeableActivity)) {
  2.             if (sa.getBoolean(R.styleable.AndroidManifestApplication_resizeableActivity, true)) {
  3.                 ai.privateFlags |= PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE;
  4.             } else {
  5.                 ai.privateFlags |= PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE;
  6.             }
  7.         } else if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.N) {
  8.             ai.privateFlags |= PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
  9.         }
复制代码
1.5 基础的窗口信息

在Android 体系中,窗口是应用程序界面的基本单位,用于承载和表现应用的视图内容。每个 Activity 都有一个主窗口,但也可以有其他窗口,如对话框、悬浮窗等。
在应用上层的一些组件的体现上,厘革不是很大,但是 Framework 中的对于窗口的管理,迭代厘革不停都比较大,可能之前有的类,新的架构下就被精简了,所以重要掌握窗口的一些概念,这样比较轻易在新的架构之下找到对应的实现。
这里先回顾一下基础的窗口和界面相关的概念:


  • Window(窗口): 窗口是应用程序界面的基本单位,用于承载和表现应用的视图内容。每个 Activity 都有一个主窗口,但也可以有其他窗口,如对话框、悬浮窗等。
  • WindowManagerService: 是 Android 体系中的窗口管理服务,负责管理窗口的创建、表现、移动、调解大小、层级关系等。它是 Android 窗口体系的焦点组件。
  • View(视图): View 是 Android 中用户界面的基本构建块,用于在窗口中绘制和表现内容。它是窗口中可见元素的基础。
  • ViewGroup(视图组): ViewGroup 是一种特殊的 View,它可以包含其他视图(包括 View 和其他 ViewGroup)来形成复杂的用户界面。
  • Surface(表面): Surface 是用于绘制图形内容的地区,窗口和视图内容都可以在 Surface 上绘制。每个窗口通常对应一个 Surface。
  • SurfaceFlinger: 是 Android 体系中的一个组件,负责管理和合成窗口中的 Surface,以及在屏幕上绘制这些 Surface。
  • LayoutParams(布局参数): LayoutParams 是窗口或视图的布局参数,用于指定视图在其父视图中的位置、大小和外观等。
  • Window Token(窗口令牌): Window Token 是一个用于标识窗口所属于的应用程序或使命的对象。它在窗口的表现和交互中起着告急作用。
  • Window Decor(窗口装饰): Window Decor 是窗口的装饰元素,如标题栏、状态栏等,可以影响窗口的外观和交互。
  • Dialog(对话框): 对话框是一种特殊的窗口,用于在当前活动之上表现暂时的提示、选择或输入内容。
  • Activity(活动): 在 Android 应用程序中,每个 Activity 都与一个 PhoneWindow 相关联。PhoneWindow 用于管理 Activity 的界面绘制和交互。
  • Window Callback(窗口回调): PhoneWindow 实现了 Window.Callback 接口,该接口用于处理窗口变乱和交互。通过实现这个接口,您可以监听和响应窗口的状态厘革、输入变乱等。
  • DecorView(装饰视图): PhoneWindow 中的内容通常由 DecorView 承载。DecorView 是一个特殊的 ViewGroup,用于包含应用程序的用户界面内容和窗口装饰元素,如标题栏、状态栏等。
  • PhoneWindow(应用程序窗口): PhoneWindow 是 android.view.Window 类的实现之一,用于表现一个应用程序窗口。它提供了窗口的基本功能,如绘制、布局、装饰、焦点管理等。
1.6 窗口范例

   frameworks/base/core/java/android/app/WindowConfiguration.java
  1. public class WindowConfiguration implements Parcelable, Comparable<WindowConfiguration> {
  2.     public static final int WINDOWING_MODE_UNDEFINED = 0;
  3.     public static final int WINDOWING_MODE_FULLSCREEN = 1;//全屏窗口模式
  4.     public static final int WINDOWING_MODE_PINNED = 2;//固定窗口模式
  5.     public static final int WINDOWING_MODE_SPLIT_SCREEN_PRIMARY = 3;
  6.     public static final int WINDOWING_MODE_SPLIT_SCREEN_SECONDARY = 4;
  7.     public static final int WINDOWING_MODE_FREEFORM = 5;//自由窗口模式
  8.     public static final int WINDOWING_MODE_MULTI_WINDOW = 6;//多窗口模式
  9.     /** @hide */
  10.     @IntDef(prefix = { "WINDOWING_MODE_" }, value = {
  11.             WINDOWING_MODE_UNDEFINED,
  12.             WINDOWING_MODE_FULLSCREEN,
  13.             WINDOWING_MODE_MULTI_WINDOW,
  14.             WINDOWING_MODE_PINNED,
  15.             WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
  16.             WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
  17.             WINDOWING_MODE_FREEFORM,
  18.     })
  19.     public @interface WindowingMode {}
  20. }
复制代码
二、小窗的创建

小窗的创建机会有两块,一块是在页面创建的时候,也就是PhoneWindow创建的时候,会创建DecorView,DecorView 会判断是否要建立一个 DecorCaptionView。 别的一块当DecorView动态厘革的时候,当有参数变量,经过onWindowSystemUiVisibilityChanged方法回调大概 onConfigurationChanged方法回调,体系会对DecorView 举行更新并判断是否需要新建一个小窗DecorCaptionView的视图。
2.1 页面创建的时候创建小窗

  1. >frameworks/base/core/java/android/app/Activity.java
  2. public class Activity extends ContextThemeWrapper
  3.         implements LayoutInflater.Factory2,
  4.         Window.Callback, KeyEvent.Callback,
  5.         OnCreateContextMenuListener, ComponentCallbacks2,
  6.         Window.OnWindowDismissedCallback,
  7.         ContentCaptureManager.ContentCaptureClient {
  8.    
  9.     public void setContentView(View view) {
  10.             //注释1,调用PhoneWindow的setContentView方法
  11.         getWindow().setContentView(view);
  12.         initWindowDecorActionBar();
  13.     }
  14.   }
  15. >frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java  
  16. public class PhoneWindow extends Window implements MenuBuilder.Callback {
  17.    
  18.     private DecorView mDecor;
  19.     ViewGroup mContentParent;
  20.     @Override
  21.     public void setContentView(View view, ViewGroup.LayoutParams params) {
  22.         if (mContentParent == null) {
  23.                 //注释2,调用installDecor
  24.             installDecor();
  25.         }
  26.         ...代码省略...
  27.     }
  28.     private void installDecor() {
  29.        ...代码省略...
  30.        //注释3,创建窗口对应的DecorView视图
  31.        mDecor = generateDecor(-1);
  32.        ...代码省略...
  33.        //注释4,调用generateLayout方法
  34.        mContentParent = generateLayout(mDecor);            
  35.         }
  36.        
  37.     protected ViewGroup generateLayout(DecorView decor) {
  38.         ...代码省略...
  39.         //注释5,调用DecorView的onResourcesLoaded方法
  40.         mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);  
  41.         ...代码省略...         
  42.     }
  43. }
  44. >frameworks/base/core/java/com/android/internal/policy/DecorView.java
  45. public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
  46.         //加载应用需要的布局资源
  47.     void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
  48.         ...代码省略...
  49.         //创建小窗视图
  50.         mDecorCaptionView = createDecorCaptionView(inflater);
  51.         //应用需要加载的根布局
  52.         final View root = inflater.inflate(layoutResource, null);
  53.         if (mDecorCaptionView != null) {
  54.             //注释6,如果小窗视图不为空,则将小窗的视图添加到DecorView中
  55.             if (mDecorCaptionView.getParent() == null) {
  56.                 addView(mDecorCaptionView,new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
  57.             }
  58.             //然后将应用根布局添加到mDecorCaptionView中
  59.             mDecorCaptionView.addView(root,
  60.                     new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
  61.         } else {
  62.                 //注释7,如果不是小窗,则将应用根布局添加到DecorView中
  63.             addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
  64.         }
  65.         mContentRoot = (ViewGroup) root;
  66.         initializeElevation();
  67.     }
  68.     private DecorCaptionView createDecorCaptionView(LayoutInflater inflater) {
  69.         DecorCaptionView decorCaptionView = null;
  70.         for (int i = getChildCount() - 1; i >= 0 && decorCaptionView == null; i--) {
  71.             View view = getChildAt(i);
  72.             if (view instanceof DecorCaptionView) {
  73.                 // The decor was most likely saved from a relaunch - so reuse it.
  74.                 decorCaptionView = (DecorCaptionView) view;
  75.                 removeViewAt(i);
  76.             }
  77.         }
  78.         final WindowManager.LayoutParams attrs = mWindow.getAttributes();
  79.         final boolean isApplication = attrs.type == TYPE_BASE_APPLICATION ||
  80.                 attrs.type == TYPE_APPLICATION || attrs.type == TYPE_DRAWN_APPLICATION;
  81.         final WindowConfiguration winConfig = getResources().getConfiguration().windowConfiguration;
  82.         // Only a non floating application window on one of the allowed workspaces can get a caption
  83.         if (!mWindow.isFloating() && isApplication && winConfig.hasWindowDecorCaption()) {
  84.             // Dependent on the brightness of the used title we either use the
  85.             // dark or the light button frame.
  86.             if (decorCaptionView == null) {
  87.                     //调用inflateDecorCaptionView方法
  88.                 decorCaptionView = inflateDecorCaptionView(inflater);
  89.             }
  90.             decorCaptionView.setPhoneWindow(mWindow, true /*showDecor*/);
  91.         } else {
  92.             decorCaptionView = null;
  93.         }
  94.         enableCaption(decorCaptionView != null);
  95.         return decorCaptionView;
  96.     }
  97.    
  98.     private DecorCaptionView inflateDecorCaptionView(LayoutInflater inflater) {
  99.         final Context context = getContext();
  100.         inflater = inflater.from(context);
  101.         //注释8,构建小窗对应的布局文件
  102.         final DecorCaptionView view = (DecorCaptionView) inflater.inflate(R.layout.decor_caption,
  103.                 null);
  104.         setDecorCaptionShade(view);
  105.         return view;
  106.     }
  107. }
复制代码
如上所示Activity的setContentView方法经过层层调用最终会触发DecorView的onResourcesLoaded方法,假如是小窗模式,会走到解释6处,将页面需要的布局资源添加到DecorCaptionView中,然后将DecorCaptionView添加到DecorView中;假如是普通应用场景,会走到解释7处,会将布局资源添加到DecorView中;最终WMS会将DecorView添加到屏幕上。别的在解释8处可以看到小窗加载的是什么布局资源。
2.2 在DecorView的回调方法中创建小窗

  1. public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
  2.     @Override
  3.     public void onWindowSystemUiVisibilityChanged(int visible) {
  4.         updateColorViews(null /* insets */, true /* animate */);
  5.         //调用updateDecorCaptionStatus方法更新小窗的状态
  6.         updateDecorCaptionStatus(getResources().getConfiguration());
  7.         if (mStatusGuard != null && mStatusGuard.getVisibility() == VISIBLE) {
  8.             updateStatusGuardColor();
  9.         }
  10.     }
  11.     @Override
  12.     protected void onConfigurationChanged(Configuration newConfig) {
  13.         super.onConfigurationChanged(newConfig);
  14.         //调用updateDecorCaptionStatus方法更新小窗的状态
  15.         updateDecorCaptionStatus(newConfig);
  16.         initializeElevation();
  17.     }
  18.     private void updateDecorCaptionStatus(Configuration config) {
  19.         //如果定义窗口类型为小窗,且不是全屏模式, 则创建一个DecorCaptionView在DecorView内部。
  20.         final boolean displayWindowDecor = config.windowConfiguration.hasWindowDecorCaption()
  21.                 && !isFillingScreen(config);
  22.         if (mDecorCaptionView == null && displayWindowDecor) {
  23.             // Configuration now requires a caption.
  24.             final LayoutInflater inflater = mWindow.getLayoutInflater();
  25.             //注释1,调用createDecorCaptionView方法创建小窗视图
  26.             mDecorCaptionView = createDecorCaptionView(inflater);
  27.             if (mDecorCaptionView != null) {
  28.                 if (mDecorCaptionView.getParent() == null) {
  29.                     addView(mDecorCaptionView, 0,
  30.                             new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
  31.                 }
  32.                 removeView(mContentRoot);
  33.                 mDecorCaptionView.addView(mContentRoot,
  34.                         new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
  35.             }
  36.         } else if (mDecorCaptionView != null) {
  37.             //注释2,如果已经创建了 DecorCaptionView, 则更新配置信息,比如窗口大小,窗口位置等。
  38.             mDecorCaptionView.onConfigurationChanged(displayWindowDecor);
  39.             // 是否显示小窗的标题栏
  40.             enableCaption(displayWindowDecor);
  41.         }
  42.     }
  43. }
复制代码
在DecorView的onWindowSystemUiVisibilityChanged回调方法和onConfigurationChanged回调方法中,都会进一步调用updateDecorCaptionStatus方法;然后假如判断需要创建小窗且小窗还未被创建,则在解释1处,会调用createDecorCaptionView方法创建小窗视图;否则在解释2处,调用DecorCaptionView的onConfigurationChanged方法关照小窗举行设置厘革。
三、小窗的实现

2.2 小窗的标题栏

   frameworks/base/core/java/com/android/internal/widget/DecorCaptionView.java
  1. public class DecorCaptionView extends ViewGroup implements View.OnTouchListener,
  2.         GestureDetector.OnGestureListener {
  3.     private View mCaption;  // 标题栏
  4.     private View mContent; // 小窗View之下,应用的根View
  5.     private View mMaximize; // 最大化按钮
  6.     private View mClose; // 关闭按钮
  7. }
复制代码
2.3 触摸变乱

  1. public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
  2.         //如果在应用的小窗View外部点击的话,直接将事件拦截掉,这样就不会触发应用的点击事件。
  3.     @Override
  4.     public boolean onInterceptTouchEvent(MotionEvent event) {
  5.         int action = event.getAction();
  6.         //是否显示小窗标题栏
  7.         if (mHasCaption && isShowingCaption()) {
  8.         //如果窗口可调整大小,并且事件是(开始)在小窗外部,则不要将 ACTION_DOWN 事件进行传递。窗口调整大小事件应由WindowManager处理。
  9.             if (action == MotionEvent.ACTION_DOWN) {
  10.                 final int x = (int) event.getX();
  11.                 final int y = (int) event.getY();
  12.                 if (isOutOfInnerBounds(x, y)) {
  13.                     return true;
  14.                 }
  15.             }
  16.         }
  17.                 ...代码省略...
  18.         return false;
  19.     }
  20. }
复制代码
  frameworks/base/core/java/com/android/internal/widget/DecorCaptionView.java
  1. public class DecorCaptionView extends ViewGroup implements View.OnTouchListener,
  2.         GestureDetector.OnGestureListener {
  3.         
  4.     private View mClickTarget;
  5.    
  6.     @Override
  7.     public boolean onInterceptTouchEvent(MotionEvent ev) {
  8.         //如果用户点击最大化或者关闭按钮,就拦截事件,这样就不会触发应用的点击事件
  9.         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
  10.             final int x = (int) ev.getX();
  11.             final int y = (int) ev.getY();
  12.             //Only offset y for containment tests because the actual views are already translated.
  13.             //是否点击到最大化按钮的区域
  14.             if (mMaximizeRect.contains(x, y - mRootScrollY)) {
  15.                 mClickTarget = mMaximize;
  16.             }
  17.             //是否点击到关闭按钮的区域
  18.             if (mCloseRect.contains(x, y - mRootScrollY)) {
  19.                 mClickTarget = mClose;
  20.             }
  21.         }
  22.         return mClickTarget != null;
  23.     }
  24. }
复制代码
在小窗的View中,应用就不能通过View的变乱来直接处理,DecorCaptionView 需要通过计算View地点的矩形地区,然后计算点击的地区是否处于该矩形地区范围内判断为点击,就是一个点和面的问题,原生小窗上的问题就是这个触控面太小,手指的点击地区可能不轻易触发。 (在我开发的ChatDev游戏中,也有面和面碰撞的问题,玩家的位置和碰撞位置的计算)
2.4 小窗的界限

现在国内的厂商的小窗设计,都是通过在DecorView里面模拟DecorCaptionView自定义一个View,用来作为小窗内部应用的容器。


  • 界限圆角 一样平常会对这个小窗容器举行一个UI上的美化,重要的一个就是界限的圆角轮廓绘制。
  • 导航栏重叠的问题
DisplayPolicy 作为体系里面控制表现 dock栏、状态栏、导航栏的样式的重要类, 在每次绘制布局之后,都会走到如下applyPostLayoutPolicyLw 函数,举行表现规则的条件,当判断重叠之后,在导航栏更新透明度规则的时候,将其标记中不透明的纯深色背景和淡色前景清空。
  1. public class DisplayPolicy {
  2.     public void applyPostLayoutPolicyLw(WindowState win, WindowManager.LayoutParams attrs,
  3.             WindowState attached, WindowState imeTarget) {
  4.         final boolean affectsSystemUi = win.canAffectSystemUiFlags();
  5.         if (DEBUG_LAYOUT) Slog.i(TAG, "Win " + win + ": affectsSystemUi=" + affectsSystemUi);
  6.         applyKeyguardPolicy(win, imeTarget);
  7.         // 检查自由窗口是否与导航栏区域重叠。
  8.         final boolean isOverlappingWithNavBar = isOverlappingWithNavBar(win);
  9.         if (isOverlappingWithNavBar && !mIsFreeformWindowOverlappingWithNavBar
  10.                 && win.inFreeformWindowingMode()) {// 如果窗口是自由窗口,并且窗口和导航栏重叠
  11.             mIsFreeformWindowOverlappingWithNavBar = true;
  12.         }
  13.         if (!affectsSystemUi) {
  14.             return;
  15.         }
  16.                 ...代码省略...
  17.     }
  18. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

渣渣兔

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表