­

Window 機制源碼分析

  • 2019 年 12 月 26 日
  • 筆記

Window 機制深入源碼分析

Window 是一個抽象的基類,表示一個窗口,包含一個View treelayout參數。

View treeroot View可以通過getDecorView得到。還可以設置Window的Content View。其實現類是PhoneWindowActivity,Dialog,Toast,都包含一個Window,該Window在Activity的attach()函數中mWindow = new PhoneWindow(this);創建。 每一個Window對應一個View與ViewRootImpl,它與view是通過ViewRootImpl來建立關係的,它是以view的形式存在。在實際中無法直接訪問,需要通過WindowManager這個應用程序窗口管理器進行訪問。而WindowManager是一個接口,繼承自ViewManager,真正實現的方法是在WindowManagerImpl中。

Window 添加過程

WindowManagerImpl內部調用了WindowManagerGlobal的addView方法,採用了懶漢式線程安全的方法進行初始化WindowManagerGlobal。addView,updateView,removeView全部都是通過WindowManagerGlobal進行操作實現的。 WindowManagerGlobal的addView 方法

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 {              // If there's no parent, then hardware acceleration for this view is              // set from the application's hardware acceleration setting.              final Context context = view.getContext();              if (context != null                      && (context.getApplicationInfo().flags                              & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {                  wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;              }          }             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.                  if (index >= 0) {                      removeViewLocked(index, true);                  }                  throw e;              }          }      }
  • 首先進行參數的檢查,參數不對進行拋出異常 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");     }
  • 如果當前父view不為空進行一些布局參數設置,否則父view為空時,必須在應用或Activity中設置硬件加速。 final Context context = view.getContext();         if (context != null                 && (context.getApplicationInfo().flags                         & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {             wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;         }
  • 創建ViewRootImpl,並將view添加到集合中 root = new ViewRootImpl(view.getContext(), display);           view.setLayoutParams(wparams);           mViews.add(view);         mRoots.add(root);         mParams.add(wparams); 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>(); private final ArraySet<View> mDyingViews = new ArraySet<View>(); mView集合中存儲着Window所對應的view,而mRoots存儲着Window所對應的ViewRootImplmParams存儲着Window所對應的布局參數,mDyingViews存儲着Window所對應的正在被刪除的view。
  • 調用ViewRootImpl來設置view,並完成view的添加過程 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {     synchronized (this) {         if (mView == null) {             mView = view;               mAttachInfo.mDisplayState = mDisplay.getState();             mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);               mViewLayoutDirectionInitial = mView.getRawLayoutDirection();             mFallbackEventHandler.setView(view);             mWindowAttributes.copyFrom(attrs);             if (mWindowAttributes.packageName == null) {                 mWindowAttributes.packageName = mBasePackageName;             }        requestLayout();                 switch (res) {                     case WindowManagerGlobal.ADD_BAD_APP_TOKEN:                     case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:                         throw new WindowManager.BadTokenException(                                 "Unable to add window — token " + attrs.token                                 + " is not valid; is your activity running?");                     case WindowManagerGlobal.ADD_NOT_APP_TOKEN:                         throw new WindowManager.BadTokenException(                                 "Unable to add window — token " + attrs.token                                 + " is not for an application");                     case WindowManagerGlobal.ADD_APP_EXITING:                         throw new WindowManager.BadTokenException(                                 "Unable to add window — app for token " + attrs.token                                 + " is exiting");                     case WindowManagerGlobal.ADD_DUPLICATE_ADD:                         throw new WindowManager.BadTokenException(                                 "Unable to add window — window " + mWindow                                 + " has already been added");       …… } 內部調用requestLayout完成整個View的繪製,同時進行當前Window檢查,可不可以添加當前view @Override public void requestLayout() {     if (!mHandlingLayoutInLayoutRequest) {         checkThread();         mLayoutRequested = true;         scheduleTraversals();     } } scheduleTraversals內部最終會調用performTraversals來完成整個view的繪製
  • 最後通過WindowSession來完成Window的添加過程 IWindowSession mWindowSession是一個binder接口對象,添加一個也就完成一個的Binder進程通信 try {                 mOrigWindowType = mWindowAttributes.type;                 mAttachInfo.mRecomputeGlobalAttributes = true;                 collectViewAttributes();                 res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,                         getHostVisibility(), mDisplay.getDisplayId(),                         mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,                         mAttachInfo.mOutsets, mInputChannel);             } catch (RemoteException e) {                 mAdded = false;                 mView = null;                 mAttachInfo.mRootView = null;                 mInputChannel = null;                 mFallbackEventHandler.setView(null);                 unscheduleTraversals();                 setAccessibilityFocus(null, null);                 throw new RuntimeException("Adding window failed", e);             } finally {                 if (restore) {                     attrs.restore();                 }             } addToDisplay方法,內部調用WindowManagerServiceaddWindow方法完成Window的添加過程,它會為每一個應用保留一個單獨的Sessions對象。Window的添加過程最後統統交給了WindowManagerServiceaddWindow方法來完成。 @Override public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,         int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,         Rect outOutsets, InputChannel outInputChannel) {     return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,             outContentInsets, outStableInsets, outOutsets, outInputChannel); }

Window更新過程

Window更新過程照樣調用的是WindowManagerGlobalupdateViewLayout方法。

public void updateViewLayout(View view, ViewGroup.LayoutParams params) {          if (view == null) {              throw new IllegalArgumentException("view must not be null");          }          if (!(params instanceof WindowManager.LayoutParams)) {              throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");          }             final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;             view.setLayoutParams(wparams);             synchronized (mLock) {              int index = findViewLocked(view, true);              ViewRootImpl root = mRoots.get(index);              mParams.remove(index);              mParams.add(index, wparams);              root.setLayoutParams(wparams, false);          }      }

首先獲取需要更新的當前view,移除當前view位置的參數,並將新的布局參數添加進去,通過新的LayoutParams來替換舊的layoutParams

Window 刪除過程

Window刪除過程調用的是WindowManagerGlobalremoveView方法。

 public void removeView(View view, boolean immediate) {          if (view == null) {              throw new IllegalArgumentException("view must not be null");          }             synchronized (mLock) {              int index = findViewLocked(view, true);              View curView = mRoots.get(index).getView();              removeViewLocked(index, immediate);              if (curView == view) {                  return;              }                 throw new IllegalStateException("Calling with view " + view                      + " but the ViewAncestor is attached to " + curView);          }      }

首先查找需要刪除view的位置,再調用removeViewLocked進行刪除,刪除分為兩種,一種是異步刪除,一種是同步刪除。mGlobal.removeView(view, false);,false為異步刪除,true為同步刪除mGlobal.removeView(view, true);異步刪除主要是通過發送一個handler消息,然後調用die方法進行刪除,這個刪除不是立刻就刪除了,而是添加到隊列中,mDyingViews表示待刪除的view列表。

/**       * @param immediate True, do now if not in traversal. False, put on queue and do later.       * @return True, request has been queued. False, request has been completed.       */      boolean die(boolean immediate) {          // Make sure we do execute immediately if we are in the middle of a traversal or the damage          // done by dispatchDetachedFromWindow will cause havoc on return.          if (immediate && !mIsInTraversal) {              doDie();              return false;          }             if (!mIsDrawing) {              destroyHardwareRenderer();          } else {              Log.e(mTag, "Attempting to destroy the window while drawing!n" +                      "  window=" + this + ", title=" + mWindowAttributes.getTitle());          }          mHandler.sendEmptyMessage(MSG_DIE);          return true;      }

die方法內部做了一個判斷,是否立即刪除,立即刪除(同步刪除)就不用發消息,直接調用doDie方法,而異步刪除需要發送一個消息。

void doDie() {          checkThread();          if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);          synchronized (this) {              if (mRemoved) {                  return;              }              mRemoved = true;              if (mAdded) {                  dispatchDetachedFromWindow();              }                 if (mAdded && !mFirst) {                  destroyHardwareRenderer();                     if (mView != null) {                      int viewVisibility = mView.getVisibility();                      boolean viewVisibilityChanged = mViewVisibility != viewVisibility;                      if (mWindowAttributesChanged || viewVisibilityChanged) {                          // If layout params have been changed, first give them                          // to the window manager to make sure it has the correct                          // animation info.                          try {                              if ((relayoutWindow(mWindowAttributes, viewVisibility, false)                                      & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {                                  mWindowSession.finishDrawing(mWindow);                              }                          } catch (RemoteException e) {                          }                      }                         mSurface.release();                  }              }                 mAdded = false;          }          WindowManagerGlobal.getInstance().doRemoveView(this);      }

真正的刪除操作發生在dispatchDetachedFromWindow方法中

 void dispatchDetachedFromWindow() {          if (mView != null && mView.mAttachInfo != null) {              mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);              mView.dispatchDetachedFromWindow();          }             mAccessibilityInteractionConnectionManager.ensureNoConnection();          mAccessibilityManager.removeAccessibilityStateChangeListener(                  mAccessibilityInteractionConnectionManager);          mAccessibilityManager.removeHighTextContrastStateChangeListener(                  mHighContrastTextManager);          removeSendWindowContentChangedCallback();             destroyHardwareRenderer();             setAccessibilityFocus(null, null);             mView.assignParent(null);          mView = null;          mAttachInfo.mRootView = null;             mSurface.release();             if (mInputQueueCallback != null && mInputQueue != null) {              mInputQueueCallback.onInputQueueDestroyed(mInputQueue);              mInputQueue.dispose();              mInputQueueCallback = null;              mInputQueue = null;          }          if (mInputEventReceiver != null) {              mInputEventReceiver.dispose();              mInputEventReceiver = null;          }          try {              mWindowSession.remove(mWindow);          } catch (RemoteException e) {          }             // Dispose the input channel after removing the window so the Window Manager          // doesn't interpret the input channel being closed as an abnormal termination.          if (mInputChannel != null) {              mInputChannel.dispose();              mInputChannel = null;          }             mDisplayManager.unregisterDisplayListener(mDisplayListener);             unscheduleTraversals();      }
  • 首先進行垃圾回收操作,清除數據與移除消息和監聽;
  • 通過mWindowSession.remove(mWindow);,它是一次IPC進程操作,與addView一樣,最後會調用WindowManagerremoveWindow方法;
  • ViewdispatchDetachedFromWindow方法,內部會調用onDetachedFromWindow方法,方法中可以進行資源回收,比如停止動畫,與線程操作;
  • 最後調用WindowManagerGlobaldoRemoveView將之前的mRoots,mParams,mDyingView中的Window關聯的ViewRootImpl對象移除 void doRemoveView(ViewRootImpl root) {     synchronized (mLock) {         final int index = mRoots.indexOf(root);         if (index >= 0) {             mRoots.remove(index);             mParams.remove(index);             final View view = mViews.remove(index);             mDyingViews.remove(view);         }     }     if (ThreadedRenderer.sTrimForeground && ThreadedRenderer.isAvailable()) {         doTrimForeground();     } }