«

Android窗口怎么实现

时间:2024-6-19 13:20     作者:韩俊     分类: Android


这篇文章主要介绍“Android窗口怎么实现”,在日常操作中,相信很多人在Android窗口怎么实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Android窗口怎么实现”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

对于Window的认识阶段

第一阶段

刚刚学习Android的时候,都听说过一个概念,Activity代表一个界面。实际开发起来也是如此,在Activity中加载xml文件,绑定数据与view。

Activity == UI

第二阶段

看了几篇文章,接触了Window,WMS 概念 。发现Activity并不是UI界面,Activity内部持有Window对象,Window的实现类PhoneWindow内部持有DecorView作为根布局,开发人员编写的xml 会添加到DecorView中。 哦!结合对WMS粗浅的理解,WMS是窗口管理服务,window不就是窗口么,可能window在创建完成后最终传递给WMS管理。 Window == UI

第三阶段

之后深入到源码中发现Window虽然翻译过来是窗口,但实际上并不是真正的窗口。

理由有二:Window并没有与WMS交互,Window没有view管理之类的功能。

首先可以确定的是wms是系统窗口服务,所有窗口都要与wms打交道。如果window代表的窗口,那么它或者它的唯一子类PhoneWindow,必然存在Binder机制与wms交互,然而并没有。

既然Window没有与wms交互,那它做了什么工作呢?

在面向对象中,设计一个类的意义可以从它的属性以及暴露的方法来推测。

如下是从:PhoneWindow中摘取的一些通过名字可以大概推测出作用的属性

大部分都是关于资源的设置:状态栏,导航栏,是否透明,转场动画,应用主题等等

private DecorView mDecor;
private TextView mTitleView;
int mStatusBarColor = 0;
int mNavigationBarColor = 0;
private int mTitleColor = 0;
private CharSequence mTitle = null;
boolean mIsFloating;
private boolean mIsTranslucent;
private LayoutInflater mLayoutInflater;
private Transition mEnterTransition = null;
private Transition mReturnTransition = USE_DEFAULT_TRANSITION;
private int mTheme = -1;
private boolean mIsStartingWindow;

Window类注释 — 百度翻译

Abstract base class for a top-level window look and behavior policy. An instance of this class should be used as the top-level view added to the window manager. It provides standard UI policies such as a background, title area, default key processing, etc.

顶级窗口外观和行为策略的抽象基类。此类的实例应用作添加到窗口管理器的顶级视图。它提供标准的UI策略,例如背景、标题区域、默认键处理等。

结合window类注释可以做出结论,Window也是一层封装,提供通用页面模板,并不是真正的window。

寻找真正的Window

上面讨论了Window类 并不是真正的Window,只是一层封装。系统提供了

WindowManager
允许开发人员添加Window。

如下代码是在Activity获取windowManager 添加Window。为什么api是

addView
。不应该是
addWindow
才对么? 难道view才是window?(下面代码会报错 添加系统window需要权限)

val wm:WindowManager =windowManager
val layoutParams = WindowManager.LayoutParams()
layoutParams.run{
        width = WindowManager.LayoutParams.WRAP_CONTENT
        height = WindowManager.LayoutParams.WRAP_CONTENT
        format = PixelFormat.TRANSLUCENT
        gravity = Gravity.STARTor Gravity.TOP
        flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
        type =
                if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.O) WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
        else WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG
}
val view :View = LayoutInflater.from(this).inflate(R.layout.xxx,null)
wm.addView(view, layoutParams)

跟踪源码

WindowManager
是个接口,实现类为
WindowManagerImpl

WindowManagerImpl
内部把逻辑转发给
WindowManagerGlobal

WindowManagerGlobal
调用
ViewRootImpl

ViewRootImpl
通过WindowSession 与 wms 完成进程间通信

具体方法调用流程

WindowManager.addView()
—
WindowManagerImpl.addView()
—
WindowManagerGlobal.addView()
—
ViewRootImpl.setView()
—
WindowSession.addToDisplayAsUser()

ViewRootImpl类核心逻辑如下:

WindowManager.addView()
在应用层最终调用
ViewRootImpl.setView()

添加的View通过 WindowSession 进入 wms,方法

IWindowSession.addToDisplay
第一个参数
mWindow
代表真正的window。

mWindow的实现类W,类型是 IWindow.Stub ,Binder对象 对其他进程暴露方法。

W类 持有ViewRootImpl ,公开的接口方法内部调用ViewRootImpl 类。

所以

IWindowSession
是把一个Binder对象传递给WMS,WMS通过进程间通信操作ViewRootImpl ,ViewRootImpl 操作View

ViewRootImpl 操作的View

对应到当前场景是 windowManager.addView() 添加的View

对应到Activity则是PhoneWindow中DecorView

经过这一顿分析 好像没有确定Window的实体对象 难以捉摸。它不像一个User类,Person类那样明晃晃的放在开发者面前。原本以为 传递给WMS肯定会是Window了,结果是Binder,WMS进程间通信最终操控的是View。

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
    final W mWindow;
    View mView;
    final IWindowSession mWindowSession;
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
            mView = view;
            res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mDisplayCutout, inputChannel,
                            mTempInsets, mTempControls);
    }
    static class W extends IWindow.Stub {
        private final WeakReference<ViewRootImpl> mViewAncestor;
        private final IWindowSession mWindowSession;
                W(ViewRootImpl viewAncestor) {
            mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
            mWindowSession = viewAncestor.mWindowSession;
        }
                ....
                @Override
        public void hideInsets(@InsetsType int types, boolean fromIme) {
            final ViewRootImpl viewAncestor = mViewAncestor.get();
            if (viewAncestor != null) {
                viewAncestor.hideInsets(types, fromIme);
            }
        }
        @Override
        public void moved(int newX, int newY) {
            final ViewRootImpl viewAncestor = mViewAncestor.get();
            if (viewAncestor != null) {
                viewAncestor.dispatchMoved(newX, newY);
            }
        }
                ....省略其他方法
    }
}

Window到底是什么

window是一个抽象的概念,对应手机屏幕的一块区域,实际是view。

View成了Window??? 什么场景下可以把View叫做Window呢?

想象一个场景:一个Activity内有DialogA,DialogB

这个场景会创建三个Window,Activity一个,Dialog两个,对应三个xml布局。是三个抽象的Window,对应三个具体的View,应该叫做View树

它们彼此之间互不影响,为DialogA添加View,不会影响到Activity和DialogB。因为它们属于不同的Window。

这也应该是添加Window的Api 叫做

addView()
而不是
addWidnow()
的原因。

根本就没有具体的Window,只有具体的View,Window是抽象的。

理解了什么是Window之后,在简单说一下添加window的Api。

View表示需要在屏幕展示的内容

layoutParams 则是对内容进行约束,基本的宽高,位置。

layoutParams.type 设置window类型,其实是弹窗的显示层级。

应用window:1 ~ 99

子window:1000 ~ 1999

系统window:2000~ 2999

数值越大层级越高,层级高覆盖层级低的,一般通过常量设置,系统window需要申请权限

layoutParams.flags 设置Window不同场景下的逻辑,比如:

// 全屏显示,隐藏所有的 Window 装饰,比如在游戏、播放器中的全屏显示 public static final int FLAG_FULLSCREEN = 0x00000400;

// 表示比FLAG_FULLSCREEN低一级,会显示状态栏 public static final int FLAG_FORCE_NOT_FULLSCREEN = 0x00000800;

// 当用户的脸贴近屏幕时(比如打电话),不会去响应此事件 public static final int FLAG_IGNORE_CHEEK_PRESSES = 0x00008000;

// 全屏显示,隐藏所有的 Window 装饰,比如在游戏、播放器中的全屏显示 public static final int FLAG_FULLSCREEN = 0x00000400;

// 表示比FLAG_FULLSCREEN低一级,会显示状态栏 public static final int FLAG_FORCE_NOT_FULLSCREEN = 0x00000800;

// 当用户的脸贴近屏幕时(比如打电话),不会去响应此事件 public static final int FLAG_IGNORE_CHEEK_PRESSES = 0x00008000;

val wm:WindowManager =windowManager
val layoutParams = WindowManager.LayoutParams()
layoutParams.run{
        width = WindowManager.LayoutParams.WRAP_CONTENT
        height = WindowManager.LayoutParams.WRAP_CONTENT
        format = PixelFormat.TRANSLUCENT
        gravity = Gravity.STARTor Gravity.TOP
        x = 0
        y = 0
        flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
        type =
                if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.O) WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
        else WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG
}
val view :View = LayoutInflater.from(this).inflate(R.layout.xxx,null)
wm.addView(view, layoutParams)

Activity-Window-View的关系

Activity是一层封装,屏蔽复杂的系统实现细节,抽象出UI生命周期,方便开发人员工作,专注于界面样式的编写

Window,指PhoneWindow,页面通用模板,所有的Window都需要主题,状态栏,导航栏,背景等等设置。PhoneWindow是对上述内容的一个模板实现。

软件设计中很重要的一点就是找到业务当中的 “变与不变”。 在Window体系中,一个页面通用不变的部分交给PhoneWindow实现。变化的部分就是View,让开发人员能够自由定制。

PhoneWindow的存在也是帮Activity减轻负担,指责单一是一个好理解并且非常有效的原则。Activity已经非常复杂了, 设计出PhoneWindow把UI相关的代码从Activity中剥离出去。

由了上述层层抽象封装才有了最初学习Android时的概念,Activity == 页面。

标签: android

热门推荐