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

news/2025/2/27 4:16:45

一、小窗模式

1.1 小窗功能的开启方式

  • 开发者模式下开启小窗功能
    在这里插入图片描述
  • adb 手动开启
adb shell settings put global enable_freeform_support  1
adb shell settings put global force_resizable_activities  1

1.2 源码配置

  • copy file
# add for freedom
PRODUCT_COPY_FILES += \
   frameworks/native/data/etc/android.software.freeform_window_management.xml:$(TARGET_COPY_OUT_SYSTEM)/etc/permissions/android.software.freeform_window_management.xml
  • overlay
    <!-- add for freeform -->
    <bool name="config_freeformWindowManagement">true</bool>

1.3 小窗的启动方式

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

  • 三方应用通过ActivityOptions 启动
    public void startFreeFormActivity(View view) {
        Intent intent = new Intent(this, FreeFormActivity.class);
        ActivityOptions options = ActivityOptions.makeBasic();
        options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
        startActivity(intent, options.toBundle());
    }
  • 多任务启动
    通过长按应用图标,选择小窗模式,ActivityOptions 会设置 ActivityOptions#setLaunchWindowingMode 为 WINDOWING_MODE_FREEFORM,然后通过 ActivityManager#startActivity 启动 Activity。

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

public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
    int startActivityFromRecents(int callingPid, int callingUid, int taskId,
            SafeActivityOptions options) {
    	...代码省略...        
    }
}

1.4 应用兼容

应用需要设置 android:resizeableActivity=“true”,应用安装过程中会解析AndroidManifest.xml,并设置 PackageParser.ActivityInfo 的 privateFlags,在启动应用的时候,会根据 privateFlags 的值来判断是否支持小窗。

        if (sa.hasValueOrEmpty(R.styleable.AndroidManifestApplication_resizeableActivity)) {
            if (sa.getBoolean(R.styleable.AndroidManifestApplication_resizeableActivity, true)) {
                ai.privateFlags |= PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE;
            } else {
                ai.privateFlags |= PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE;
            }
        } else if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.N) {
            ai.privateFlags |= PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
        }

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

public class WindowConfiguration implements Parcelable, Comparable<WindowConfiguration> {
    public static final int WINDOWING_MODE_UNDEFINED = 0;
    public static final int WINDOWING_MODE_FULLSCREEN = 1;//全屏窗口模式
    public static final int WINDOWING_MODE_PINNED = 2;//固定窗口模式
    public static final int WINDOWING_MODE_SPLIT_SCREEN_PRIMARY = 3;
    public static final int WINDOWING_MODE_SPLIT_SCREEN_SECONDARY = 4;
    public static final int WINDOWING_MODE_FREEFORM = 5;//自由窗口模式
    public static final int WINDOWING_MODE_MULTI_WINDOW = 6;//多窗口模式

    /** @hide */
    @IntDef(prefix = { "WINDOWING_MODE_" }, value = {
            WINDOWING_MODE_UNDEFINED,
            WINDOWING_MODE_FULLSCREEN,
            WINDOWING_MODE_MULTI_WINDOW,
            WINDOWING_MODE_PINNED,
            WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
            WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
            WINDOWING_MODE_FREEFORM,
    })
    public @interface WindowingMode {}
}

二、小窗视图的创建

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback,
        ContentCaptureManager.ContentCaptureClient {
    
    public void setContentView(View view) {
    	//注释1,调用PhoneWindow的setContentView方法
        getWindow().setContentView(view);
        initWindowDecorActionBar();
    }
  }
  
public class PhoneWindow extends Window implements MenuBuilder.Callback {
    
    private DecorView mDecor;
    ViewGroup mContentParent;

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        if (mContentParent == null) {
        	//注释2,调用installDecor
            installDecor();
        } 
        ...代码省略...
    }
    private void installDecor() {
       ...代码省略...
       //注释3,创建窗口对应的DecorView视图
       mDecor = generateDecor(-1);
       ...代码省略...
       //注释4,调用generateLayout方法
       mContentParent = generateLayout(mDecor);            
	}
	
    protected ViewGroup generateLayout(DecorView decor) {
        ...代码省略...
        //注释5,调用DecorView的onResourcesLoaded方法
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);  
        ...代码省略...          
    }
}

 >frameworks/base/core/java/com/android/internal/policy/DecorView.java
```java
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
	//加载应用需要的布局资源
    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        ...代码省略...
        //创建小窗视图
        mDecorCaptionView = createDecorCaptionView(inflater);
        //应用需要加载的根布局
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) {
            //注释6,如果小窗视图不为空,则将小窗的视图添加到DecorView中
            if (mDecorCaptionView.getParent() == null) {
                addView(mDecorCaptionView,new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            //然后将应用根布局添加到mDecorCaptionView中
            mDecorCaptionView.addView(root,
                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {
        	//注释7,如果不是小窗,则将应用根布局添加到DecorView中
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
        initializeElevation();
    }
    
    private DecorCaptionView createDecorCaptionView(LayoutInflater inflater) {
        DecorCaptionView decorCaptionView = null;
        for (int i = getChildCount() - 1; i >= 0 && decorCaptionView == null; i--) {
            View view = getChildAt(i);
            if (view instanceof DecorCaptionView) {
                // The decor was most likely saved from a relaunch - so reuse it.
                decorCaptionView = (DecorCaptionView) view;
                removeViewAt(i);
            }
        }
        final WindowManager.LayoutParams attrs = mWindow.getAttributes();
        final boolean isApplication = attrs.type == TYPE_BASE_APPLICATION ||
                attrs.type == TYPE_APPLICATION || attrs.type == TYPE_DRAWN_APPLICATION;
        final WindowConfiguration winConfig = getResources().getConfiguration().windowConfiguration;
        // Only a non floating application window on one of the allowed workspaces can get a caption
        if (!mWindow.isFloating() && isApplication && winConfig.hasWindowDecorCaption()) {
            // Dependent on the brightness of the used title we either use the
            // dark or the light button frame.
            if (decorCaptionView == null) {
            	//调用inflateDecorCaptionView方法
                decorCaptionView = inflateDecorCaptionView(inflater);
            }
            decorCaptionView.setPhoneWindow(mWindow, true /*showDecor*/);
        } else {
            decorCaptionView = null;
        }

        enableCaption(decorCaptionView != null);
        return decorCaptionView;
    }
    
    private DecorCaptionView inflateDecorCaptionView(LayoutInflater inflater) {
        final Context context = getContext();
        inflater = inflater.from(context);
        //注释8,构建小窗对应的布局文件
        final DecorCaptionView view = (DecorCaptionView) inflater.inflate(R.layout.decor_caption,
                null);
        setDecorCaptionShade(view);
        return view;
    }
}

如上所示Activity的setContentView方法经过层层调用最终会触发DecorView的onResourcesLoaded方法,如果是小窗模式,会走到注释6处,将页面需要的布局资源添加到DecorCaptionView中,然后将DecorCaptionView添加到DecorView中;如果是普通应用场景,会走到注释7处,会将布局资源添加到DecorView中;最终WMS会将DecorView添加到屏幕上。另外在注释8处可以看到小窗加载的是什么布局资源。

三、小窗的实现

2.1 小窗生成

小窗主要在 DecorCaptionView 中处理, Activity -> PhoneWindow -> DecorView -> DecorCaptionView,相比 DecorView 多了一个 Caption(标题栏)

建立的条件有两种:
一个在 PhoneWindow 建立的时候,创建 DecorView,DecorView 会判断是否要创建一个 DecorCaptionView。
第二个就是在 DecorView 中动态变化中,有参数变量,通过onWindowSystemUiVisibilityChanged(View的可见性变化) 以及 onConfigurationChanged(各种触发配置变化的条件) 的回调,实现对DecorView 进行是否要新建一个小窗的视图。

在 DecorView 中主要处理了小窗的参数变化,以及标题栏的显示与隐藏。

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
    private void updateDecorCaptionStatus(Configuration config) {
        // 如果定义窗口类型为小窗,且不是全屏模式, 则创建一个 DecorCaptionView 在 DecorView 内部。
        final boolean displayWindowDecor = config.windowConfiguration.hasWindowDecorCaption()
                && !isFillingScreen(config);
        if (mDecorCaptionView == null && displayWindowDecor) {
            // Configuration now requires a caption.
            final LayoutInflater inflater = mWindow.getLayoutInflater();
            mDecorCaptionView = createDecorCaptionView(inflater);
            if (mDecorCaptionView != null) {
                if (mDecorCaptionView.getParent() == null) {
                    addView(mDecorCaptionView, 0,
                            new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
                }
                removeView(mContentRoot);
                mDecorCaptionView.addView(mContentRoot,
                        new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
        } else if (mDecorCaptionView != null) {
            // 如果已经创建了 DecorCaptionView, 则更新配置信息,比如窗口大小,窗口位置等。
            mDecorCaptionView.onConfigurationChanged(displayWindowDecor);
            // 是否显示小窗的标题栏
            enableCaption(displayWindowDecor);
        }
    }
}

2.2 小窗的标题栏

public class DecorCaptionView extends ViewGroup implements View.OnTouchListener,
        GestureDetector.OnGestureListener {
    
    private View mCaption;  //标题栏
    private View mContent; //小窗View之下,应用的根View
    private View mMaximize; //最大化按钮
    private View mClose; //关闭按钮   
 }

2.3 触摸事件

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
	//如果在应用的小窗View外部点击的话,直接将事件拦截掉,这样就不会触发应用的点击事件。
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        int action = event.getAction();
        //是否显示小窗标题栏
        if (mHasCaption && isShowingCaption()) {
        //如果窗口可调整大小,并且事件是(开始)在小窗外部,则不要将 ACTION_DOWN 事件进行传递。窗口调整大小事件应由WindowManager处理。
            if (action == MotionEvent.ACTION_DOWN) {
                final int x = (int) event.getX();
                final int y = (int) event.getY();
                if (isOutOfInnerBounds(x, y)) {
                    return true;
                }
            }
        }
		...代码省略...
        return false;
    }
}

frameworks/base/core/java/com/android/internal/widget/DecorCaptionView.java

public class DecorCaptionView extends ViewGroup implements View.OnTouchListener,
        GestureDetector.OnGestureListener {
        
    private View mClickTarget;
    
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //如果用户点击最大化或者关闭按钮,就拦截事件,这样就不会触发应用的点击事件
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            final int x = (int) ev.getX();
            final int y = (int) ev.getY();
            //Only offset y for containment tests because the actual views are already translated.
            //是否点击到最大化按钮的区域
            if (mMaximizeRect.contains(x, y - mRootScrollY)) {
                mClickTarget = mMaximize;
            }
            //是否点击到关闭按钮的区域
            if (mCloseRect.contains(x, y - mRootScrollY)) {
                mClickTarget = mClose;
            }
        }
        return mClickTarget != null;
    }
 }

在小窗的View中,应用就不能通过View的事件来直接处理,DecorCaptionView 需要通过计算View所在的矩形区域,然后计算点击的区域是否处于该矩形区域范围内判断为点击,就是一个点和面的问题,原生小窗上的问题就是这个触控面太小,手指的点击区域可能不容易触发。 (在我开发的ChatDev游戏中,也有面和面碰撞的问题,玩家的位置和碰撞位置的计算)

2.4 小窗的边界

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

  • 边界圆角 一般会对这个小窗容器进行一个UI上的美化,主要的一个就是边界的圆角轮廓绘制。

  • 导航栏重叠的问题

DisplayPolicy 作为系统里面控制显示 dock栏、状态栏、导航栏的样式的主要类, 在每次绘制布局之后,都会走到如下applyPostLayoutPolicyLw 函数,进行显示规则的条件,当判断重叠之后,在导航栏更新透明度规则的时候,将其标记中不透明的纯深色背景和浅色前景清空。

public class DisplayPolicy {
    public void applyPostLayoutPolicyLw(WindowState win, WindowManager.LayoutParams attrs,
            WindowState attached, WindowState imeTarget) {
        final boolean affectsSystemUi = win.canAffectSystemUiFlags();
        if (DEBUG_LAYOUT) Slog.i(TAG, "Win " + win + ": affectsSystemUi=" + affectsSystemUi);
        applyKeyguardPolicy(win, imeTarget);

        // 检查自由窗口是否与导航栏区域重叠。
        final boolean isOverlappingWithNavBar = isOverlappingWithNavBar(win);
        if (isOverlappingWithNavBar && !mIsFreeformWindowOverlappingWithNavBar
                && win.inFreeformWindowingMode()) {// 如果窗口是自由窗口,并且窗口和导航栏重叠
            mIsFreeformWindowOverlappingWithNavBar = true;
        }

        if (!affectsSystemUi) {
            return;
        }
		...代码省略...
    }
 }

http://www.niftyadmin.cn/n/5869435.html

相关文章

直角三角堰计算公式

直角三角堰的计算公式通常用于确定流经直角三角形形状的堰的流量。河北瑾航科技遥测终端机 通过采集液位数据(模拟量、串口485/232)&#xff0c;计算得到瞬时流量&#xff0c;然后通过积分进行累计算出累积量&#xff1b;直角三角堰的流量计算公式为&#xff1a; 直角三角堰 计…

Windows程序设计28:MFC模态与非模态对话框

文章目录 前言一、创建模态对话框1.创建模态对话框模板2.绑定自定义对话框类3.创建模态对话框DoModal4.销毁模态对话框二、创建非模态对话框1.创建对话框模板2.绑定自定义对话框类3.创建非模态对话框Create、ShowWindow4.销毁非模态对话框5.销毁自身窗口指针总结前言 Windows程…

XTOM工业级蓝光三维扫描仪在笔记本电脑背板模具全尺寸检测中的高效精准应用

——某3C精密制造企业模具优化与质量管控案例 镁合金具有密度小、强度高、耐腐蚀性好等优点&#xff0c;成为笔记本电脑外壳主流材料。冲压模具作为批量生产笔记本电脑镁合金背板的核心工具&#xff0c;其精度直接决定了产品的尺寸一致性、结构可靠性与外观品质。微米级模具误…

MySQL数据库的基本命令

1.use mysql;切换***数据库 2.show databases&#xff1b;语句查看当前系统存在的数据库 其中的4个数据库都属于系统数据库&#xff0c; informain_schema:储存系统中一些数据库对象信息 mysql:主要 performance_schema: sys: 3.show tables;查看当前数据库中的表 4.sele…

记录一下用docker克隆某授权制定ip的环境恢复

#首先还是要看日志根据问题去进行调整 java web的老项目配置文件一般是 bin启动里边的脚本 还有conf中的 xml配置文件 再或者就是classes中的配置文件,再或者就是lib中的jar包中的配置文件 1.安装docker 2.创建docker网络 docker network create --driver bridge --subnet…

JavaSE学习笔记26-集合(Collection)

集合 Java 中的集合&#xff08;Collection&#xff09;是 Java 标准库中非常重要的一部分&#xff0c;用于存储和操作一组对象。Java 集合框架&#xff08;Java Collections Framework&#xff09;提供了一套丰富的接口和类&#xff0c;用于处理各种数据结构&#xff0c;如列…

策略模式结合SpringBoot

一 定义策略类接口 import org.jetbrains.annotations.NotNull;import javax.annotation.Nullable;public interface ApprovalStrategy<T> {/*** 返回需要的类型 不能为空** return*/NotNullClass<T> getSupportedType();void handlePass(T businessId, Nullable S…

精准识别IP应用场景

基于全球领先的IP应用场景识别服务IPv4/IPv6全量数据库&#xff0c;为企业提供高精度、低延迟的场景化解析能力&#xff0c;助您构建更安全、智能的网络生态。 ​精准识别&#xff0c;毫秒响应 全量数据覆盖&#xff0c;依托全球最大的IP地址库&#xff0c;支持IPv4/IPv6双协…