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