精华内容
下载资源
问答
  • Android全面解析之Window机制

    千次阅读 多人点赞 2020-09-01 20:55:54
    带你认识最本质Android的window机制

    文章已授权『鸿洋』公众号发布

    前言

    你好!
    我是一只修仙的猿,欢迎阅读我的文章。

    Window,读者可能更多的认识是windows系统的窗口。在windows系统上,我们可以多个窗口同时运行,每个窗口代表着一个应用程序。但在安卓上貌似并没有这个东西,但读者可以马上想到,不是有小窗口模式吗,像米UI最新的系统,不就是可以随意创建一个小窗口,然后两个应用同时操作?是的,那是属于android中,window的一种表现方式。但是手机屏幕终究不能和电脑相比,因为屏幕太小了,小到只能操作一款应用,多个窗口就显得非常不习惯,所以Android上关于窗口方面的知识读者可能接触不多。那window的意思就只是小米系统中那种小窗口吗?

    当然不是。Android框架层意义上的window和我们认识的window其实是有点不一样的。我们日常最直观的,每个应用界面,都有一个应用级的window。再例如popupWindow、Toast、dialog、menu都是需要通过创建window来实现。所以其实window我们一直都见到,只是不知道那就是window。了解window的机制原理,可以更好地了解window,进而更好地了解android是怎么管理屏幕上的view。这样,当我们需要使用dialog或者popupWindow的时候,可以懂得他背后究竟做了什么,才能够更好的运用dialog、popupWindow等。

    当然,到此如果你有很多的疑问,甚至质疑我的理论,那就希望你可以阅读完这一篇文章。我会从window是什么,有什么用,内部机制是什么,各种组件是如何创建window等等方面来阐述Android中的window。文章内容非常多,读者可自选章节阅读。

    什么是window机制

    先假设如果没有window,会发生什么:

    我们看到的界面ui是view,如我们的应用布局,更简单是一个button。假如屏幕上现在有一个Button,如图1,现在往屏幕中间添加一个TextView,那么最终的结果是图2,还是图3:

    示例图

    在上图的图2中,如果我要实现点击textView执行他的监听事件逻辑,点击不是textView的区域让textView消失,需要怎么实现呢?读者可能会说,我们可以在Activity中添加这部分的逻辑,那如果我们需要让一个悬浮窗在所有界面显示呢,如上文我讲到的小米悬浮窗,两个不用应用的view,怎么确定他们的显示次序?又例如我们需要弹出一个dialog来提示用户,怎么样可以让dialog永远处于最顶层呢,包括显示dialog期间应用弹出的如popupWindow必须显示在dialog的低下,但toast又必须显示在dialog上面。

    很明显,我们的屏幕可以允许多个应用同时显示非常多的view,他们的显示次序或者说显示高度是不一样的,如果没有一个统一的管理者,那么每一家应用都想要显示在最顶层,那么屏幕上的view会非常乱。

    同时,当我们点击屏幕时,这个触摸事件应该传给哪个view?很明显我们都知道应该传给最上层的view,但是接受事件的是屏幕,是另一个系统服务,他怎么知道触摸位置的最上层是哪个view呢?即时知道,他又怎么把这个事件准确地传给他呢?

    为了解决等等这些问题,急需有一个管理者来统一管理屏幕上的显示的view,才能让程序有条不紊地走下去。而这,就是Android中的window机制。

    window机制就是为了管理屏幕上的view的显示以及触摸事件的传递问题。

    什么是window?

    那什么是window,在Android的window机制中,每个view树都可以看成一个window。为什么不是每个view呢?因为view树中每个view的显示次序是固定的,例如我们的Activity布局,每一个控件的显示都是已经安排好的,对于window机制来说,属于“不可再分割的view”。

    什么是view树?例如你在布局中给Activity设置了一个布局xml,那么最顶层的布局如LinearLayout就是view树的根,他包含的所有view就都是该view树的节点,所以这个view树就对应一个window。

    举几个具体的例子:

    • 我们在添加dialog的时候,需要给他设置view,那么这个view他是不属于antivity的布局内的,是通过WindowManager添加到屏幕上的,不属于activity的view树内,所以这个dialog是一个独立的view树,所以他是一个window。
    • popupWindow他也对应一个window,因为它也是通过windowManager添加上去的,不属于Activity的view树。
    • 当我们使用使用windowManager在屏幕上添加的任何view都不属于Activity的布局view树,即使是只添加一个button。

    view树(后面使用view代称,后面我说的view都是指view树)是window机制的操作单位,每一个view对应一个window,view是window的存在形式,window是view的载体,我们平时看到的应用界面、dialog、popupWindow以及上面描述的悬浮窗,都是window的表现形式。注意,我们看到的不是window,而是view。**window是view的管理者,同时也是view的载体。他是一个抽象的概念,本身并不存在,view是window的表现形式。**这里的不存在,指的是我们在屏幕上是看不到window的,他不像windows系统,如下图:

    windows系统窗口

    有一个很明显的标志:看,我就是window。但在Android中我们是无法感知的,我们只能看到view无法看到window,window是控制view需要怎么显示的管理者。每个成功的男人背后都有一个女人,每个view背后都有一个window。

    window本身并不存在,他只是一个概念。举个栗子:如班集体,就是一个概念,他的存在形式是这整个班的学生,当学生不存在那么这个班集体也就不存在。但是他的好处是得到了一个新的概念,我们可以以班为单位来安排活动。因他不存在,所以也很难从源码中找到他的痕迹,window机制的操作单位都是view,如果要说他在源码中的存在形式,笔者目前的认知就是在WindowManagerService中每一个view对应一个windowStatus。WindowManagerService是什么如果没了解过可以先忽略后面会讲到。读者可以慢慢思考一下这个抽象的概念,后面会慢慢深入讲源码帮助理解。

    • view是window的存在形式,window是view的载体
    • window是view的管理者,同时也是view的载体。他是一个抽象的概念,本身并不存在,view是window的表现形式

    思考:Android中不是有一个抽象类叫做window还有一个PhoneWindow实现类吗,他们不就是window的存在形式,为什么说window是抽象不存在的?读者可自行思考,后面会讲到。

    Window的相关属性

    在了解window的操作流程之前,先补充一下window的相关属性。

    window的type属性

    前面我们讲到window机制解决的一个问题就是view的显示次序问题,这个属性就决定了window的显示次序。window是有分类的,不同类别的显示高度范围不同,例如我把1-1000m高度称为低空,1001-2000m高度称为中空,2000以上称为高空。window也是一样按照高度范围进行分类,他也有一个变量Z-Order,决定了window的高度。window一共可分为三类:

    • 应用程序窗口:应用程序窗口一般位于最底层,Z-Order在1-99
    • 子窗口:子窗口一般是显示在应用窗口之上,Z-Order在1000-1999
    • 系统级窗口:系统级窗口一般位于最顶层,不会被其他的window遮住,如Toast,Z-Order在2000-2999。如果要弹出自定义系统级窗口需要动态申请权限

    Z-Order越大,window越靠近用户,也就显示越高,高度高的window会覆盖高度低的window。

    window的type属性就是Z-Order的值,我们可以给window的type属性赋值来决定window的高度。系统为我们三类window都预设了静态常量,如下(以下常用参数介绍转自参考文献第一篇文章):

    • 应用级window

      // 应用程序 Window 的开始值
      public static final int FIRST_APPLICATION_WINDOW = 1;
      
      // 应用程序 Window 的基础值
      public static final int TYPE_BASE_APPLICATION = 1;
      
      // 普通的应用程序
      public static final int TYPE_APPLICATION = 2;
      
      // 特殊的应用程序窗口,当程序可以显示 Window 之前使用这个 Window 来显示一些东西
      public static final int TYPE_APPLICATION_STARTING = 3;
      
      // TYPE_APPLICATION 的变体,在应用程序显示之前,WindowManager 会等待这个 Window 绘制完毕
      public static final int TYPE_DRAWN_APPLICATION = 4;
      
      // 应用程序 Window 的结束值
      public static final int LAST_APPLICATION_WINDOW = 99;
      
    • 子window

      // 子 Window 类型的开始值
      public static final int FIRST_SUB_WINDOW = 1000;
      
      // 应用程序 Window 顶部的面板。这些 Window 出现在其附加 Window 的顶部。
      public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
      
      // 用于显示媒体(如视频)的 Window。这些 Window 出现在其附加 Window 的后面。
      public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
      
      // 应用程序 Window 顶部的子面板。这些 Window 出现在其附加 Window 和任何Window的顶部
      public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
      
      // 当前Window的布局和顶级Window布局相同时,不能作为子代的容器
      public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
      
      // 用显示媒体 Window 覆盖顶部的 Window, 这是系统隐藏的 API
      public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW + 4;
      
      // 子面板在应用程序Window的顶部,这些Window显示在其附加Window的顶部, 这是系统隐藏的 API
      public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
      
      // 子 Window 类型的结束值
      public static final int LAST_SUB_WINDOW = 1999;
      
    • 系统级window

      // 系统Window类型的开始值
      public static final int FIRST_SYSTEM_WINDOW = 2000;
      
      // 系统状态栏,只能有一个状态栏,它被放置在屏幕的顶部,所有其他窗口都向下移动
      public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;
      
      // 系统搜索窗口,只能有一个搜索栏,它被放置在屏幕的顶部
      public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1;
      
      // 已经从系统中被移除,可以使用 TYPE_KEYGUARD_DIALOG 代替
      public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4;
      
      // 系统对话框窗口
      public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8;
      
      // 锁屏时显示的对话框
      public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9;
      
      // 输入法窗口,位于普通 UI 之上,应用程序可重新布局以免被此窗口覆盖
      public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11;
      
      // 输入法对话框,显示于当前输入法窗口之上
      public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;
      
      // 墙纸
      public static final int TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13;
      
      // 状态栏的滑动面板
      public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14;
      
      // 应用程序叠加窗口显示在所有窗口之上
      public static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;
      
      // 系统Window类型的结束值
      public static final int LAST_SYSTEM_WINDOW = 2999;
      

    Window的flags参数

    flag标志控制window的东西比较多,很多资料的描述是“控制window的显示”,但我觉得不够准确。flag控制的范围包括了:各种情景下的显示逻辑(锁屏,游戏等)还有触控事件的处理逻辑。控制显示确实是他的很大部分功能,但是并不是全部。下面看一下一些常用的flag,就知道flag的功能了(以下常用参数介绍转自参考文献第一篇文章):

    // 当 Window 可见时允许锁屏
    public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001;
    
    // Window 后面的内容都变暗
    public static final int FLAG_DIM_BEHIND = 0x00000002;
    
    // Window 不能获得输入焦点,即不接受任何按键或按钮事件,例如该 Window 上 有 EditView,点击 EditView 是 不会弹出软键盘的
    // Window 范围外的事件依旧为原窗口处理;例如点击该窗口外的view,依然会有响应。另外只要设置了此Flag,都将会启用FLAG_NOT_TOUCH_MODAL
    public static final int FLAG_NOT_FOCUSABLE = 0x00000008;
    
    // 设置了该 Flag,将 Window 之外的按键事件发送给后面的 Window 处理, 而自己只会处理 Window 区域内的触摸事件
    // Window 之外的 view 也是可以响应 touch 事件。
    public static final int FLAG_NOT_TOUCH_MODAL  = 0x00000020;
    
    // 设置了该Flag,表示该 Window 将不会接受任何 touch 事件,例如点击该 Window 不会有响应,只会传给下面有聚焦的窗口。
    public static final int FLAG_NOT_TOUCHABLE      = 0x00000010;
    
    // 只要 Window 可见时屏幕就会一直亮着
    public static final int FLAG_KEEP_SCREEN_ON     = 0x00000080;
    
    // 允许 Window 占满整个屏幕
    public static final int FLAG_LAYOUT_IN_SCREEN   = 0x00000100;
    
    // 允许 Window 超过屏幕之外
    public static final int FLAG_LAYOUT_NO_LIMITS   = 0x00000200;
    
    // 全屏显示,隐藏所有的 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 之外时,将接收到一个MotionEvent.ACTION_OUTSIDE事件。
    public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;
    
    @Deprecated
    // 窗口可以在锁屏的 Window 之上显示, 使用 Activity#setShowWhenLocked(boolean) 方法代替
    public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
    
    // 表示负责绘制系统栏背景。如果设置,系统栏将以透明背景绘制,
    // 此 Window 中的相应区域将填充 Window#getStatusBarColor()和 Window#getNavigationBarColor()中指定的颜色。
    public static final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = 0x80000000;
    
    // 表示要求系统壁纸显示在该 Window 后面,Window 表面必须是半透明的,才能真正看到它背后的壁纸
    public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
    

    window的solfInputMode属性

    这一部分就是当软件盘弹起来的时候,window的处理逻辑,这在日常中也经常遇到,如:我们在微信聊天的时候,点击输入框,当软键盘弹起来的时候输入框也会被顶上去。如果你不想被顶上去,也可以设置为被软键盘覆盖。下面介绍一下常见的属性(以下常见属性介绍选自参考文献第一篇文章):

    // 没有指定状态,系统会选择一个合适的状态或者依赖于主题的配置
    public static final int SOFT_INPUT_STATE_UNCHANGED = 1;
    
    // 当用户进入该窗口时,隐藏软键盘
    public static final int SOFT_INPUT_STATE_HIDDEN = 2;
    
    // 当窗口获取焦点时,隐藏软键盘
    public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;
    
    // 当用户进入窗口时,显示软键盘
    public static final int SOFT_INPUT_STATE_VISIBLE = 4;
    
    // 当窗口获取焦点时,显示软键盘
    public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;
    
    // window会调整大小以适应软键盘窗口
    public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;
    
    // 没有指定状态,系统会选择一个合适的状态或依赖于主题的设置
    public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;
    
    // 当软键盘弹出时,窗口会调整大小,例如点击一个EditView,整个layout都将平移可见且处于软件盘的上方
    // 同样的该模式不能与SOFT_INPUT_ADJUST_PAN结合使用;
    // 如果窗口的布局参数标志包含FLAG_FULLSCREEN,则将忽略这个值,窗口不会调整大小,但会保持全屏。
    public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;
    
    // 当软键盘弹出时,窗口不需要调整大小, 要确保输入焦点是可见的,
    // 例如有两个EditView的输入框,一个为Ev1,一个为Ev2,当你点击Ev1想要输入数据时,当前的Ev1的输入框会移到软键盘上方
    // 该模式不能与SOFT_INPUT_ADJUST_RESIZE结合使用
    public static final int SOFT_INPUT_ADJUST_PAN = 0x20;
    
    // 将不会调整大小,直接覆盖在window上
    public static final int SOFT_INPUT_ADJUST_NOTHING = 0x30;
    

    window的其他属性

    上面的三个属性是window比较重要也是比较复杂 的三个,除此之外还有几个日常经常使用的属性:

    • x与y属性:指定window的位置
    • alpha:window的透明度
    • gravity:window在屏幕中的位置,使用的是Gravity类的常量
    • format:window的像素点格式,值定义在PixelFormat中

    如何给window属性赋值

    window属性的常量值大部分存储在WindowManager.LayoutParams类中,我们可以通过这个类来获得这些常量。当然还有Gravity类和PixelFormat类等。

    一般情况下我们会通过以下方式来往屏幕中添加一个window:

    // 在Activity中调用
    WindowManager.LayoutParams windowParams = new WindowManager.LayoutParams();
    windParams.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN;
    TextView view = new TextView(this);
    getWindowManager.addview(view,windowParams);
    

    我们可以直接给WindowManager.LayoutParams对象设置属性。

    第二种赋值方法是直接给window赋值,如

    getWindow().flags = WindowManager.LayoutParams.FLAG_FULLSCREEN;
    

    除此之外,window的solfInputMode属性比较特殊,他可以直接在AndroidManifest中指定,如下:

     <activity android:windowSoftInputMode="adjustNothing" />
    

    最后总结一下:

    • window的重要属性有type、flags、solfInputMode、gravity等
    • 我们可以通过不同的方式给window属性赋值
    • 没必要去全部记下来,等遇到需求再去寻找对应的常量即可

    Window的添加过程

    通过理解源码之后,可以对之前的理论理解更加的透彻。window的添加过程,指的是我们通过WindowManagerImpl的addView方法来添加window的过程。

    想要添加一个window,我们知道首先得有view和WindowManager.LayoutParams对象,才能去创建一个window,这是我们常见的代码:

    Button button = new Button(this);
    WindowManager.LayoutParams windowParams = new WindowManager.LayoutParams();
    // 这里对windowParam进行初始化
    windowParam.addFlags...
    // 获得应用PhoneWindow的WindowManager对象进行添加window
    getWindowManager.addView(button,windowParams);
    

    然后接下来我们进入addView方法中看看。我们知道这个windowManager的实现类是WindowManagerImpl,上面讲过,进入他的addView方法看一看:

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }
    

    可以发现他把逻辑直接交给mGlobal去处理了。这个mGlobal是WindowManagerGlobal,是一个全局单例,是WindowManager接口的具体逻辑实现。这里运用的是桥接模式。那我们进WindowManagerGlobal的方法看一下:

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        // 首先判断参数是否合法
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (display == null) {
            throw new IllegalArgumentException("display must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }
        
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        // 如果不是子窗口,会对其做参数的调整
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }
        
    	synchronized (mLock) {
            ...
            // 这里新建了一个viewRootImpl,并设置参数
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
    
            // 添加到windowManagerGlobal的三个重要list中,后面会讲到
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
    
            // 最后通过viewRootImpl来添加window
            try {
                root.setView(view, wparams, panelParentView);
            } 
            ...
        }  
    }
    

    代码有点长,一步步看:

    • 首先对参数的合法性进行检查
    • 然后判断该窗口是不是子窗口,如果是的话需要对窗口进行调整,这个好理解,子窗口要跟随父窗口的特性。
    • 接着新建viewRootImpl对象,并把view、viewRootImpl、params三个对象添加到三个list中进行保存
    • 最后通过viewRootImpl来进行添加

    补充一点关于WindowManagerGlobal中的三个list,他们分别是:

    private final ArrayList<View> mViews = new ArrayList<View>();
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    private final ArrayList<WindowManager.LayoutParams> mParams =
         new ArrayList<WindowManager.LayoutParams>();
    

    每一个window所对应的这三个对象都会保存在这里,之后对window的一些操作就可以直接来这里取对象了。当window被删除的时候,这些对象也会被从list中移除。

    可以看到添加的window的逻辑就交给ViewRootImpl了。viewRootImpl是window和view之间的桥梁,viewRootImpl可以处理两边的对象,然后联结起来。下面看一下viewRootImpl怎么处理:

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            ...
            try {
                mOrigWindowType = mWindowAttributes.type;
                mAttachInfo.mRecomputeGlobalAttributes = true;
                collectViewAttributes();
                // 这里调用了windowSession的方法,调用wms的方法,把添加window的逻辑交给wms
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                        mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                        mTempInsets);
                setFrame(mTmpFrame);
            } 
            ...
        }
    }
    

    viewRootImpl的逻辑很多,重要的就是调用了mWindowSession的方法调用了WMS的方法。这个mWindowSession很重要重点讲一下。

    mWindowSession是一个IWindowSession对象,看到这个命名很快地可以像到这里用了AIDL跨进程通信。IWindowSession是一个IBinder接口,他的具体实现类在WindowManagerService,本地的mWindowSession只是一个Binder对象,通过这个mWindowSession就可以直接调用WMS的方法进行跨进程通信。

    那这个mWindowSession是从哪里来的呢?我们到viewRootImpl的构造器方法中看一下:

    public ViewRootImpl(Context context, Display display) {
    	...
     	mWindowSession = WindowManagerGlobal.getWindowSession();
     	...
    }
    

    可以看到这个session对象是来自WindowManagerGlobal。再深入看一下:

    public static IWindowSession getWindowSession() {
     synchronized (WindowManagerGlobal.class) {
         if (sWindowSession == null) {
             try {
                 ...
                 sWindowSession = windowManager.openSession(
                         new IWindowSessionCallback.Stub() {
                             ...
                         });
             } 
             ...
         }
         return sWindowSession;
     }
    }
    

    这熟悉的代码格式,可以看出来这个session是一个单例,也就是整个应用的所有viewRootImpl的windowSession都是同一个,也就是一个应用只有一个windowSession。对于wms而言,他是服务于多个应用的,如果说每个viewRootImpl整一个session,那他的任务就太重了。WMS的对象单位是应用,他在内部给每个应用session分配了一些数据结构如list,用于保存每个应用的window以及对应的viewRootImpl。当需要操作view的时候,通过session直接找到viewRootImpl就可以操作了。

    后面的逻辑就交给WMS去处理了,WMS就会创建window,然后结合参数计算window的高度等等,最后使用viewRootImpl进行绘制。这后面的代码逻辑就不讲了,这是深入到WMS的内容,再讲进去就太复杂了(笔者也还没读懂WMS)。读源码的目的是了解整个系统的本质与工作流程,对系统整体的感知,而不用太深入代码细节,Android系统那么多的代码,如果深入进去会出不来的,所以点到为止就好了。

    我们知道windowManager接口是继承viewManager接口的,viewManager还有另外两个接口:removeView、updateView。这里就不讲了,有兴趣的读者可以自己去阅读源码。讲添加流程主要是为了理解window系统的运作,对内部的流程感知,以便于更好的理解window。

    最后做个总结:

    window的添加过程是通过PhoneWindow对应的WindowManagerImpl来添加window,内部会调用WindowManagerGlobal来实现。WindowManagerGlobal会使用viewRootImpl来进行跨进程通信让WMS执行创建window的业务。

    每个应用都有一个windowSession,用于负责和WMS的通信,如ApplicationThread与AMS的通信。

    window机制的关键类

    前面的源码流程中涉及到很多的类,这里把相关的类统一分析一下。先看一张图:

    window内部关键类

    这基本上是我们这篇文章涉及到的所有关键类。且听我慢慢讲。(图中绿色的window并不是一个类,而是真正意义上的window)

    window相关

    window的实现类只有一个:PhoneWindow,他继承自Window抽象类。后面我会重点分析他。

    WindowManager相关

    顾名思义,windowManager就是window管理类。这一部分的关键类有windowManager,viewManager,windowManagerImpl,windowManagerGlobal。windowManager是一个接口,继承自viewManager。viewManager中包含了我们非常熟悉的三个接口:addView,removeView,updateView
    windowManagerImpl和PhoneWindow是成对出现的,前者负责管理后者。WindowManagerImpl是windowManager的实现类,但是他本身并没有真正实现逻辑,而是交给了WindowManagerGlobal。WindowManagerGlobal是全局单例,windowManagerImpl内部使用桥接模式,他是windowManager接口逻辑的真正实现

    view相关

    这里有个很关键的类:ViewRootImpl。每个view树都会有一个。当我使用windowManager的addView方法时,就会创建一个ViewRootImpl。ViewRootImpl的作用很关键:

    • 负责连接view和window的桥梁事务
    • 负责和WindowManagerService的联系
    • 负责管理和绘制view树
    • 事件的中转站

    每个window都会有一个ViewRootImpl,viewRootImpl是负责绘制这个view树和window与view的桥梁,每个window都会有一个ViewRootImpl。

    WindowManagerService

    这个是window的真正管理者,类似于AMS(ActivityManagerService)管理四大组件。所有的window创建最终都要经过windowManagerService。整个Android的window机制中,WMS绝对是核心,他决定了屏幕所有的window该如何显示如何分发点击事件等等。

    window与PhoneWindow的关系

    解释一下标题,window是指window机制中window这个概念,而PhoneWindow是指PhoneWindow这个类。后面我在讲的时候,如果是指类,我会在后面加个‘类’字。如window是指window概念,window类是指window这个抽象类。读者不要混淆。

    还记得我在讲window的概念的时候留了一个思考吗?

    思考:Android中不是有一个抽象类叫做window还有一个PhoneWindow实现类吗,他们不就是window的存在形式,为什么说window是抽象不存在的

    这里我再抛出几个问题:

    • 有一些资料认为PhoneWindow就是window,是view容器,负责管理容器内的view,windowManagerImpl可以往里面添加view,如上面我们讲过的addView方法。但是,同时它又说每个window对应一个viewRootImpl,但却没解释为什么每次addView都会新建一个viewRootImpl,前后发送矛盾。
    • 有一些资料也是认为PhoneWindow是window,但是他说addView方法不是添加view而是添加window,同时拿这个方法的名字作为论据证明view就是window,但是他没解释为什么在使用addView方法创建window的过程却没有创建PhoneWindow对象。

    我们一步步来看。我们首先来看一下源码中对于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策略,如背景、标题区域、默认键处理等。
    

    大概意思就是:这个类是顶级窗口的抽象基类,顶级窗口必须继承他,他负责窗口的外观如背景、标题、默认按键处理等。这个类的实例被添加到windowManager中,让windowManager对他进行管理。PhoneWindow是一个top-level window(顶级窗口),他被添加到顶级窗口管理器的顶层视图,其他的window,都需要添加到这个顶层视图中,所以更准确的来说,PhoneWindow并不是view容器,而是window容器。

    那PhoneWindow的存在意义是什么?

    第一、提供DecorView模板。如下图:

    我们的Activity是通过setContentView把布局设置到DecorView中,那么DecorView本身的布局,就成为了Activity界面的背景。同时DecorView是分为标题栏和内容两部分,所以也可以可界面设置标题栏。同时,由于我们的界面是添加在的DecorView中,属于DecorView的一部分。那么对于DecorView的window属性设置也会对我们的布局界面生效。还记得谷歌的官方给window类注释的最后一句话吗:它提供标准的UI策略,如背景、标题区域、默认键处理等。这些都可以通过DecorView实现,这是PhoneWindow的第一个作用。

    第二、抽离Activity中关于window的逻辑。Activity的职责非常多,如果所有的事情都自己做,那么会造成本身代码极其臃肿。阅读过Activity启动的读者可能知道,AMS也通过ActivityStarter这个类来抽离启动Activity启动的逻辑。这样关于window相关的事情,就交给PhoneWindow去处理了。(事实上,Activity调用的是WindowManagerImpl,但因PhoneWindow和WindowManagerImpl两者是成对存在,他们共同处理window相关的事务,所以这里就简单写成交给PhoneWindow处理。)当Activity需要添加界面时,只需要一句setContentView,调用了PhoneWindow的setContentView方法,就把布局设置到屏幕上了。具体怎么完成,Activity不必管。

    第三、限制组件添加window的权限。PhoneWindow内部有一个token属性,用于验证一个PhoneWindow是否允许添加window。在Activity创建PhoneWindow的时候,就会把从AMS传过来的token赋值给他,从而他也就有了添加token的权限。而其他的PhoneWindow则没有这个权限,因而也无法添加window。这部分内容我在另一篇文章有详细讲解,感兴趣的读者可以前往了解一下传送门

    当然,PhoneWindow的作用肯定远不止如此,这里列出很重要的三条,也是笔者目前学习到的三个最重要的作用。官方对于一个类的设计的考虑肯定是非常多,不是笔者简单的分析所能阐述,而只是给出一个新的思考方向,带大家认识真正的window。

    总结一下:

    • PhoneWindow本身不是真正意义上的window,他更多可以认为是辅助Activity操作window的工具类。
    • windowManagerImpl并不是管理window的类,而是管理PhoneWindow的类。真正管理window的是WMS。
    • PhoneWindow可以配合DecorView可以给其中的window按照一定的逻辑提供标准的UI策略
    • PhoneWindow限制了不同的组件添加window的权限。

    常见组件的window创建流程

    上面讲的是通过windowManagerImpl创建window的过程,我们通过前面的讲解了解到,WindowManagerImpl是管理PhoneWindow的,他们是同时出现的。因而有两种创建window的方式:

    • 已经存在PhoneWindow,直接通过WindowManagerImpl创建window
    • PhoneWindow尚未存在,先创建PhoneWindow,再利用windowManagerImpl来创建window

    当我们在Activity中使用getWindowManager方法获取到的就是应用的PhoneWindow对应的WindowManagerImpl。下面来讲一下不同的组件是如何创建window的,

    Activity

    如果有阅读过Activity的启动流程的读者,会知道Activity的启动最后来到了ActivityThread的handleLaunchActivity这个方法。

    关于Activity的启动流程,我写过一篇文章,有兴趣的读者可以点击下方链接前往:

    Activity启动流程详解(基于api28)

    至于为什么是这个方法这里就不讲了,有兴趣的读者可以去看上面的文章。我们直接来看这个方法的代码:

    public void handleLaunchActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        ...;
        // 这里对WindowManagerGlobal进行初始化
        WindowManagerGlobal.initialize();
    
       	// 启动Activity并回调activity的onCreate方法
        final Activity a = performLaunchActivity(r, customIntent);
        ...
    }
    
    
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        try {
            // 这里创建Application
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
    		...
            if (activity != null) {
                ...
                Window window = null;
                if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                    window = r.mPendingRemoveWindow;
                    r.mPendingRemoveWindow = null;
                    r.mPendingRemoveWindowManager = null;
                }
                appContext.setOuterContext(activity);
                // 这里将window作为参数传到activity的attach方法中
                // 一般情况下这里window==null
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback,
                        r.assistToken);  
                ...
                // 最后这里回调Activity的onCreate方法
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
            }
        
        ...
    }
    

    handleLaunchActivity的代码中首先对WindowManagerGlobal进行初始化,然后调用了performLaunchActivity方法。代码很多,这里只截取了重要部分。首先会创建Application对象,然后再调用Activity的attach方法,把window作为参数传进去,最后回调activity的onCreate方法。所以这里最有可能创建window的方法就是Activity的attach方法了。我们进去看一下:

    final void attach(...,Context context,Window window, ...) {
        ...;
     	// 这里新建PhoneWindow对象,并对window进行初始化
    	mWindow = new PhoneWindow(this, window, activityConfigCallback);
        // Activity实现window的callBack接口,把自己设置给window
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);    
        ...
        // 这里初始化window的WindowManager对象
    	mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);        
    }
    

    同样只截取了重要代码,attach方法参数非常多,我只留下了window相关的参数。在这方法里首先利用传进来的window创建了PhoneWindow。Activity实现window的callBack接口,可以把自己设置给window当观察者。当window发生变化的时候可以通知activity。然后再创建WindowManager和PhoneWindow绑定在一起,这样我们就可以通过windowManager操作PhoneWindow了。(这里不是setWindowManager吗,windowManager是什么时候创建的?)我们进去setWindowManager方法看一下:

    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated;
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        // 这里创建了windowManager
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }
    

    这个方法里首先会获取到应用服务的WindowManager(实现类也是WindowManagerImpl),然后通过这个应用服务的WindowManager创建了新的windowManager。

    从这里可以看到是利用系统服务的windowManager来创建新的windowManagerImpl,因而这个应用所有的WindowManagerImpl都是同个内核windowManager,而创建出来的仅仅是包了个壳。

    这样PhoneWindow和WindowManagerImpl就绑定在一起了。Activity可以通过WindowManagerImpl来操作PhoneWindow。


    到这里Activity的PhoneWindow和WindowManagerImpl对象就创建完成了,接下来是如何把Activity的布局文件设置给PhoneWindow。在上面我讲到调用Activity的attach方法之后,会回调Activity的onCreate方法,在onCreate方法我们会调用setContentView来设置布局,如下:

    public void setContentView(View view, ViewGroup.LayoutParams params) {
        getWindow().setContentView(view, params);
        initWindowDecorActionBar();
    }
    

    这里的getWindow就是获取到我们上面创建的PhoneWindow对象。我们继续看下去:

    // 注意他有多个重载的方法,要选择参数对应的方法
    public void setContentView(int layoutResID) {
        // 创建DecorView
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
    
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            // 这里根据布局id加载布局
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            // 回调activity的方法
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }
    

    同样我们只看重点代码:

    • 首先看decorView创建了没有,没有的话创建DecorView
    • 把布局加载到DecorView中
    • 回调Activity的callBack方法

    这里补充一下什么是DecorView。DecorView是在PhoneWindow中预设好的一个布局,这个布局长这样:

    decorView

    他是一个垂直排列的布局,上面是ActionBar,下面是ContentView,他是一个FrameLayout。我们的Activity布局就加载到ContentView里进行显示。所以Decorview是Activity布局最顶层的viewGroup。

    然后我们看一下怎么初始化DercorView的:

    private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            // 这里创建了DecorView
            mDecor = generateDecor(-1);
            ...
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            // 对DecorView进行初始化,得到ContentView
            mContentParent = generateLayout(mDecor);
            ...
        }
    }
    

    installDecor方法中主要是新建一个DecorView对象,然后加载预设好的布局对DecorView进行初始化,(预设好的布局就是上面讲述的布局)并获取到这个预设布局的ContentView。好了然后我们再回到window的setContentView方法中,初始化了DecorView之后,把Activity布局加载到DecorView的ContentView中如下代码:

    // 注意他有多个重载的方法,要选择参数对应的方法
    public void setContentView(int layoutResID) {
        ...
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            // 这里根据布局id加载布局
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        ...
       	mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            // 回调activity的方法
            cb.onContentChanged();
        }
    }
    

    所以可以看到Activitiy的布局确实是添加到DecorView的ContentView中,这也是为什么onCreate中使用的是setContentView而不是setView。最后会回调Activity的方法告诉Activity,DecorView已经创建并初始化完成了。


    到这里DecorView创建完成了,但还缺少了最重要的一步:把DecorView作为window添加到屏幕上。从前面的介绍我们知道添加window需要用到WindowManagerImpl的addView方法。这一步是在ActivityThread的handleResumeActivity方法中被执行:

    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        // 调用Activity的onResume方法
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
        ...
        // 让decorView显示到屏幕上
    	if (r.activity.mVisibleFromClient) {
            r.activity.makeVisible();
      	}
    

    这一步方法有两个重点:回调onResume方法,把decorView添加到屏幕上。我们看一下makeVisible方法做了什么:

    void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }
    

    是不是非常熟悉?直接调用WindowManagerImpl的addView方法来吧decorView添加到屏幕上,至此,我们的Activity界面就会显示在屏幕上了。


    好了,这部分很长,最后来总结一下:

    • 从Activity的启动流程可以得到Activity创建Window的过程
    • 创建PhoneWindow -> 创建WindowManager -> 创建decorView -> 利用windowManager把DecorView显示到屏幕上
    • 回调onResume方法的时候,DecorView还没有被添加到屏幕,所以当onResume被回调,指的是屏幕即将到显示,而不是已经显示

    PopupWindow

    popupWindow日常使用的也比较多,最常见的需求是弹一个菜单出来等。popupWindow也是利用windowManager来往屏幕上添加window,但,popupWindow是依附于activity而存在的,当Activity未运行时,是无法弹出popupWindow的,通过源码可以知道,当调用onResume方法的时候,其实后续还有很多事情在做,这个时候Activity也是尚未完全启动,所以popupWindow不能在onCreate、onStart、onResume方法中弹出。

    弹出popupWindow的过程分为两个:创建view;通过windowManager添加window。首先看到PopupWindow的构造方法:

    public PopupWindow(View contentView, int width, int height, boolean focusable) {
        if (contentView != null) {
            mContext = contentView.getContext();
            mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        }
    
        setContentView(contentView);
        setWidth(width);
        setHeight(height);
        setFocusable(focusable);
    }
    

    他有多个重载方法,但最终都会调用到这个有四个参数的方法。主要是前面的得到context和根据context获得WindowManager。


    然后我们看到他的显示方法。显示方法有两个:showAtLocationshowAsDropDown。主要是处理显示的位置不同,其他都是相似的。我们看到第一个方法:

    public void showAtLocation(View parent, int gravity, int x, int y) {
        mParentRootView = new WeakReference<>(parent.getRootView());
        showAtLocation(parent.getWindowToken(), gravity, x, y);
    }
    

    逻辑很简单,父view的根布局存储了起来,然后调用另外的重载方法:

    public void showAtLocation(IBinder token, int gravity, int x, int y) {
        // 如果contentView是空直接返回
        if (isShowing() || mContentView == null) {
            return;
        }
    
        TransitionManager.endTransitions(mDecorView);
        detachFromAnchor();
        mIsShowing = true;
        mIsDropdown = false;
        mGravity = gravity;
    	// 得到WindowManager.LayoutParams对象
        final WindowManager.LayoutParams p = createPopupLayoutParams(token);
        // 做一些准备工作
        preparePopup(p);
    
        p.x = x;
        p.y = y;
    	// 执行popupWindow显示工作
        invokePopup(p);
    }
    

    这个方法的逻辑主要有:

    • 判断contentView是否为空或者是否进行显示
    • 做一些准备工作
    • 进行popupWindow显示工作

    这里我们看一下他的准备工作做了什么:

    private void preparePopup(WindowManager.LayoutParams p) {
        ...
            
        if (mBackground != null) {
            mBackgroundView = createBackgroundView(mContentView);
            mBackgroundView.setBackground(mBackground);
        } else {
            mBackgroundView = mContentView;
        }
    	// 创建了DecorView
        // 注意,这里的DecorView并不是我们之前讲的DecorView,而是他的内部类:PopupDecorView
        mDecorView = createDecorView(mBackgroundView);
        mDecorView.setIsRootNamespace(true);
    
        ...
    }
    

    接下来再看他的显示工作:

    private void invokePopup(WindowManager.LayoutParams p) {
        ...
       	// 调用windowManager添加window
        mWindowManager.addView(decorView, p);
    
        ...
    }
    

    到这里popupWindow就会被添加到屏幕上了。


    最后总结一下:

    • 根据参数构建popupDecorView
    • 把popupDecorView添加到屏幕上

    Dialog

    dialog的创建过程Activity比较像:创建PhoneWindow,初始化DecorView,添加DecorView。我这里就简单讲解一下。首先看到他的构造方法:

    Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        ...
        // 获取windowManager
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    	// 构造PhoneWindow
        final Window w = new PhoneWindow(mContext);
        mWindow = w;
        // 初始化PhoneWindow
        w.setCallback(this);
        w.setOnWindowDismissedCallback(this);
        w.setOnWindowSwipeDismissedCallback(() -> {
            if (mCancelable) {
                cancel();
            }
        });
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);
        mListenersHandler = new ListenersHandler(this);
    }
    

    这里和前面的Activity创建过程非常像,但是有个重点需要注意mWindowManager其实是Activity的WindowManager,这里的context一般是activity(实际上也只能是activity,非activity会抛出异常,相关内容读者有兴趣可以阅读这篇文章window的token验证),我们看到activity的getSystemService方法:

    public Object getSystemService(@ServiceName @NonNull String name) {
        if (getBaseContext() == null) {
            throw new IllegalStateException(
                    "System services not available to Activities before onCreate()");
        }
    	// 获取activity的windowManager
        if (WINDOW_SERVICE.equals(name)) {
            return mWindowManager;
        } else if (SEARCH_SERVICE.equals(name)) {
            ensureSearchManager();
            return mSearchManager;
        }
        return super.getSystemService(name);
    }
    

    可以看到这里的windowManager确实是Activity的WindowManager。接下来看到他的show方法:

    public void show() {
       ...
        // 回调onStart方法,获取前面初始化好的decorview
        onStart();
        mDecor = mWindow.getDecorView();
        ...
        WindowManager.LayoutParams l = mWindow.getAttributes();
        ...
        // 利用windowManager来添加window    
        mWindowManager.addView(mDecor, l);
        ...
        mShowing = true;
        sendShowMessage();
    }
    

    注意这里的mWindowManager是Activity的WindowManager,所以实际上,这里是添加到了Activity的PhoneWindow中。接下来的和前面的添加流程一样,这里我也不多讲解了。


    总结一下:

    • dialog和popupWindow不同,dialog创建了新的PhoneWindow,使用了PhoneWindow的DecorView模板。而popupWindow没有
    • dialog的显示层级数更高,会直接显示在Activity上面,在dialog后添加的popUpWindow也会显示在dialog下
    • dialog的创建流程和activity非常像

    从Android架构角度看Window

    前面我们介绍过关于PhoneWindow和window之间的关系,了解到PhoneWindow其实不是Window,只是一个window容器。不知读者有没想过一个问题,为什么谷歌要建一个不是window但却名字是window的类?是故意要迷惑我们吗?要了解这个问题,我们先来回顾一下整个android的window机制结构。

    首先从WindowManagerService开始,我们知道WMS是window的最终管理者,在WMS中为每一个应用持有一个session,关于session前面我们讲过,每个应用都是全局单例,负责和WMS通信的binder对象。WMS为每个window都建立了一个windowStatus对象,同一个应用的window使用同个session进行跨进程通信,结构大概如下:

    WMS结构

    而负责与WMS通信的,是viewRootImpl。前面我们讲过每个view树即为一个window,viewRootImpl负责和WMS进行通信,同时也负责view的绘制。如果把上面的图画仔细一点就是:

    更详细的结构图

    图中每一个windowStatus对应一个viewRootImpl,WMS通过viewRootImpl来控制view。这也就是window机制的管理结构。当我们需要添加window的时候,最终的逻辑实现是WindowManagerGlobal,他的内部使用自己的session创建一个viewRootImpl,然后向WMS申请添加window,结构图大概如下:

    window的添加结构

    windowManagerGlobal使用自己的IWindowSession创建viewRootImpl,这个IWindowSession是全局单例。viewRootImpl和WMS申请创建window,然后WMS允许之后,再通知viewRootImpl绘制view,同时WMS通过windowStatus存储了viewRootImpl的相关信息,这样如果WMS需要修改view,直接通过viewRootImpl就可以修改view了。


    从上面的描述中可以发现我全程没有提及到PhoneWindow和WindowManagerImpl。这是因为他们不属于window机制内的类,而是封装于window机制之上的框架。假设如果没有PhoneWindow和WindowManager我们该如何添加一个window?首先需要调用WindowGlobal获取session,再创建viewRootImpl,再访问wms,然后再利用viewRootImpl绘制view,是不是很复杂,而这仅仅只是整体的步骤。而WindowManagerImpl正是这个功能。他内部拥有WindowManagerGlobal的单例,然后帮助我们完成了这一系列的步骤。同时,windowManagerImpl也是只有一个实例,其他的windowManagerImpl都是建立在windowManagerImpl单例上。这一点在前面有通过源码介绍到。

    另外,上面我讲到PhoneWindow并不是window而是一个辅助Activity管理的工具类,那为什么他不要命名为windowUtils呢?首先,PhoneWindow这个类是谷歌给window机制进行更上一层的封装。PhoneWindow内部拥有一个DecorView,我们的布局view都是添加到decorView中的,因为我们可以通过给decorView设置背景,宽高度,标题栏,按键反馈等等,来间接给我们的布局view设置。这样一来,PhoneWindow的存在,向开发者屏蔽真正的window,暴露给开发者一个“存在的”window。我们可以认为PhoneWindow就是一个window,window是view容器。当我们需要在屏幕上添加view的时候,只需要获得应用window对应的windowManagerImpl,然后直接调用addView方法添加view即可。这里也可以解释为什么windowManager的接口方法是addView而不是addWindow,一是window确实是以view的存在形式没错,二是为了向开发者屏蔽真正的window,让我们以为是在往window中添加view,window是真实存在的东西。他们的关系画个图如下:

    window整体结构

    黄色部分输于谷歌提供给开发者的window框架,而绿色是真正的window机制结构。通过PhoneWindow我们可以很方便地进行window操作,而不须了解底层究竟是如何工作的。PhoneWindow的存在,更是让window的“可见性”得到了实现,让window变成了一个“view容器”。

    好了最后来总结一下:

    • Android内部的window机制与谷歌暴露给我们的api是不一样的,谷歌封装的目的是为了让我们更好地使用window。
    • dialog、popupWindow等框架更是对具体场景进行更进一步的封装。
    • 我们在了解window机制的时候,需要跳过应用层,看到window的本质,才能更好地帮助我们理解window。
    • 在android的其他地方也是一样,利用封装向开发者屏蔽底层逻辑,让我们更好地运用。但如果我们需要了解他的机制的时候,就需要绕过这层封装,看到本质。

    总结

    全文到这里,就基本结束了。下面先总结一下我这篇文章说了什么:

    • 详述了什么是window
    • 对window的各种参数进行讲解
    • 讲解window机制内的关键类
    • 从源码讲解window的添加流程以及各大组件的window添加流程
    • 详解了PhoneWindow与window的关系,谈了关于谷歌的封装思想

    文中最重要的一点就是认识window的本质,区分好window和view之间的关系以及window与PhoneWindow的关系。

    笔者在写这篇文章的时候,对于各节的安排是比较犹豫的:如果先讲概念,没有源码流程的讲解很难懂;先讲源码流程,没有概念的认知很难读懂源码。最终还是决定了先讲window的真正概念,先让读者有个整体上的感知。

    文章很长,笔者对于window想要讲的都在这篇文章中。

    希望文章对你有帮助。

    全文到此,感谢你的阅读

    原创不易,觉得有帮助可以点赞收藏评论转发关注。
    笔者才疏学浅,有任何错误欢迎评论区或私信交流。
    如需转载请私信交流。

    另外欢迎光临笔者的个人博客:传送门


    参考文献

    《Android开发艺术探索》

    《Android进阶解密》

    展开全文
  • JavaScript Window窗口对象

    千次阅读 多人点赞 2020-01-18 11:49:05
    文章目录一、Window对象概述1、Window对象属性2、Window对象方法3、如何使用二、对话框1、警告对话框2、确认对话框3、提示对话框三、打开与关闭窗口1、打开窗口2、关闭窗口(1)关闭当前窗口(2)关闭子窗口四、控制...


    一、Window对象概述

    Window对象可以用来打开浏览器,调整浏览器的位置、大小等等功能。

    Window对象可以处理框架和框架之间的关系,通过这种关系在一个框架中处理另一个框架中的文档。他也是所有对象的顶级对象,通过Window对象的子对象进行操作,可以实现更多的效果。

    1、Window对象属性

    每个对象都有自己的属性,顶级对象Window是所有其他子对象的父对象,它可以出现在每个页面上,并且可以咋单个JS应用程序中被多次使用。

    下表列出了一些Window对象常用属性:

    属性说明
    document对话框中显示当前文档
    frames表示当前对话框中所有frame对象的集合
    location指定当前文档的URL
    name对话框的名字
    status/defaultStatus状态栏中的当前信息
    top表示最顶层的浏览器对话框
    parent/opener表示包含当前对话框的夫对话框
    closed表示当前对话框是否关闭(布尔值)
    self表示当前对话框
    screen表示用户屏幕,提供屏幕尺寸、颜色深度等信息
    navigator表示浏览器对象,用于获取与浏览器相关的信息

    2、Window对象方法

    方法说明
    alert()弹出警告对话框
    confrim()在确认对话框显示指定的字符串
    prompt()弹出一个提示对话框
    open()打开新浏览器对话框并且显示URL或名字引用的文档,并设置创建对话框的属性
    close()关闭被引用的对话框
    focus()指定对话框放在所有浏览器最前面
    blur()指定对话框放在所有浏览器最后面
    scrollTo(x,y)把对话框滚动到指定的坐标
    scrollBy(offsetx,offsety)按照指定位移量滚动对话框
    setTimeout(timer)在指定的毫秒数过后,对传递的表达式求值
    setInerval(interval)指定周期性执行代码
    moveTo(x,y)将对话框移动到指定坐标
    moveBy(offsetx,offsety)将对话框移动到指定的位移量处
    resizeTo(x,y)设置对话框大小
    resizeBy(offsetx,offsety)按照指定的位移量设置对话框大小
    print()“打印”
    navigate(URL)使用对话框显示URL指定的页面

    3、如何使用

    JS运行使用一个字符串来给窗口命名,也可以使用一些关键字来代替。

    Window对象可以直接调用方法和属性,不需要使用new运算符来创建对象:

    window.属性名
    window.方法名(参数列表)
    
    //    使用关键字来代替window   //
    parent.属性名
    parent.方法名(参数列表)
    

    二、对话框

    为了响应用户的某种需求而弹出的小窗口

    1、警告对话框——alert()

    语法如下:

    window.alert(str);
    

    弹出警告对话框,str为对话框显示的字符串,一般运行结果如下:
    在这里插入图片描述

    2、确认对话框——confrim()

    语法如下:

    window.confrim(question);
    

    其中question是要显示在确认对话框的纯文本,也是表达了程序想让用户回答的问题

    而且他有一个返回值,如果单击了确定返回真、反之为假。

    比如下面的这段小程序:

    <script>
        var flag = window.confirm("你确定要关闭浏览器窗口吗?");
        if(flag)
            window.close();
    </script>
    

    会展示下面的结果:
    在这里插入图片描述

    3、提示对话框——prompt()

    弹出一个提示框,在提示框中有一个输入框,在显示输入框的时候,在输入框内显示提示字符串,在输入文本框显示缺省文本并等待用户输入,输入之后,单击确定返回用户输入的字符串,如果是取消则返回null值

    语法如下:

    window.prompt(str1,str2)
    

    参数说明:其中str1表示在对话框要被显示的信息,str2指定对话框内输入框的值。

    比如下面这段小程序:

    <script>
        function pro() {
            var message = document.getElementById("message");
            message.value = window.prompt(message.value,"返回的信息")
        }
    </script>
    <input id="message" type="text" size="40" value="请在此输入信息">
    <br><br>
    <input type="button" value="显示对话框" onClick="pro()">
    

    效果如下:
    在这里插入图片描述


    三、打开与关闭窗口

    1、打开窗口——open()

    语法格式:

    WindowVar = window.open(url,name,[location])
    

    参数说明:

    • WindowVar:如果open方法成功,返回一个Window对象的句柄。
    • url目标窗口的URL
    • name:目标窗口Window对象名称
    • location:大考窗口的可选参数
      他的可选参数,如下表所示:
    参数说明
    top窗口顶部距离屏幕顶部的像素数
    left窗口左端距离屏幕左端的像素数
    width/height宽度/高度
    scrollbars是否显示滚动条
    resizable设定对话框大小是否固定
    toolbar浏览器的工具条,包括后退及前进按钮
    menubar菜单条,一般包括有文件、编辑及其他一些条目
    location定位区,地址栏
    direction更新信息的按钮

    下面举几个例子:

    打开一个新窗口:window.open("new.html","new")

    打开一个指定大小的窗口:window.open("new.html","new","height=140,width=690")

    打开一个指定位置的窗口:window.open("new.html","new","top=300,left=200")

    打开一个带滚动条的固定窗口:window.open("new.html","new","scrollbars,resizable")

    2、关闭窗口

    (1)关闭当前窗口

    下面任意语句可以实现关闭当前窗口:

    window.close();
    close();
    this.close();
    

    例子:关闭子窗口时自动刷新父窗口

    步骤1: 在原来的页面,怼一个<a>链接,用于打开一个新窗口,代码如下:

    <a href="#" onClick="javascript:window.open('new.html','width=400,height=220')">打开链接</a>
    

    步骤2: 新建new.html,然后写入脚本和一个按钮,代码如下:

    <script>
        function clo() {
            alert("关闭子窗口!");
            window.opener.location.reload();        //刷新父窗口
            window.close();
        }
    </script>
    <input type="submit" value="关闭" onclick="clo()">
    

    效果如下:
    在这里插入图片描述

    (2)关闭子窗口

    通过窗口句柄以变量的形式进行保存,然后通过close方法关闭创建的窗口,语法如下:

    windowname.close();
    

    其中windowname表示已打开窗口的句柄

    例如,在主窗口旁边弹出一个子窗口,当单击主窗口的按钮后,自动关闭子窗口,代码如下:

    <form name="form1">
        <input type="button" name="Button" value="关闭子窗口" onclick="newclose()">
    </form>
    <script>
        var win = window.open("new.html","new","width=300,height=100");
        function newclose() {
            win.close();
        }
    </script>
    

    效果如下:
    在这里插入图片描述


    四、控制窗口

    1、移动窗口

    (1)moveTo()方法

    语法如下:

    window.moveTo(x,y);
    

    其中x,y是窗口左上角的坐标,

    (2)resizeTo()方法

    这个方法可以将当前窗口改成(x,y)大小,x、y分别代表宽度和高度,语法如下:

    window.resizeTo(x,y);
    

    (3)screen对象

    屏幕对象反映了当前用户的屏幕设置,下表列出了屏幕对象的一些属性:

    属性说明
    width整个屏幕的水平尺寸
    height整个屏幕的垂直尺寸
    pixelDepth显示器每个像素的位数
    colorDepth返回当前颜色设置所用的位数,1代表黑白;8代表256色(256种颜色);16代表增强色(大概64000种颜色);24/32代表真彩色(1600万种颜色)
    availWidth返回窗口内容区域的水平尺寸
    availHeight返回川康内容区域的垂直尺寸

    2、窗口滚动

    利用Window对象的scroll()方法可以指定窗口的当前位置,从而实现窗口的滚动效果,语法如下:

    window.scroll(x,y);			//滚动到指定的绝对位置,相对文档的左上角
    window.scrollTo(x,y);		//同上
    window.scrollBy(x,y);		
    /*可以将文档滚动到相对位置,如果参数x为正数,向右滚动,否则向左;如果参数y为正数向下滚动,否则向上*/
    

    比如选择一张我们的女神江疏影把图片拉大,然后写脚本:

    <img src="1.jpg" height="1000">
    <script>
        var pos = 0;
        function scroller() {
            pos++;
            scrollTo(0,pos);
            clearTimeout(timer);
            var timer = setTimeout("scroller()",10);
        }
        scroller();
    </script>![在这里插入图片描述](https://img-blog.csdnimg.cn/20200118113027730.gif)
    

    效果如下:(太美啦!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!)
    在这里插入图片描述

    3、改变窗口大小

    实现将当前窗口改变为指定的大小(x,y),当x,y>0为扩大,反之为缩小,语法如下:

    window.resizeBy(x,y);
    

    例子:动态改变窗口大小

    代码如下:

    <script>
        var winheight,winsize,x;
        function openwin() {
            winheight=100;
            winsize=100;
            x=5;
            win2 = window.open("new.html","","scrollbars='no'");
            win2.moveTo(0,0);
            win2.resizeTo(100,100);
            resize();
        }
        function resize() {
            if(winheight>=screen.availHeight-3)
                x=0;
            win2.resizeBy(5,x);
            winheight+=5;
            winsize+=5;
            if(winsize>=screen.width-5){
                winheight=100;
                winsize=100;
                return;
            }
            setTimeout("resize()",50);
        }
    </script>
    <a href="javascript:openwin()">打开一个自动改变大小的窗口</a>
    

    效果如下:
    在这里插入图片描述

    4、访问窗口历史

    利用history对象实现访问窗口历史,history对象是一个只读URL字符串数组,主要存储一个最近访问网页的URL地址列表。语法如下:

    [window.]history.property|method([parameters])
    

    history对象常用属性:

    属性说明
    length历史列表的长度,用于判断列表中的入口数目
    current当前文档的URL
    next历史列表的下一个URL
    previous历史列表的前一个URL

    常用方法:

    方法说明
    back()退回前一页
    forward()重新进入下一页
    go()进入指定网页

    比如使用history对象中的back()forward()方法来引导跳转:

    <a href="javascript:window.history.forward();">forward</a>
    <a href="javascript:window.history.back()">back</a>
    

    使用history.go()方法指定要访问的历史记录,如果参数为正数向前移动,否则向后移动

    <a href="javascript:window.history.go(-1)">向后退一次</a>
    <a href="javascript:window.history.go(2)">向前进两次</a>
    

    length属性访问history数组的长度,通过它可以很快访问末尾:

    <a href="javascript:window.history.go(window.history.length-1)">末尾</a>
    

    5、设置超时

    为一个窗口设置在某段时间后执行何种操作,称为设置超时

    window对象的setTimeout()方法用于设一个超时,语法如下:

    timerId = setTimeout(要执行的代码,以毫秒为单位时间)
    

    还可以使用clearTimeout()来中止该超时设置,语法格式如下:

    clearTimeout(timerId);
    

    展开全文
  • JavaScript中window.open()和Window Location href的区别

    万次阅读 多人点赞 2019-03-06 11:12:41
    3:window.open和window.location.href的区别 1:区别 2.window.open不一定是打开一个新窗口!!!!!!!! 3:关于重新定位 4.:在框架内指定页面打开连接 5:是否打开其他网站地址 6:window.open()经过设置后的弹...

    目录

     

    1:window.location.href的用法:

    2:window.open()的用法

    3: window.open和window.location.href的区别

    1:区别

    2.window.open不一定是打开一个新窗口!!!!!!!!  

    3:关于重新定位

    4.:在框架内指定页面打开连接

    5:是否打开其他网站地址

    6:window.open()经过设置后的弹出窗口 

     7:用函数控制弹出窗口: 

    8 :同时弹出两个窗口

    9: 【弹出的窗口之定时关闭控制】    

    10:【在弹出窗口中加上一个关闭按钮】 

    11:打开新页面


    1:window.location.href的用法:

    self.location.href;//当前页面打开URL页面
    
    window.location.href;//当前页面打开URL页面
    
    this.location.href;//当前页面打开URL页面
    
    location.href;// 当前页面打开URL页面
    
    parent.location.href;//在父页面打开新页面
    
    top.location.href;//在顶层页面打开新页面 

    2:window.open()的用法

    open() 方法用于打开一个新的浏览器窗口或查找一个已命名的窗口。

    window.open(URL,name,specs,replace)

    参数说明
    URL可选。打开指定的页面的URL。如果没有指定URL,打开一个新的空白窗口
    name可选。指定target属性或窗口的名称。支持以下值:
    • _blank - URL加载到一个新的窗口。这是默认
    • _parent - URL加载到父框架
    • _self - URL替换当前页面
    • _top - URL替换任何可加载的框架集
    • name - 窗口名称
    specs可选。一个逗号分隔的项目列表。支持以下值:
     
    channelmode=yes|no|1|0是否要在影院模式显示 window。默认是没有的。仅限IE浏览器
    directories=yes|no|1|0是否添加目录按钮。默认是肯定的。仅限IE浏览器
    fullscreen=yes|no|1|0浏览器是否显示全屏模式。默认是没有的。在全屏模式下的 window,还必须在影院模式。仅限IE浏览器
    height=pixels窗口的高度。最小.值为100
    left=pixels该窗口的左侧位置
    location=yes|no|1|0是否显示地址字段.默认值是yes
    menubar=yes|no|1|0是否显示菜单栏.默认值是yes
    resizable=yes|no|1|0是否可调整窗口大小.默认值是yes
    scrollbars=yes|no|1|0是否显示滚动条.默认值是yes
    status=yes|no|1|0是否要添加一个状态栏.默认值是yes
    titlebar=yes|no|1|0是否显示标题栏.被忽略,除非调用HTML应用程序或一个值得信赖的对话框.默认值是yes
    toolbar=yes|no|1|0是否显示浏览器工具栏.默认值是yes
    top=pixels窗口顶部的位置.仅限IE浏览器
    width=pixels窗口的宽度.最小.值为100
    replaceOptional.Specifies规定了装载到窗口的 URL 是在窗口的浏览历史中创建一个新条目,还是替换浏览历史中的当前条目。支持下面的值:
    • true - URL 替换浏览历史中的当前条目。
    • false - URL 在浏览历史中创建新的条目。

    3: window.open和window.location.href的区别

    1:区别

    window.location是window对象的属性,而window.open是window对象的方法
      window.location是你对当前浏览器窗口的URL地址对象的参考!   
      window.open是用来打开一个新窗口的函数! 


    2.window.open不一定是打开一个新窗口!!!!!!!!  

     
      只要有窗口的名称和window.open中第二个参数中的一样就会将这个窗口替换,用这个特性的话可以在iframe和frame中来代替location.href。 
    如<iframe name="aa"></iframe>   
      <input type=button   οnclick="window.open('1.htm','aa','')">和   
      <input type=button   
       οnclick="self.frames['aa'].location.href='1.htm'">的效果一样 

    3:关于重新定位


    在给按钮、表格、单元格、下拉列表和DIV等做链接时一般都要用Javascript来完成,和做普通链接一样,可能我们需要让链接页面在当前窗口打开,也可能需要在新窗口打开,这时我们就可以使用下面两项之一来完成: 
        window.open 用来打开新窗口 
        window.location 用来替换当前页,也就是重新定位当前页 
        可以用以下来个实例来测试一下。 
    <input type="button" value="新窗口打开" οnclick="window.open('http://www.google.com')"> 
    <input type="button" value="当前页打开" οnclick="window.location='http://www.google.com/'">  

    4.:在框架内指定页面打开连接


    window.location或window.open如何指定target? 
    这是一个经常遇到的问题,特别是在用frame框架的时候 
    解决办法: 
    window.location 改为 top.location 即可在顶部链接到指定页 
    或 
    window.open("你的网址","_top"); 


    5:是否打开其他网站地址


    window.open()是可以在一个网站上打开另外的一个网站的地址 

    而window.location()是只能在一个网站中打开本网站的网页 

      window.open()详解 

    <script type="text/javascript"> 
     <!-- window.open ('page.html'); --> 
    </script> 
        
      因为着是一段javascripts代码,所以它们应该放在<SCRIPT LANGUAGE="javascript">标签和</script>之间。<!-- 和 -->是对一些版本低的浏览器起作用,在这些老浏览器中不会将标签中的代码作为文本显示出来。 
    要养成这个好习惯啊。 
    window.open ('page.html') 用于控制弹出新的窗口page.html,如果page.html不与主窗口在同一路径下,前面应写明路径,绝对路径(http://)和相对路径(../)均可。用单引号和双引号都可以,只是不要混用。这一段代码可以加入HTML的任意位置,<head>和</head>之间可以,<body>间</body>也可以,越前越早执行,尤其是页面代码长,又想使页面早点弹出就尽量往前放。 
      

    6:window.open()经过设置后的弹出窗口 


       
      下面再说一说弹出窗口的设置。只要再往上面的代码中加一点东西就可以了。 我们来定制这个弹出的窗口的外观,尺寸大小,弹出的位置以适应该页面的具体情况。 
       
      <SCRIPT LANGUAGE="javascript"> 
      <!-- 
      window.open ('page.html', 'newwindow', 'height=100, width=400, top=0, left=0, toolbar=no, menubar=no, scrollbars=no, resizable=no,location=n o, status=no') //这句要写成一行 
      --> 
      </SCRIPT> 
       
      参数解释: 
       
      window.open 弹出新窗口的命令; 
      'page.html' 弹出窗口的文件名; 
      'newwindow' 弹出窗口的名字(不是文件名),非必须,可用空''代替; 
      height=100 窗口高度; 
      width=400 窗口宽度; 
      top=0 窗口距离屏幕上方的象素值; 
      left=0 窗口距离屏幕左侧的象素值; 
      toolbar=no 是否显示工具栏,yes为显示; 
      menubar,scrollbars 表示菜单栏和滚动栏。 
      resizable=no 是否允许改变窗口大小,yes为允许; 
      location=no 是否显示地址栏,yes为允许; 
      status=no 是否显示状态栏内的信息(通常是文件已经打开),yes为允许; 
      </SCRIPT> 
       

     7:用函数控制弹出窗口: 


       
      下面是一个完整的代码。 
      <html> 
      <head> 
      <script LANGUAGE="JavaScript"> 
      <!-- 
      function openwin() { 
      window.open ("page.html", "newwindow", "height=100, width=400, toolbar =no, menubar=no, scrollbars=no, resizable=no, location=no, status=no") //写成一行 
      } 
      //--> 
      </script> 
      </head> 
      <body οnlοad="openwin()"> 
      任意的页面内容... 
      </body> 
      < ml> 

      这里定义了一个函数openwin(),函数内容就是打开一个窗口。在调用它之前没有任何用途。怎么调用呢? 

      方法一:<body οnlοad="openwin()"> 浏览器读页面时弹出窗口; 
      方法二:<body οnunlοad="openwin()"> 浏览器离开页面时弹出窗口; 
      方法三:用一个连接调用: 
      <a href="#" οnclick="openwin()">打开一个窗口</a> 
      注意:使用的“#”是虚连接。 
      方法四:用一个按钮调用: 
      <input type="button" οnclick="openwin()" value="打开窗口"> 

      
      

    8 :同时弹出两个窗口

     

     对源代码稍微改动一下: 
       
      <script LANGUAGE="JavaScript"> 
      <!-- 
      function openwin() { 
      window.open ("page.html", "newwindow", "height=100, width=100, top=0, left=0,toolbar=no, menubar=no, scrollbars=no, resizable=no, location=n o, status=no")//写成一行 
      window.open ("page2.html", "newwindow2", "height=100, width=100, top=1 00, left=100,toolbar=no, menubar=no, scrollbars=no, resizable=no, loca tion=no, status=no")//写成一行 
      } 
      //--> 
      </script> 
      为避免弹出的2个窗口覆盖,用top和left控制一下弹出的位置不要相互覆盖即可 。最后用上面说过的四种方法调用即可。 
      注意:2个窗口的name(newwindows和newwindow2)不要相同,或者干脆全部为空。 

      【主窗口打开文件1.htm,同时弹出小窗口page.html】 

      如下代码加入主窗口<head>区: 
      <script language="javascript"> 
      <!-- 
      function openwin() { 
      window.open("page.html","","width=200,height=200") 
      } 
      //--> 
      </script> 
      加入<body>区: 
      <a href="1.htm" οnclick="openwin()">open</a>即可。 
     

    9: 【弹出的窗口之定时关闭控制】 
       


      下面我们再对弹出的窗口进行一些控制,效果就更好了。如果我们再将一小段 代码加入弹出的页面(注意是加入page.html的HTML中,可不是主页面中,否则 ...),让它10秒后自动关闭是不是更酷了? 
    首先,将如下代码加入page.html文件的<head>区: 
      <script language="JavaScript"> 
      function closeit() 
      { 
      setTimeout("self.close()",10000) //毫秒 
      } 
      </script> 
      然后,再用<body οnlοad="closeit()"> 这一句话代替page.html中原有的<BODY>这一句就可以了。(这一句话千万不要忘记写啊!这一句的作用是调用关闭窗 口的代码,10秒钟后就自行关闭该窗口。) 
     

    10:【在弹出窗口中加上一个关闭按钮】 

     <FORM>
     <INPUT TYPE='BUTTON' VALUE='关闭' onClick='window.close()'>
     </FORM>
     呵呵,现在更加完美了!

      然后,用<body οnlοad="loadpopup()">(注意不是openwin而是loadpop啊!)替换主页面中原有的<BODY>这一句即可。你可以试着刷新一下这个页面或重新进 入该页面,窗口再也不会弹出了。  

    11:打开新页面


    用window.open()打开新页面 
    但是用window.location.href="" 却是在原窗口打开的. 
    有时浏览器会一些安全设置window.open肯定被屏蔽。例如避免弹出广告窗口。 

    展开全文
  • js的window对象与属性的使用

    万次阅读 2018-11-23 06:11:36
     浏览器对象:window对象。  Window 对象会在 &lt;body&gt; 或 &lt;frameset&gt; 每次出现时被自动创建。 2. window对象 window对象是BOM中所有对象的核心。BOM Browser Object Model 3.window...

    1.BOM的概述
        browser object modal :浏览器对象模型。
        浏览器对象:window对象。
        Window 对象会在 <body> 或 <frameset> 每次出现时被自动创建。

    2. window对象

    window对象是BOM中所有对象的核心。BOM    Browser  Object Model

    3.window对象的属性

    window.open(), (打开窗口)
    window.close(), (关闭一个窗口)
    window.self()(窗口本身)  
    window.focus()(使当前的窗口在所有窗口之前. )
    window.status=”内容” (js中状态栏显示内容:)
    window.navigate(”http://www.google.com”); (js中的窗口重定向:)
    window.print() (js中的打印:)
    window.prompt(”message”,”defaultreply”); (js中的提示输入框:)
    window.scroll(x,y) (js中的窗口滚动条)
    window.scrollby(js中的窗口滚动到位置:)
    window.history.back()返回上一页
    window.history.forward()返回下一页,
    window.history.go(返回第几页,也可以使用访问过的url) 如果是0那么就是刷新
    history.length 
    window.createElement
    

    1.(位置类型-获得浏览器的位置)
    IE:
    window.screenLeft
    可以获得浏览器距屏幕左上角的左边距  
    window.screenTop
    可以获得浏览器距屏幕左上角的上边距

     FF:  
    alert(screenX)  
    alert(screenY)

    //IE
    左边距
    alert(screenLeft)
    上边距
    alert(screenTop)
    
    //FF
    左边距
    alert(screenX)
    上边距
    alert(screenY)

    (获得浏览器的尺寸)

    FF:window.innerWidth  获得窗口的宽度
    window.innerHeight  获得窗口的高度

    //FF:
    alert(window.innerWidth);
    alert(window.innerHeight);
    //IE:
    alert(document.documentElement.clientWidth)
    alert(document.documentElement.clientHeight)
    

    2.窗体控制

    screen对象记录了客户端显示屏的信息

    a.属性

    availHeight   返回显示屏幕的高度 (除 Windows 任务栏之外)。

    availWidth    返回显示屏幕的宽度 (除 Windows 任务栏之外)。

    height       返回显示屏幕的高度。

    width        返回显示屏幕的宽度。

    <script>
      document.write(screen.availHeight)
      document.write("<br/>")
      document.write(screen.height)
       document.write("<hr/>")
      document.write(screen.availWidth)
       document.write("<br/>")
      document.write(screen.width)
    </script>

    b.方法

    对窗体的移动,window.moveBy(x,y)  相对于当前位置沿着X\Y轴移动指定的像素,如负数是反方向。moveTo(x,y)  相对于浏览器的左上角沿着X\Y轴移动到指定的像素,如负数是反方向。

    窗体尺寸的改变,resizeBy(x,y)  相对于当前窗体的大小,调整宽度和高度。resizeTo(x,y)  把窗体调整为指定宽度和高度

    script>
    //窗体控制
    //位置
    moveBy(100,100);
    //moveTo(200,200)
    //尺寸
     window.resizeBy(100,100)
    resizeTo(400,400)
    </script>

    对窗体滚动条的控制,scrollBy(x,y) 相对于当前滚动条的位置移动的像素(前提有滚动条)。scrollTo(x,y) 相对于当前窗口的高度或宽度,移动到指定的像素

    scrollBy(0,100)
    scrollTo(0,200)

    innerHeight: 
    innerWidth:  IE不支持

     </head>
    	<script type="text/javascript">
    	<!--
    		  /*
    			window对象的属性:
    					  1.innerHeight: 返回文档显示区的高度  
    					  2.innerWidth: 返回文档显示区的宽度   IE不支持
    						  通用写法: window.document.body.clientWidth ;
    					  3. outerheight  包括了工具栏,菜单栏等的高度
    					  4. outerwidth   包括滚动条的宽度
    
    		  */
    		function init(){
    			var x = window.document.body.clientWidth ;
    			var y = window.document.body.clientHeight ;
    			alert(x + ":" + y) ;
    		}
    	//-->
    	</script>
     <body onload = "init()">
           <p>你好</p>
     </body>

    3.window.event window事件

    获取事件对象,当没有事件发生的时候为null。

     window.event
     window.onload=function  (e) {
    	 e
    	 var ev=e||window.event;
    }

    a.鼠标事件

    相对于浏览器位置的(左上角为(0,0))
         clientX  当鼠标事件发生的时候,鼠标相对于浏览器X轴的位置
         clientY  当鼠标事件发生的时候,鼠标相对于浏览器Y轴的位置

    相对于屏幕位置的
         screenX   当鼠标事件发生的时候,鼠标相对于屏幕X轴的位置
         screenY

      window.onload=function  (e) {
         window.event
    	 var ev=e||window.event;
    	 var div1=document.getElementById("div1");
         document.onmousemove=function  (e) {
           var ev=e||window.event;
    	   var cx=ev.clientX;
    	   var cy=ev.clientY;
    	   var sx=ev.screenX;
    	   var sy=ev.screenY;
    	   div1.innerHTML="cx:"+cx+"--cy:"+cy+"<br/>sx:"+sx+"--sy:"+sy;
    	 }
    }
    
    <div id="div1" style="width:200px;height:200px;border:1px solid red">

    相对于事件源的位置
    IE:
    offsetX   当鼠标事件发生的时候,鼠标相对于事件源X轴的位置
    offsetY

    FF:
    layerX   当鼠标事件发生的时候,鼠标相对于事件源X轴的位置
    laterY

     window.onload=function  (e) {
         window.event
    	 var ev=e||window.event;
    	 var div1=document.getElementById("div1");
         div1.onclick=function  (e) {
         var ev=e||window.event;
    	 var ox=ev.offsetX ||ev.layerX;
    	 var oy=ev.offsetY ||ev.layerY;
         div1.innerHTML="ox:"+ox+"--oy:"+oy;
         }

    具体使用

    模拟窗口拖拽

    divs=document.createElement("div");	  
     divs.onmousedown=function  (e) {
            var ev=e||window.event;
            var ox=ev.offsetX ||ev.layerX;//第一次点击div不能动,相对于事件源的位置
    	     var oy=ev.offsetY ||ev.layerY;
    		 var ok=true;//标识鼠标放开的时候还移动
    		this.onmousemove=function  (e) {//移动的时候跟随鼠标移动
    		   if(ok){
    		   var ev=e||window.event;
    		   var cx=ev.clientX;
    		   var cy=ev.clientY;
    		   this.style.top=cy-oy+"px";//绝对定位
    		   this.style.left=cx-ox+"px";
    		   }
    		}
    		this.onmouseup=function  () {
             if(ok){
    		   ok=false;
    		 }
    	   }
    	   }
    
    <input type="button" id="but">

    b.键盘事件对象

    keyCode  获得键盘码
    空格:32   回车13  左上右下:37 38 39 40
    altKey   判断alt键是否被按下  按下是true 反之是false   布尔值
    ctrlKey   判断ctrl键
    shiftKey 判断shift键
    type   用来检测事件的类型   主要是用于多个事件通用一个事件处理程序的时候

    document.body.onkeydown=function  (e) {
    	   var ev=e||window.event;
    	   ev.keyCode
    	   ev.altKey
    	   ev.type
    }

    具体使用

    点击提交,内容自动读取

    <Script>
      window.onload=function  () {
        var one=document.getElementById("one");
        var texts=document.myform.texts;
    	var but=document.myform.but;
    	but.onclick=texts.onkeydown=function  (e) {//回车
    	   var ev=e||window.event;
           if(ev.type=="click" ||(ev.type=="keydown" && ev.keyCode==13 && ev.ctrlKey==true)){
    	      var elep=document.createElement("p");
    		  elep.innerHTML=texts.value;
    		  elep.className="pone";
    		  one.appendChild(elep);
    		  texts.value="";
    
    	   }
    	}
    
      }
    </script>
    
    
    <body>
    <div id="one" style="width:400px; background-color:#eeeeee;padding:10px">
      <h3>
         留言记录:
      </h3>
      <hr/>
      <p class="pone">
         你好
      </p>
    </div>
    <form name="myform">
      <textarea name="texts" cols=50 rows=10>
      </textarea>
      <input type="button" value="提交" id="but">
    </form>
    </body>

    4.关系类型

     A.parent返回父窗口
     B.top返回顶层窗口

     C.self===window

     D.stutas  设置窗口状态栏的文本

    <script>
      window.onload=function  () {
         alert(top===parent)
    	window.status="自定义的状态栏文字"
    	alert(frames[0])
      }
    </script>
    
    <frameset rows="20%,*">
       <frame src="top.html" >
       <frameset cols="20%,*" >
       <frame src="left.html" >
       <frame src="right.html" >
       </frameset>
    </frameset>

    self :等同于window对象
    opener:当前打开窗口的父窗口对象,支持opener.opener…的多重继续。
                       2种情况下使用opener:
                       1.使用winodw.open()方法打开的页面
                       2.超链(里面的target属性要设置成_blank)
    open方法,是打开一个页面.

    js中分为两种窗体输出:模态和非模态.window.showmodaldialog(),window.showmodeless()

    js的window.open()方法的使用

    open(string method,string url,boolean asynch,String username,string password)

    指定和服务器端交互的HTTP方法,URL地址,即其他请求信息;
    method:表示http请求方法,一般使用"GET","POST".
    url:表示请求的服务器的地址;
    asynch:表示是否采用异步方法,true为异步,false为同步;
    后边两个可以不指定,username和password分别表示用户名和密码,提供http认证机制需要的用户名和密码。

            		var url = "completeFormone.html?s=" + Math.random()+"&installAcceptId="+rows[0].installAcceptId;
            		window.open(url);

    打开新的窗口,open(url,name,feafurse,replace) 通过脚本打开新的窗口。

    open("test.html","windows","status=0,menubar=0,toolbar=0")
     window.onload=function  () {
        var names=document.getElementById("names");
    	var but=document.getElementById("but");
    	but.onclick=function  () {
    	  open("test.html","windows","status=0,menubar=0,toolbar=0")
    	}
     }

    模态和非模态.window.showmodaldialog(),window.showmodeless()

    showmodaldialog(”url”[,arguments][,features])

    重新打开一个页面

    	<script type="text/javascript">
    	<!--
    		function fun(){
    		
    			window.open("sub.html","","width=200,height=200,status=no,titlebar=no,menubar=no,toolbar=no,resizable=0") ;
    		}
    	//-->
    	</script>
     <body>
    	<input type="button" value="打开sub.html页面" onclick="fun()">
     </body>
    		  function fun(){
    			 self.open("sub.html") ;
    		  }
    
    	</script>
     <body>
          <input type="text" name="" id = "txt"> 
    	  <input type="button" value="打开sub.html页面" onclick="fun()">
    
    	  <a href = "sub.html" target = "_blank">打开sub.html页面</a>
     </body>

    openWindow()参数的传递与关闭刷新

    点击弹出一个新窗口

    	afvButton.click(function(){
    		debugger;
    		var orandid = $($("body input[id='orandid_view_act']"),$("div[id='divMain']",$("body",parent.document)).context.activeElement).val();
    		var volid = _grid.getIds();
    		openWindow(volid+"&volType=1",orandid);
    	})
    
    function openWindow(ids,orandid){
    	var options = {
    		modal : true,
    		title : "站箱调压器AFV检修记录",
    		collapsible : false,
    		minimizable : false,
    		maximizable : false,
    		closable : true,
    		closed : false
    	};
    	var uid = "self_card_";
    	options["id"] = uid;
    	winFormDesigner = UIFactory.getUI(uid);
    	if(!winFormDesigner){
    		winFormDesigner = UIFactory.create(xpad.ui.Window, options);
    	}
    	var root = jQuery("body");
    	var offset = root.offset();
    	var winleft = 0;
    	var wintop = 0;
    	var newSize = {};
    	newSize["left"] = 0;
    	newSize["top"] = 0;
    	newSize["width"] = jQuery("body").width();
    	newSize["height"] = jQuery("body").height();
    	winFormDesigner.window("resize", newSize);
    	setTimeout(function(){
    			winFormDesigner.loadURL(Leopard.getContextPath() + "/platform/views/cusviews/devMatainView/afvVoltage.jsp?ids="+ids+"&orandid="+orandid);
    		
    	}, 0);
    }
    

    设置窗口的滚动条

    为class为list_wrap增加overflow:auto属性,并动态设置高度

    如果内容被修剪,则浏览器会显示滚动条,以便查看其余内容

    <script type="text/javascript">
    $(function(){
    var height = $(window).height();
    $(".list_wrap").css("height",height);	
    })
    </script>
    

    参数的传递

    jsp获取openWindow传递的参数

    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
    <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
    <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
    
    <c:set var="ctx" value="${pageContext.request.contextPath}" />
    <input id="root" type="hidden" value="${ctx }"/>
    <input id="ids" type="hidden" value="<%=request.getParameter("ids") %>"/>
    <input id="volType" type="hidden" value="<%=request.getParameter("volType") %>"/>
    <input id="orandid" type="hidden" value="<%=request.getParameter("orandid") %>"/>

    js获取jsp页面的值

    var root = null;
    var ids = null;
    var xcbh = null;
    
    
    $(document).ready(function() {
    	root = $("#root").val();
    	ids = $("#ids").val();
    	volType = $("#volType").val();
    	orandid = $("#orandid").val();
    	initpage();
    });
    
    function initpage(){
    	var isRead = $("#isRead").val();
    	if(isRead && isRead=="true"){
    		$(".tb_query").show();
    	}else{
    		$(".tb_query").hide();
    	}
    	root = $("#root").val();
    	
    	showTime();
    	
    	if(ids!="null"){
    		readxctyz();
    		readxctyzx();
    	}
    	var timer = "";
    	$("#save").click(function(){
    		xctyz();
    		$(this).attr("disabled", true); 
    		timer = setTimeout(function(){
    			$("#save").attr("disabled", false); 
            },6000);
    
    	})
    	
    	 $("#reset").click(function(){
    		    tjbxxcz();
    		    tyzxccz();
    	})
    }

    后台接收参数

    	@SuppressWarnings("unchecked")
    	@RequestMapping("/Addxctyz")
    	@ResponseBody
    	public Boolean Addxctyz(HttpServletRequest request, HttpServletResponse response,String requestParam){
    		String orandid = request.getParameter("orandid ");
    		String ids = request.getParameter("ids");
    }

    关闭openwindow刷新页面

    在外面div设置刷新按钮

    if($("#reloadGrid").length==0){
    	$("#SY_TYZJKXC-QForm .formBody").append("<button id='reloadGrid' style="dispaly:none">刷新</button>");
    	$("#reloadGrid").hide();
    	$("#reloadGrid").click(function(){
    		_grid.reload();
    	});
    }

    返回刷新外层div

        $.ajax({  
            url:root + "/AddAfv",  
            data:param,  
            type:"post",  
            dataType:"json",  
            success:function(data){  
                alert("保存成功");  
                debugger;
                $($("#reloadVolGrid",$("#layout_RECODR_MAINTAIN_VOLTAGE_listbar",parent.$(".panel window").context))).click();
            },  
            error:function(){  
            	alert("服务器正忙,请稍后重试");  
            }  
        }) 

    窗口全屏大小:

    <script>function fullscreen(){ this.moveto(0,0);this.outerwidth=screen.availwidth;this.outerheight=screen.availheight;}window.maximize=fullscreen;</script> 
    

    close方法

     <body>
    	<script type="text/javascript">
    	<!--
    		window.close() ;
    	//-->
    	</script>
     </body>

    parent:是打开窗口的父窗口对象

    2种情况下使用parent:
                       1.iframe 框架
                       2.frame 框架
    frames[]: 数组类型,代表子窗口的window对象的集合,可以通过frames[索引]拿到子窗口的window对象。
    示例:父子窗口相互传参.

      <title>window对象的parent属性</title>
     </head>
    	<script type="text/javascript">
    	<!--
    		function fun(){
    			//1.拿到文本框中填写的数据
    			   var v = document.getElementById("txt").value ;
    			//2.拿到子窗口对象
    			   var w = window.frames[0];
    			//3.拿到子窗口中的文本框对象
    			   var txt = w.document.getElementById("txt") ;
    			//4.将内容赋值给父窗口中的文本框对象的value属性
    				txt.value = v ;
    		}
    	//-->
    	</script>
     <body>
    	  姓名:<input type="text" name="" id = "txt"><input type="button" value="传递数据到子窗口中" onclick="fun()">
    	  <iframe src = "sub1.html"></iframe>
     </body>

    sub1.html

     </head>
    	<script type="text/javascript">
    	<!--
    		function fun(){
    			//1.拿到文本框中填写的数据
    			   var v = document.getElementById("txt").value ;
    			//2.拿到父窗口对象
    			   var w = window.parent;
    			//3.拿到父窗口中的文本框对象
    			   var txt = w.document.getElementById("txt") ;
    			//4.将内容赋值给父窗口中的文本框对象的value属性
    				txt.value = v ;
    		}
    	//-->
    	</script>
     <body>
          <input type="text" name="" id = "txt"><input type="button" value="传递数据到父窗口中" onclick="fun()">
     </body>

     对话框:
    1)消息框 alert() ;
    2)确认框 confirm() ;
    3)输入框 prompt() ; (了解)

    	<script type="text/javascript">
    	<!--
    		/*
    			三种对话框:1. 消息框:alert() ;
    					    2. 确认框: confirm() :返回Boolean类型的值
    						3. 输入框: prompt(): 返回输入的字符串(了解)
    		*/
    		//window.alert("你好") ;
    
    		/*while(true){
    			if(confirm("你爱我吗?") == false)
    			   continue ;
    			break ;
    		}*/
    
    		var a = prompt("请输入年龄:",12) ;
    		alert(a) ;
    	//-->
    	</script>

    window的模态窗体

     <body>
    	  <script type="text/javascript">
    	  <!--
    		   /*
    			    模态窗体:
    		   */
    
    		 //  window.showModalDialog("你好") ;  
    		  window.showModelessDialog("你好");
    	  //-->
    	  </script>
     </body>

    history对象

    history对象包含浏览器访问过的url,浏览器的历史记录访问

    <Script>
      window.onload=function  () {
         var one=document.getElementById("one");
    	 one.onclick=function  () {
    	    history.forward()
                history.back()
    		history.go(-3)
    		history.go(3)
    	 }
      }
    </script>
    
    <body>
    <p>
       history1.html
    </p>
    <script>
      alert(history.length)
    </script>
    <a href="history2.html">链接到2</a>
    <input type="button" value="前进" id="one">
    
    </body>

    a.  forward()前进  b.  back() 后退  c.  go(n) 正数是前进,负数是后退.

     </head>
    	 <script type="text/javascript">
    	 <!--
    		  /*
    			   history对象存储了访问过的页面。
    		  */
    
    		  function fun(){
    				history.forward();
    		  }
    	 //-->
    	 </script>
     <body>
    	  <a href = "b.html">b.html</a>
    	  <input type="button" value="前进" onclick="fun()">
     </body>

    b.html

     <script type="text/javascript">
    	 <!--
    		  /*
    			   history对象存储了访问过的页面。
    		  */
    
    		  function fun(){
    				history.forward();
    		  }
    
    		  function fun1(){
    			   history.back() ;
    		  }
    
    		  function fun2(){
    			   history.go(2) ;
    		  }
    	 //-->
    	 </script>
     <body>
    	  <a href = "c.html">c.html</a>
    	  <input type="button" value="前进" onclick="fun()">
    	  <input type="button" value="后退" onclick="fun1()">
    	  <input type="button" value="直接去d页面" onclick="fun2()">
     </body>

    c.html

     <script type="text/javascript">
    	 <!--
    		  /*
    			   history对象存储了访问过的页面。
    		  */
    
    		  function fun(){
    				history.forward();
    		  }
    
    		  function fun1(){
    			   history.back() ;
    		  }
    
    		  function fun2(){
    			   history.go(-2) ;
    		  }
    	 //-->
    	 </script>
     <body>
    	  <a href = "d.html">d.html</a>
    	  <input type="button" value="前进" onclick="fun()">
    	  <input type="button" value="后退" onclick="fun1()">
    	  <input type="button" value="直接去a页面" onclick="fun2()">
     </body

    d.html

     <script type="text/javascript">
    	 <!--
    		  /*
    			   history对象存储了访问过的页面。
    		  */
    
    		  function fun1(){
    			   history.back() ;
    		  }
    
    		  function fun2(){
    			   history.go(-3) ;
    		  }
    	 //-->
    	 </script>
     <body>
    	  <input type="button" value="后退" onclick="fun1()">
    	  <input type="button" value="直接去a页面" onclick="fun2()">
     </body>

    location对象

    location对象包含有当前url的相关信息
    1.href 属性: 是指要连接到其他的URL,返回完整的url
                            写法一、window.location.href='demo_window对象的close方法.html' ;
                            写法二、window.location='demo_window对象的close方法.html' ;

    1.reload方法: 刷新
                写法: window.location.reload() ;

    2.search   返回url?后面的查询部分

    3.protocol(http:),

    4.hostname(www.example.com)

    5.port(80)

    6.host(www.example.com:80)

    7.pathname(”/a/a.html”)

    8.hash(”#giantgizmo”,指跳转到相应的锚记)

    方法

    assign()   加载新的文档

    reload(boolean)   重新加载文档, 当参数是true,任何时候都会重新加载,false的时候,只有在文档改变的时候才会加载,否则直接读取内存当中的。

    replace() 用新的文档代替当前的文档  (没有历史记录)

    location.href="location2.html?1234"
    location.assign("location2.html");
    location.reload()
    location.replace("location2.html")
    
    
    window.location.reload()(刷新当前页面.)
    
    window.location.href=”url” (指定当前显示链接的位置)
    
    parent.location.reload()刷新父亲对象(用于框架)
    
    opener.location.reload()刷新父窗口对象(用于单开窗口)
    
    top.location.reload()刷新最顶端对象(用于多开窗口)
    
    
    	<script type="text/javascript">
    	<!--
    		/*1 href属性
    		  2. reload()方法:重新加载本页面
    		*/
    		function fun(){
    			//window.location.href = "b.html" ;
    			window.location = "b.html" ;
    		}
    
    		function fun1(){
    			window.location.reload();
    		}
    	//-->
    	</script>
     <body>
    		<input type="button" value="直接去b.html" onclick="fun()">
    		<input type="button" value="重新加载本页面" onclick="fun1()">
     </body>

    跳转到其他页面

    window.location.href=CONTEXT_PATH + "/subjectClassify/showSubjectClassifyNewsList?act=" + WebConst.EDIT+"&entityId="+subjectClassifyId;

    window.location.href在当前页面重新打开连接

       <div class="tit"><span onclick="ProdataShow.indexSkip('device')">资产信息</span></div>
    
            <div class="wrap_sub" style="height: 50%;">
             <div class="wraper">
                <div class="tit"><span onclick="ProdataShow.indexSkip('rushRepair')">应急抢修</span></div>
                <div class="con" id="demo1"></div>
             </div>
            </div>
    <script type="text/javascript" src="${rc.contextPath}/view/cusviews/js/index.js"></script>

    index.js

    $(function(){
    	ProdataShow.initOther();
    });
    
    /**
     * 首页
     */
    var ProdataShow = {
    		initOther:function(){
    			$(".amap-maptypecontrol").css("top","38px");
    			$(".amap-toolbar").css("top","86px");
    		},
    		
    		/**
    		 * 首页各个功能跳转
    		 * type:device-资产信息、rushRepair-应急抢修、pipeRun-管网运行、produceWork-生产作业
    		 * leakCheck-泄露检测
    		 */
    		indexSkip:function(type){
    			if($.isEmptyStr(type)){
    				layer.alert('地址不存在!', {icon: 0});
    				return;
    			}
    			var url = ""
    			switch(type)
    			{
    			case 'device':
    				url = CONTEXT_PATH + "/cusviews/dev/index";
    				break;
    			case 'rushRepair':
    				url = CONTEXT_PATH + "/cusviews/rush/index";
    				break;
    			case 'pipeRun':
    				url = CONTEXT_PATH + "/cusviews/pipe/index";
    				break;
    			case 'produceWork':
    				url = CONTEXT_PATH + "/cusviews/produce/index";
    				break;
    			case 'leakCheck':
    				url = CONTEXT_PATH + "/cusviews/leak/index";
    				break;
    			default:
    				url = CONTEXT_PATH + "/cusviews/index";
    			}
    			window.location.href = url;
    		}
    
    		
    }

    定时器倒数跳转其他页面

    <script>
    window.onload=function  () {
    setTimeout(function  () {
       location.href="location1.html";
    },3000)
    
    var num=document.getElementById("num")
    var nums=3
    setInterval(function  () {
        nums--
    	num.innerHTML=nums;
    },1000)
    }
    
    </script>
    <center>
    <div id="out">
       <div class="one">
       </div>
       <div class="two">
          3秒钟以后跳转
    	  <p id="num">
    	     3
    	  </p>
       </div>
    </div>
    </center>


     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    展开全文
  • Window类的设计与实现_艾孜尔江撰

    千次阅读 2021-06-14 11:06:12
    设计(头文件) #pragma once #include "LoadInAdvance.h" namespace RenderEngine { class Window { public: static Window&... int Initialize(int windowWidth, int windowHeight, LPCTSTR
  • Py之slidingwindow&sliding_window:slidingwindow、sliding_window的简介、安装、使用方法之详细攻略 目录 sliding_window的简介 sliding_window的安装 sliding_window的使用方法 slidingwindow、...
  • 文章目录前言AbstractShifted Windows AttentionShifted window partitioning in successive blocks 前言 论文链接:Swin Transformer: Hierarchical Vision Transformer using Shifted Windows 源码地址:...
  • UnityEditor研究学习之EditorWindow

    千次阅读 2019-03-22 22:26:41
    UnityEditor研究学习之EditorWindow 在unity使用过程中,其实我们都是在各个不同功能的Window下工作。 比如在Scene窗口中操作物体,在Inspector中操作物体属性,在Game视窗中观察游戏状态。 所以窗口是Unity...
  • 应用窗口添加在AppWindowToken的WindowList中,系统窗口添加在WindowToken的WindowList中,子窗口添加在父窗口的WindowState的WindowList中,AppWindowToken继承WindowToken,WindowToken继承WindowContainer,...
  • js中document与window的区别

    千次阅读 2019-04-05 13:24:43
     window.self //引用本窗户window=window.self  window.name //为窗口命名  window.defaultStatus //设定窗户状态栏信息  window.location //URL地址,配备布置这个属性可以打开新的页面 对象方法  ...
  • 一文搞定JS中window对象

    千次阅读 2020-06-25 14:16:41
    1.window对象简介 2.窗口操作 3.对话框 4.定时器 5.location对象 6.navagator对象 7.document对象 1.window对象简介 1.在JavaScript中,一个浏览器窗口就是一个window对象。 2.一个窗口就是一个window对象,...
  • 最新Clover,解决window10

    千次下载 热门讨论 2015-09-28 11:22:15
    解决clover在window10奔溃的问题 文章链接地址:http://www.cnblogs.com/draem0507/p/4843612.html
  • Android Window 机制探索

    万次阅读 多人点赞 2017-11-16 21:01:01
    Window的概念 setContentView installDecor generateLayout Window的类型 应用窗口 子窗口 系统窗口 Window的内部机制(Activity) Window的创建过程 Window的添加过程 Window的token Activity的attach...
  • 今天,在网上看到很多关于Mac电脑的WindowServer的疑问。 网友小A说:“WindowServer这个进程为什么内存占用这么大?” 网友小B说:“为啥WindowServer这个进程这么吃CPU?这个是啥啊?我没有装Windows” 网友小C说:...
  • Flink中window Trigger的理解

    千次阅读 2019-06-03 18:12:27
    初识Flink,对于window相关的几个概念不甚了解,先抛出几个关键问题! window是如何划分的? watermark生成方式?多久产生一次? window触发计算条件? 延迟数据如何处理? 一、window是如何划分的? ...
  • 一篇文章看明白 Activity 与 Window 与 View 之间的关系

    万次阅读 多人点赞 2018-03-01 10:24:31
    Android - Activity 与 Window 与 View 之间的关系 概述 我们知道 Activity 启动后就可以看到我们写的 Layout 布局界面,Activity 从 setContentView() 到显示中间做了什么呢?下面我们就来分析下这个过程。 如...
  • window width 与window innerWidth的区别

    千次阅读 2019-04-23 12:07:24
    window width 与window innerWidth的区别
  • Flink 原理与实现:Window 机制

    千次阅读 2018-08-02 15:46:54
    而窗口(window)就是从 Streaming 到 Batch 的一个桥梁。Flink 提供了非常完善的窗口机制,这是我认为的 Flink 最大的亮点之一(其他的亮点包括消息乱序处理,和 checkpoint 机制)。本文我们将介绍流式处理中的...
  • Window和WindowManager概述Window是一个抽象类,它的具体实现是PhoneWindow,创建一个Window通过WindowManager 就可以完成。WindowManager是外界访问Window的入口,它的具体实现在WindowManagerService中,...
  • NW.js之window篇(4)

    千次阅读 2018-09-20 14:54:17
    4.1 window api 概述 4.2 获取和创建窗口 4.3 window对象属性和方法 4.3.1 Window.window 4.3.2 Window.x/Window.y 4.3.3 Window.width/Window.height 4.3.4 Window.title 4.3.5 Window.menu 4.3.6 ...
  • 今天用ajax做个简单例子登录成功后跳转到展示数据页面,我想要登录成功后用layer提示一下再跳转页面,但是我用window.location.href会直接跳过我的layer提示,就想着让弹窗先出来几秒然后再跳转,现在已经解决,分享给...
  • Multi Window Android PPT

    热门讨论 2014-07-30 06:00:53
    介绍Multi Window Android的开发过程及步骤
  • 探索浏览器页面关闭window.close()的使用

    千次阅读 多人点赞 2020-08-19 10:36:51
    说起来window.close(),这也是个“不太让人省心”的角色。因为浏览器兼容性千差万别,还对他有诸多限制。 使用语法: window.close() 场景复现 昨天发现有人在csdn上传违禁文件,举报后来到了这个页面: 里面那个...
  • 关于TCP window full/TCP zero window

    千次阅读 2020-06-09 20:19:18
    关于TCP window full/TCP zero window TCP window full, 是指的发送端发送的数据已经达到的接受窗口的上限。 继而停止发送,等待新的接收窗口的通告 此时接收端返回的是TCP zero window,表示接收端窗口为0,从抓包...
  • JS中window.open()参数详解

    万次阅读 2019-05-16 14:59:57
    转自:... 1, 最基本的弹出窗口代码 window.open('page.html'); 2, 经过设置后的弹出窗口 window.open('page.html', 'newwindow', 'height=100, width=400, top=0, left=0, too...
  • memcached window 64位

    千次下载 热门讨论 2014-07-27 14:33:30
    memcached window 64位位,版本号1.4.4-14-g9c660c0,提供给需要的人下载
  • Flink Window机制详解

    万次阅读 2019-03-19 11:41:27
    而窗口(window)就是从 Streaming 到 Batch 的一个桥梁。Flink 提供了非常完善的窗口机制,这是我认为的 Flink 最大的亮点之一(其他的亮点包括消息乱序处理,和 checkpoint 机制)。本文我们将介绍流式处理中的...
  • window.localStorage的用法

    千次阅读 2019-05-27 10:15:01
    window.localStorage){ alert( "浏览器支持localstorage" ); return false ; } else { //主逻辑业务 } localStorage的写入,localStorage的写入有三种方法 if (! window .localStorage){ alert( ...
  • 有时候我们需要在桌面上显示一个类似悬浮窗的东西,这种效果就需要用 Window 来实现,Window 是一个抽象类,表示一个窗口,它的具体实现类是 PhoneWindow,实现位于 WindowManagerService 中。相信看到 ...
  • 高版本Chrome兼容window.showModalDialog办法 由于showmodaldialog 不属于W3C标准,在新版本浏览器中不再受支持,我们需要自定义一个showmodaldialog 来代替。 将要打开模态框的主页面代码: &amp;amp;amp;amp;...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,040,337
精华内容 816,134
关键字:

window