[虾扯蛋] android界面框架-Window

从纯sdk及framwork的角度看,android中界面框架相关的类型有:Window,WindowManager,View等。下面就以这几个类为出发点来概览下安卓开发的“界面架构”。

Window

该类接触不多,和它密切相关的View类就比较熟悉了。

Window和View的关系

View是可视界面上的一个矩形区域,它显示内容并接收各种交互事件。所有View形成一个ViewTree这样的结构,对应任何一个界面通过sdk自带的hierarchyviewer工具就可以看到所有View对象形成的视图树的形象的结构图,相信都不会陌生。

一般的,开发工作主要是利用系统及自定义控件组合完成各种界面,所以理解View的使用和原理更重要些。再进一步,以ViewTree为整体,再看它和window,系统服务之间的关系可以从整体上把握android中界面框架。

Window类的描述如下:

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.

Window表示“窗口”的概念,类似桌面OS中的窗口的概念,它是对用户界面的一个逻辑划分。可以参考下Windows编程中对Window类的描述是:“The point of interaction between a user and a standalone application is a window.”。每个窗口对应一个独立的交互(可以是完整屏幕大小的)界面。 可以认为窗口是系统区分不同界面(不同app,或者同一app的不同Activity等)的一个单位。窗口之间可以包含(容器和子窗口),可以重叠(窗口具有z轴深度)。 窗口本身没有显示内容的能力,它包含一个顶级的View对象来持有一棵ViewTree。 一句话概况:窗口是一个独立的可交互界面,不同窗口叠加显示,窗口包含View来显示内容。

android中的UI就是View组成的ViewTree来表达的,root view或者说顶部(top level)的View对象作为对整个ViewTree执行消息传递,测量,布局和绘制等遍历操作的全局入口,持有此root view就相当于持有对应的组成界面内容的ViewTree。

有一点就是,Window是一个框架层的概念,整个android中的“各种界面”是不同类型的Window对象。但是在应用层,我们创建不同的界面就是提供不同的“内容”View对象,然后指定其Window类型,而界面的创建,更新和关闭是通过操纵Window所包含的要显示的顶级View,而不会直接去操控Window对象。

创建Window

在一个新窗口显示一个View最简单的过程如下:

private void openNewWindow() {
    Button button = new Button(this);    
    button.setText("Button On New Window");
    WindowManager.LayoutParams params
            = new WindowManager.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
            ViewGroup.LayoutParams.WRAP_CONTENT);
    params.gravity = Gravity.LEFT | Gravity.TOP;
    params.x = 220;
    params.y = 320;
    params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
    params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
    WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
    windowManager.addView(button, params);
}

创建Window是通过WindowManager实现的,addView(View view, WindowManager.LayoutParams params)方法接收要展示的root view和窗口布局相关的参数。 执行上面的代码会创建一个新的窗口,并在屏幕坐标(220,320)的位置放置了一个Button。应用层要做的就是准备好Button对象,然后设置好相关布局参数,而Window对象的创建本身最终是通过系统服务完成的。类似Activity那样,Window对象的创建不是new出来的。

窗口的创建,更新和关闭操作都是WindowManager的工作。实际开发中很少和Window直接打交到。比如Activity对应一个窗口,但是对它的界面的各种操作好像都和Window无关,因为Window的确够“底层”了。

窗口类型

前面说过,安卓中的界面划分为一个个窗口,系统运行中各个不同的窗口可以叠加显示。和叠加相关的属性就是Z-ordered,它是正整数。通过Z-ordered值,系统来决定这些Window的覆盖顺序。但实际上并不是通过指定Z-ordered值来直接控制窗口的层叠,而是,系统提供了一组常量,被表示为窗口类型,不同窗口类型的常量对应一个Z-ordered的值范围,然后其它地方通过为Window指定type来间接控制其Z-ordered。Window类有一个setType的方法正是做这件事情的。不过在创建Window时就需要为其指定type,而我们不直接接触Window对象,WindowWindowManager.LayoutParam有一个type的变量,在通过WindowWindowManager.addView方法创建窗口时,窗口布局参数中指定需要的type——窗口类型——这是必须的,而且Window type can not be changed after the window is added。

窗口类型有:

  • 系统窗口:如状态栏,Toast那样的,常量如TYPE_SYSTEM_xx的。
  • 应用窗口:就是Activtiy。
  • 子窗口:Dialog这样的,需要依附(attach)到其它窗口(作为子窗口的container,如Activity)。

WindowManager.LayoutParams有个flags的属性,用来控制窗口的可见性,透明,是否可以获得焦点等显示和交互的有关状态。

Window相关属性和方法

接下来直面Window类。它是一个抽象类,目前只有PhoneWindow是其实现类。可以通过下面截取的类型定义的代码片段对Window有个感官认识:

public abstract class Window {
  // 一系列的FEATURE_xx常量,还记得在Activity中requestWindowFeature方法吗?
  public static final int FEATURE_xxx

  /**
   * API from a Window back to its caller.
   * This allows the client to intercept key
   * dispatching, panels and menus, etc.
   */
  private Callback mCallback;
  // The interface that apps use to talk to the window manager.
  private WindowManager mWindowManager;

  private Window mContainer;
  private Window mActiveChild;
  // The current window attributes.
  private final WindowManager.LayoutParams mWindowAttributes =
      new WindowManager.LayoutParams();

  /**
   * The ID that the main layout in the XML layout file should have.
   */
  public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

  /**
   * API from a Window back to its caller.  This allows the client to
   * intercept key dispatching, panels and menus, etc.
   */
  public interface Callback {
    public boolean dispatchTouchEvent(MotionEvent event);
    public void onAttachedToWindow();
    // ...
  }
  public abstract LayoutInflater getLayoutInflater();
  public abstract void setContentView(View view, ViewGroup.LayoutParams params);

  /**
   * Retrieve the top-level window decor view (containing the standard
   * window frame/decorations and the client's content inside of that), which
   * can be added as a window to the window manager.
   *
   * <p><em>Note that calling this function for the first time "locks in"
   * various window characteristics as described in
   * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}.</em></p>
   *
   * @return Returns the top-level window decor view.
   */
  public abstract View getDecorView();

  public abstract void setTitle(CharSequence title);
  public void setIcon(@DrawableRes int resId) { }
}

有三个值得关注的:

  • Window.Callback 该接口使得依靠(可以确定的是所有界面都是通过Window展示的)Window来显示内容的其它对象——主要就是Activity——接收Window发送的交互等事件和消息回调。例如Activity.onAttachedToWindow()这样的回调。
  • getDecorView DecorView就是和Window关联的用来显示内容的ViewTree的root。所有地方都使用Window来显示内容,不同的Window使用者会需要不同的DecorView,比如Activity就需要DecorView本身具备ActionBar这类的“界面装饰部分”。而Dialog明显没有。
  • setContentView Window显示的自定义内容。Activity中的setContentView正是调用关联的Window对象的此方法。将界面内容附加到DecorView作为其子树。

PhoneWindow

PhoneWindow作为目前Window的唯一子类,它的大致定义如下:

public class PhoneWindow extends Window {
  // This is the top-level view of the window, containing the window decor.
  private DecorView mDecor;

  // This is the view in which the window contents are placed. It is either
  // mDecor itself, or a child of mDecor where the contents go.
  private ViewGroup mContentParent;

  private TextView mTitleView;
  private ImageView mLeftIconView;
  private ActionBarView mActionBar;

  @Override
  public void injectInputEvent(InputEvent event) {
      getViewRootImpl().dispatchInputEvent(event);
  }

  private ViewRootImpl getViewRootImpl() {
      if (mDecor != null) {
          ViewRootImpl viewRootImpl = mDecor.getViewRootImpl();
          if (viewRootImpl != null) {
              return viewRootImpl;
          }
      }
      throw new IllegalStateException("view not added");
  }

  private final class DecorView extends FrameLayout {
    // ...
  }
}
  • DecorView PhoneWindow的内部类DecorView作为Window关联的ViewTree的root view。它用来放置ActionBarView,Title和Icon等一些在Activity中经常访问得到的界面元素。而应用自身提供的contentView是作为DecorView的childView存在的。
  • ViewRootImpl 从View组成ViewTree的角度看,任意的ViewGroup子类都可以做为root,但Window操作ViewTree时需要对root view做特殊对待,ViewRootImpl正是用来表达root view,是Window和ViewTree之间的交互接口:
/**
 * The top of a view hierarchy, implementing the needed protocol between View
 * and the WindowManager.  This is for the most part an internal implementation
 * detail of {@link WindowManagerGlobal}.
 *
 * {@hide}
 */
@SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"})
public final class ViewRootImpl implements ViewParent {
  // ...
}

在View中方法ViewRootImpl getViewRootImpl()可以获得一个View关联的ViewRootImpl对象。在ViewTree关联到Window后,每个View会获得一个AttachInfo对象,里面保存了rootView和ViewRootImpl这样的对象来访问根视图。

至于ViewRootImpl到底有哪些作用,后面分析WindowManager的操作时会接触到。目前为止,Window和View的大致概念和它们之间的关系交代完毕。下面需要从WindowManager来继续探索界面框架的工作原理。

WindowManager

概念window manager表示用来管理操作Window的角色。稍后会知道真实的动作完全是通过IPC调用系统服务WindowManagerService完成的,我们(调用api的客户端代码)接触到的是一个代理对象。而WindowManager正是IPC的接口描述。

接口WindowManager继承了ViewManager,它的定义是:

/** Interface to let you add and remove child views to an Activity. To get an instance
  * of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
  */
public interface ViewManager {
  // Assign the passed LayoutParams to the passed View and add the view to the window.
  public void addView(View view, ViewGroup.LayoutParams params);
  public void updateViewLayout(View view, ViewGroup.LayoutParams params);
  public void removeView(View view);
}

上面的三个方法是面向Window的,分别用来添加,更新、和移除View。

Window是界面框架的基本单位,每个可视的独立交互的界面是一个Window,而界面可视元素是View组成的,View必须依附到Window来被最终绘制和显示。SDK提供的界面又分为不同的Window类型,Dialog,StatusBar,Toast,Activity等,它们都是拥有View并依靠Window来显示内容的“界面类”。

ViewManager的这三个方法几乎就是WindowManager的所有职责,也是我们可以“间接”和Window打交道的方式。addView时会创建新的Window并将传递的View作为其呈现的内容。removeView时也就销毁了Window。

接口WindowManager继承ViewManager:

public interface WindowManager extends ViewManager {
  public static class LayoutParams extends ViewGroup.LayoutParams
            implements Parcelable {
      // View的left,top
      public int x, y;
      /**
       * The general type of window.  There are three main classes of
       * window types:Application windows、System windows、Sub-windows。
       */
      public int type;

      // Various behavioral options/flags.
      public int flags;

      // ...
  }
}

WindowManager.LayoutParams就是和Window中View相关的布局参数。

WindowManagerService

就像Activity的创建一样,Window的创建不是像View那样new出来的。而是通过IPC调用系统服务WindowManagerService完成的。如果接触过RemoteViews的话很容易明白类似的设计。 WindowManager的实现类是WindowManagerImpl:

/**
 * Provides low-level communication with the system window manager for
 * operations that are bound to a particular context, display or parent window.
 * Instances of this object are sensitive to the compatibility info associated
 * with the running application.
 *
 * This object implements the {@link ViewManager} interface,
 * allowing you to add any View subclass as a top-level window on the screen.
 * Additional window manager specific layout parameters are defined for
 * control over how windows are displayed.  It also implements the {@link WindowManager}
 * interface, allowing you to control the displays attached to the device.
 *
 * <p>Applications will not normally use WindowManager directly, instead relying
 * on the higher-level facilities in {@link android.app.Activity} and
 * {@link android.app.Dialog}.
 *
 * <p>Even for low-level window manager access, it is almost never correct to use
 * this class.  For example, {@link android.app.Activity#getWindowManager}
 * provides a window manager for adding windows that are associated with that
 * activity -- the window manager will not normally allow you to add arbitrary
 * windows that are not associated with an activity.
 *
 * @see WindowManager
 * @see WindowManagerGlobal
 * @hide
 */
public final class WindowManagerImpl implements WindowManager {
  private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

  @Override
  public void addView(View view, ViewGroup.LayoutParams params) {
      mGlobal.addView(view, params, mDisplay, mParentWindow);
  }

  @Override
  public void removeView(View view) {
      mGlobal.removeView(view, false);
  }

  // ...
}

可以看到,WindowManagerImpl将实际操作委托给WindowManagerGlobal mGlobal完成。WindowManagerGlobal是单例的。

public final class WindowManagerGlobal {
  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>();

  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);
         }

         ViewRootImpl root;
         View panelParentView = null;

         synchronized (mLock) {
             // Start watching for system property changes.
             if (mSystemPropertyUpdater == null) {
                 mSystemPropertyUpdater = new Runnable() {
                     @Override public void run() {
                         synchronized (mLock) {
                             for (int i = mRoots.size() - 1; i >= 0; --i) {
                                 mRoots.get(i).loadSystemProperties();
                             }
                         }
                     }
                 };
                 SystemProperties.addChangeCallback(mSystemPropertyUpdater);
             }

             int index = findViewLocked(view, false);
             if (index >= 0) {
                 if (mDyingViews.contains(view)) {
                     // Don't wait for MSG_DIE to make it's way through root's queue.
                     mRoots.get(index).doDie();
                 } else {
                     throw new IllegalStateException("View " + view
                             + " has already been added to the window manager.");
                 }
                 // The previous removeView() had not completed executing. Now it has.
             }

             // If this is a panel window, then find the window it is being
             // attached to for future reference.
             if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                     wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                 final int count = mViews.size();
                 for (int i = 0; i < count; i++) {
                     if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                         panelParentView = mViews.get(i);
                     }
                 }
             }

             root = new ViewRootImpl(view.getContext(), display);

             view.setLayoutParams(wparams);

             mViews.add(view);
             mRoots.add(root);
             mParams.add(wparams);
         }

         // do this last because it fires off messages to start doing things
         try {
             root.setView(view, wparams, panelParentView);
         } catch (RuntimeException e) {
             // BadTokenException or InvalidDisplayException, clean up.
             synchronized (mLock) {
                 final int index = findViewLocked(view, false);
                 if (index >= 0) {
                     removeViewLocked(index, true);
                 }
             }
             throw e;
         }
     }


  // ...
}

mViews、mRoots、mParams几个参数保存了所有管理的Window的相关状态。WindowManager是管理所有Window的。 上面ViewRootImpl就是在addView时被创建的。 最后ViewRootImpl.setView方法。

// ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
  // ...
  requestLayout();
  // ...
  res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mInputChannel);
  // ...
}

@Override
public void requestLayout() {
    checkThread();
    mLayoutRequested = true;
    scheduleTraversals();
}

方法scheduleTraversals()是对ViewTree的遍历入口。最终的WindowSession.addToDisplay完成IPC调用来创建Window用来显示界面。

此间涉及到IWindowSession,IWindow.Stub 等都是典型的aidl相关的技术了。 分析到这里,理解了Window、WindowManager和View之间的工作关系后就在全局上把握了界面框架。 简单地说,所有需要“界面”的地方,都需要通过一个Window。而具体过程,就是使用WindowManager来构造,提供需要显示地View,并设置好布局参数。 Dialog、PopupWindow,Activity这些经常用来搭建界面的类型,都是封装了Window。

比如,Activity的以下相关方法,都是和Window密切相关的:

void onAttachedToWindow();
void setContentView(int layoutResID);
Window getWindow();
WindowManager getWindowManager();
void onDetachedFromWindow();

无论如何,了解了Window、WindowManager等概念,对这些high-level的UI类型的工作原理就更加深入了。

总结

  • Window是独立交互的界面单位。android中所有界面都是不同类型的Window。
  • View组成ViewTree来表达显示内容。
  • ViewTree的每个View都指向rootView和ViewRootImpl。
  • ViewTree依附Window,Window持有ViewTree及ViewRootImpl。Window传递事件给ViewTree,对ViewTree 执行遍历操作,完成测量、布局、绘制显示。
  • WindowManager负责调用系统服务完成Window的管理操作。
  • Window是系统服务管理的界面对象,它是系统分发界面交互事件、完成界面显示相关操作的接口。
  • Window和View是界面框架的不同分级,系统级和UI元素,使得界面框架的设计更为清晰。
  • Window是界面框架较底层的实现,更高级别上,都是使用Activity、Dialog等这些封装了基于Window的高级UI类型来完成界面构建的。

好像很简单的几个概念吧,拖拖拉拉说了一大堆~~

参考资料

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏知识分享

8-51单片机ESP8266学习-AT指令(测试TCP服务器--51单片机程序配置8266,做自己的手机TCP客户端发信息给单片机控制小灯的亮灭)

http://www.cnblogs.com/yangfengwu/p/8776712.html 先把源码和资料链接放到这里 链接:https://pan.ba...

63820
来自专栏GIS讲堂

Arcgis for Androd API开发系列教程(一)——地图显示与GPS定位

序:最近呢,工作鸭梨不是怎么大,对于自己爱折腾的想法又冒出了水面,开始自己的android开发的学习之旅。但是呢,本人是做GIS的,所以呢,就打算从这方面入手看...

18050
来自专栏Android先生

Android仿微信文章悬浮窗效果

前些日子跟朋友聊天,朋友Z果粉,前些天更新了微信,说微信出了个好方便的功能啊,我问是啥功能啊,看看我大Android有没有,他说现在阅读公众号文章如果有人给你发...

32730
来自专栏Phoenix的Android之旅

ListView的相关总结

回顾一下, ListView的使用其实非常简单, 只需要提供一个 UI,提供一个装载数据的 Adapter, 在 Adapter的 getView 方法里实现每...

10620
来自专栏非著名程序员

Android实现两个ScrollView互相联动,同步滚动的效果

最近在做一个项目,用到了两个ScrollView互相联动的效果,简单来说联动效果意思就是滑动其中的一个ScrollView另一个ScrollView也一同跟着滑...

31650
来自专栏刘望舒

RxBinding使用和源码解析

作者 | juexingzhe 地址 | https://www.jianshu.com/u/ea71bb3770b4 声明 | 本文是 juexingzhe...

466100
来自专栏Android先生

Context都没弄明白,还怎么做Android开发?

作为Android开发者,不知道你有没有思考过这个问题,Activity可以new吗?Android的应用程序开发采用JAVA语言,Activity本质上也是一...

9820
来自专栏Android干货园

教你轻松几步实现底部导航栏

版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/lyhhj/article/details/45...

12210
来自专栏向治洪

开源项目Universal Image Loader for Android

In the previous article, we’ve initialized the ImageLoader with configuration; ...

18850
来自专栏Vamei实验室

安卓第五夜 维纳斯的诞生

之前各讲中,分别讲解了安卓的开发环境、架构和基本概念。从这一讲开始,我将制作一个简单的应用,并通过逐步升级它的功能,连带出安卓开发的多个情境。 《维纳斯的诞生》...

21070

扫码关注云+社区

领取腾讯云代金券