close
文章出處

從純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類型來完成界面構建的。

好像很簡單的幾個概念吧,拖拖拉拉說了一大堆~~

參考資料

(date:2016/11/3,本文使用Atom編寫)


不含病毒。www.avast.com
arrow
arrow
    全站熱搜

    AutoPoster 發表在 痞客邦 留言(0) 人氣()