window表示一個窗口的概念,如果我們需要在桌面顯示一個類似懸浮窗的東西,就需要用到window來實現。window是一個抽象類,它的具體實現是phoneWidow。創建一個widow只需要通過WindowManager即可,WindowManager是外界訪問window的入口,window的具體實現位於WindowManagerService中,WindowManager和WindowManagerService的交互是一個IPC的過程。
1. Window 和 WindowManager
下面代碼演示了通過WindowManager添加Window的過程
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button mFloatingButton = new Button(this);
mFloatingButton.setText("button");
WindowManager.LayoutParams mlayoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,0,0, PixelFormat.TRANSPARENT);
//設置flag
mlayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
//設置window類型
mlayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
mlayoutParams.gravity = Gravity.CENTER;
mlayoutParams.x = 100;
mlayoutParams.y = 100;
WindowManager windowManager = getWindowManager();
windowManager.addView(mFloatingButton, mlayoutParams);
}
}
WindowManager.LayoutParams中的flag和type參數比較重要
flag參數表示window的屬性;type參數表示window的類型
window的類型:
window有三種類型:應用window,子window,系統window。應用window對應着一個activity,子window不能單獨存在,它需要附屬在特定的父window中,比如常見的一些dialog就是一個字window。系統window需要聲明權限纔可以創建。
window分層的:每個window都有對應的z-ordered,層級大的會覆蓋在層級小的window上面。三類window中應用window層級範圍1~99,子window層級範圍1000~1999,系統window是2000~2999,這些層級範圍對應着WindowManager.LayoutParams中的type參數。
**WindowManager繼承自ViewManager,他常用的三個方法定義在ViewManager中:
public interface ViewManager{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
WindowManager是一個接口,它的實現類是WindowManagerImpl
public interface WindowManager extends ViewManager {
......
}
WindowManagerImpl實現了addView、updateViewLayout、removeView三個方法,下面是截取的WindowManagerImpl 部分源碼
public final class WindowManagerImpl implements WindowManager {
......
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
83 public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
84 applyDefaultToken(params);
85 mGlobal.addView(view, params, mDisplay, mParentWindow);
86 }
87
88 @Override
89 public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
90 applyDefaultToken(params);
91 mGlobal.updateViewLayout(view, params);
92 }
//異步移除 常用此方法刪除view
@Override
110 public void removeView(View view) {
111 mGlobal.removeView(view, false);
112 }
113 //同步移除
114 @Override
115 public void removeViewImmediate(View view) {
116 mGlobal.removeView(view, true);
117 }
}
可以看到,WindowManagerImpl 也並沒有直接實現window的三個操作,而是交給了mGlobal,從源碼中我們可以發現private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); 因此mGlobal就是WindowManagerGlobal 的實例,WindowManagerImpl 將window的操作委託給WindowManagerGlobal 來實現。
2.window的內部機制
2.1window的添加
window的添加通過WindowManager的addview方法來實現,根據上面分析我們知道,最後的實現是由WindowManagerGlobal 中的addview方法來完成:
public void addView(View view, ViewGroup.LayoutParams params,
232 Display display, Window parentWindow) {
233 if (view == null) {
234 throw new IllegalArgumentException("view must not be null");
235 }
236 if (display == null) {
237 throw new IllegalArgumentException("display must not be null");
238 }
239 if (!(params instanceof WindowManager.LayoutParams)) {
240 throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
241 }
242
243 final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
244 if (parentWindow != null) {
245 parentWindow.adjustLayoutParamsForSubWindow(wparams);
246 } else {
247 // If there's no parent, then hardware acceleration for this view is
248 // set from the application's hardware acceleration setting.
249 final Context context = view.getContext();
250 if (context != null
251 && (context.getApplicationInfo().flags
252 & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
253 wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
254 }
255 }
256
257 ViewRootImpl root;
258 View panelParentView = null;
259
260 synchronized (mLock) {
261 // Start watching for system property changes.
262 if (mSystemPropertyUpdater == null) {
263 mSystemPropertyUpdater = new Runnable() {
264 @Override public void run() {
265 synchronized (mLock) {
266 for (int i = mRoots.size() - 1; i >= 0; --i) {
267 mRoots.get(i).loadSystemProperties();
268 }
269 }
270 }
271 };
272 SystemProperties.addChangeCallback(mSystemPropertyUpdater);
273 }
274
275 int index = findViewLocked(view, false);
276 if (index >= 0) {
277 if (mDyingViews.contains(view)) {
278 // Don't wait for MSG_DIE to make it's way through root's queue.
279 mRoots.get(index).doDie();
280 } else {
281 throw new IllegalStateException("View " + view
282 + " has already been added to the window manager.");
283 }
284 // The previous removeView() had not completed executing. Now it has.
285 }
286
287 // If this is a panel window, then find the window it is being
288 // attached to for future reference.
289 if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
290 wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
291 final int count = mViews.size();
292 for (int i = 0; i < count; i++) {
293 if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
294 panelParentView = mViews.get(i);
295 }
296 }
297 }
298
299 root = new ViewRootImpl(view.getContext(), display);
300
301 view.setLayoutParams(wparams);
302
303 mViews.add(view);
304 mRoots.add(root);
305 mParams.add(wparams);
306 }
307
308 // do this last because it fires off messages to start doing things
309 try {
310 root.setView(view, wparams, panelParentView);
311 } catch (RuntimeException e) {
312 // BadTokenException or InvalidDisplayException, clean up.
313 synchronized (mLock) {
314 final int index = findViewLocked(view, false);
315 if (index >= 0) {
316 removeViewLocked(index, true);
317 }
318 }
319 throw e;
320 }
321 }
主要分爲以下幾步:
- 1.檢查參數是否合法,如果是子window還需要調整一些佈局參數
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);
}
- 2.創建ViewRootImpl並將view添加到list中
在WindowManagerGlobal 內部有幾個list比較重要:
//存儲所有window對應的view
private final ArrayList<View> mViews = new ArrayList<View>();
//存儲所有window對應的ViewRootImpl
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
//存儲所有window對應的佈局參數
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
//存儲正在被刪除的view對象,(已經調用了removeview方法但是刪除操作還未完成)
private final ArraySet<View> mDyingViews = new ArraySet<View>();
在addview中通過下面代碼中widow的一系列對象添加到list中
root = new ViewRootImpl(view.getContext(),display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
- 3.通過ViewRootImpl來更新界面並完成widow的添加
// do this last because it fires off messages to start doing things
309 try {
//通過此方法更新界面
310 root.setView(view, wparams, panelParentView);
311 } catch (RuntimeException e) {
312 // BadTokenException or InvalidDisplayException, clean up.
313 synchronized (mLock) {
314 final int index = findViewLocked(view, false);
315 if (index >= 0) {
316 removeViewLocked(index, true);
317 }
318 }
319 throw e;
320 }
可以看到調用ViewRootImpl的setview方法來完成這個步驟,在setview方法內部會通過requestLayout來完成異步刷新請求。截取部分代碼如下
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
448 synchronized (this) {
449 if (mView == null) {
450 mView = view;
451
452 mAttachInfo.mDisplayState = mDisplay.getState();
453 mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
454
455 mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
456 mFallbackEventHandler.setView(view);
457 mWindowAttributes.copyFrom(attrs);
458 if (mWindowAttributes.packageName == null) {
459 mWindowAttributes.packageName = mBasePackageName;
460 }
461 attrs = mWindowAttributes;
462 // Keep track of the actual window flags supplied by the client.
463 mClientWindowLayoutFlags = attrs.flags;
464
465 setAccessibilityFocus(null, null);
466
467 if (view instanceof RootViewSurfaceTaker) {
468 mSurfaceHolderCallback =
469 ((RootViewSurfaceTaker)view).willYouTakeTheSurface();
470 if (mSurfaceHolderCallback != null) {
471 mSurfaceHolder = new TakenSurfaceHolder();
472 mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
473 }
474 }
475
476 //......省略了部分代碼
505 // Schedule the first layout -before- adding to the window
520 // manager, to make sure we do the relayout before receiving
521 // any other events from the system.
requestLayout();//調用requestLayout方法異步刷新
523 if ((mWindowAttributes.inputFeatures
524 & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
525 mInputChannel = new InputChannel();
526 }
527 try {
528 mOrigWindowType = mWindowAttributes.type;
529 mAttachInfo.mRecomputeGlobalAttributes = true;
530 collectViewAttributes();
531 res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
532 getHostVisibility(), mDisplay.getDisplayId(),
533 mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
534 mAttachInfo.mOutsets, mInputChannel);
535 } catch (RemoteException e) {
536 mAdded = false;
537 mView = null;
538 mAttachInfo.mRootView = null;
539 mInputChannel = null;
540 mFallbackEventHandler.setView(null);
541 unscheduleTraversals();
542 setAccessibilityFocus(null, null);
543 throw new RuntimeException("Adding window failed", e);
544 } finally {
545 if (restore) {
546 attrs.restore();
547 }
548 }
549
550 }
requestLayout方法如下:
public void requestLayout(){
if(!mHandingLayoutInLayoutRequest){
checkThread();
mLayoutRequested = true;
scheduleTraversals();//view的繪製入口
}
}
可以看到scheduleTraversals方法實際是view的繪製入口
接着繼續看setView方法,在requestLayout方法之後,接着會通過mWindowSession 來完成window的添加,如下:
try {
528 mOrigWindowType = mWindowAttributes.type;
529 mAttachInfo.mRecomputeGlobalAttributes = true;
530 collectViewAttributes();
//添加widow
531 res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
532 getHostVisibility(), mDisplay.getDisplayId(),
533 mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
534 mAttachInfo.mOutsets, mInputChannel);
535 } catch (RemoteException e) {
536 mAdded = false;
537 mView = null;
538 mAttachInfo.mRootView = null;
539 mInputChannel = null;
540 mFallbackEventHandler.setView(null);
541 unscheduleTraversals();
542 setAccessibilityFocus(null, null);
543 throw new RuntimeException("Adding window failed", e);
544 } finally {
545 if (restore) {
546 attrs.restore();
547 }
548 }
在上面代碼中,mWindowSession是IWindowSession類型,是一個Binder對象,真正的實現類是Session,也就是說window的添加過程是一次IPC調用,在Session內部的addToDisplay方法中最終會通過WindowManagerService 的addwindow方法來實現window的添加,代碼如下:
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams, attrs, int viewVisibility,
int displayId, Rect outContentInsets, InputChannel outInputChannel){
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outContentInsets, outInputChannel);
}
2.2window 的刪除
刪除過程和添加類似,還是通過WindowManagerGlobal 實現,下面試WindowManagerGlobal 的removeview方法
public void removeView(View view, boolean immediate) {
345 if (view == null) {
346 throw new IllegalArgumentException("view must not be null");
347 }
348
349 synchronized (mLock) {
350 int index = findViewLocked(view, true);
351 View curView = mRoots.get(index).getView();
352 removeViewLocked(index, immediate);
353 if (curView == view) {
354 return;
355 }
356
357 throw new IllegalStateException("Calling with view " + view
358 + " but the ViewAncestor is attached to " + curView);
359 }
360 }
刪除過程首先通過findViewLocked方法來查找待刪除的view的索引,然後再調用removeViewLocked來做進一步刪除,如下:
private void removeViewLocked(int index, boolean immediate) {
388 ViewRootImpl root = mRoots.get(index);
389 View view = root.getView();
390
391 if (view != null) {
392 InputMethodManager imm = InputMethodManager.getInstance();
393 if (imm != null) {
394 imm.windowDismissed(mViews.get(index).getWindowToken());
395 }
396 }
//注意die方法
397 boolean deferred = root.die(immediate);
398 if (view != null) {
399 view.assignParent(null);
400 if (deferred) {
401 mDyingViews.add(view);
402 }
403 }
404 }
由root.die(immediate);可以看到,removeViewLocked方法是用過ViewRootImpl 來完成刪除操作的,die方法只是發送了一個請求刪除的消息,這個時候view還沒完成刪除操作,所以方法最後的代碼會將其添加到mDyingViews中,表示待刪除,ViewRootImpl 的die方法如下:
boolean die(boolean immediate) {
5580 // Make sure we do execute immediately if we are in the middle of a traversal or the damage
5581 // done by dispatchDetachedFromWindow will cause havoc on return.
5582 if (immediate && !mIsInTraversal) {
5583 doDie();
5584 return false;
5585 }
5586
5587 if (!mIsDrawing) {
5588 destroyHardwareRenderer();
5589 } else {
5590 Log.e(TAG, "Attempting to destroy the window while drawing!\n" +
5591 " window=" + this + ", title=" + mWindowAttributes.getTitle());
5592 }
5593 mHandler.sendEmptyMessage(MSG_DIE);
5594 return true;
5595 }
5596
die方法內部做了簡單的判斷,如果是異步刪除,那麼久發送一個MSG_DIE的消息,ViewRootImpl 中的Handler會處理此消息並調用doDie方法,如果是同步刪除,就不發送消息,直接調用doDie方法。handler處理消息代碼如下:
@Override
3256 public void handleMessage(Message msg) {
3257 switch (msg.what) {
3258 case MSG_INVALIDATE:
3259 ((View) msg.obj).invalidate();
3260 break;
//......省略代碼
3408 case MSG_DIE:
3409 doDie();
3410 break;
///......省略代碼
3489 }
doDie方法內部會調用dispatchDetachedFromWindow方法,真正刪除view的邏輯在該方法內部實現,在dodie方法最後會調用 WindowManagerGlobal.getInstance().doRemoveView(this);方法來刷新數據,將當前window關聯的對象從mRoots mParams mDyingViews list中刪除
doDie方法如下
void doDie() {
5598 checkThread();
5599 if (LOCAL_LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface);
5600 synchronized (this) {
5601 if (mRemoved) {
5602 return;
5603 }
5604 mRemoved = true;
5605 if (mAdded) {
//刪除view的邏輯
5606 dispatchDetachedFromWindow();
5607 }
5608
5609 if (mAdded && !mFirst) {
5610 destroyHardwareRenderer();
5611
5612 if (mView != null) {
5613 int viewVisibility = mView.getVisibility();
5614 boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
5615 if (mWindowAttributesChanged || viewVisibilityChanged) {
5616 // If layout params have been changed, first give them
5617 // to the window manager to make sure it has the correct
5618 // animation info.
5619 try {
5620 if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
5621 & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
5622 mWindowSession.finishDrawing(mWindow);
5623 }
5624 } catch (RemoteException e) {
5625 }
5626 }
5627
5628 mSurface.release();
5629 }
5630 }
5631
5632 mAdded = false;
5633 }
5634 WindowManagerGlobal.getInstance().doRemoveView(this);
5635 }
5636
dispatchDetachedFromWindow方法主要做一下幾件事:
1.垃圾回收相關工作,比如清楚數據和消息,移除毀掉
2.通過mWindowSession.remove(mWindow)方法來刪除window,這同樣是一個IPC過程,最終會調用windowmanagerservice中的removewindow方法
3.調用view的dispatchDetachedFromWindow方法時會在內部調用view的onDetachedFronWindow方法
void dispatchDetachedFromWindow() {
3066 if (mView != null && mView.mAttachInfo != null) {
3067 mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
3068 mView.dispatchDetachedFromWindow();
3069 }
3070
3071 mAccessibilityInteractionConnectionManager.ensureNoConnection();
3072 mAccessibilityManager.removeAccessibilityStateChangeListener(
3073 mAccessibilityInteractionConnectionManager);
3074 mAccessibilityManager.removeHighTextContrastStateChangeListener(
3075 mHighContrastTextManager);
3076 removeSendWindowContentChangedCallback();
3077
3078 destroyHardwareRenderer();
3079
3080 setAccessibilityFocus(null, null);
3081
3082 mView.assignParent(null);
3083 mView = null;
3084 mAttachInfo.mRootView = null;
3085
3086 mSurface.release();
3087
3088 if (mInputQueueCallback != null && mInputQueue != null) {
3089 mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
3090 mInputQueue.dispose();
3091 mInputQueueCallback = null;
3092 mInputQueue = null;
3093 }
3094 if (mInputEventReceiver != null) {
3095 mInputEventReceiver.dispose();
3096 mInputEventReceiver = null;
3097 }
3098 try {
3099 mWindowSession.remove(mWindow);
3100 } catch (RemoteException e) {
3101 }
3102
3103 // Dispose the input channel after removing the window so the Window Manager
3104 // doesn't interpret the input channel being closed as an abnormal termination.
3105 if (mInputChannel != null) {
3106 mInputChannel.dispose();
3107 mInputChannel = null;
3108 }
3109
3110 mDisplayManager.unregisterDisplayListener(mDisplayListener);
3111
3112 unscheduleTraversals();
3113 }
3114
2.3 window的更新
window的更新過程還是從WindowManagerGlobal 開始,調用updateViewLayout方法,如下:
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
324 if (view == null) {
325 throw new IllegalArgumentException("view must not be null");
326 }
327 if (!(params instanceof WindowManager.LayoutParams)) {
328 throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
329 }
330
331 final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
332
333 view.setLayoutParams(wparams);
334
335 synchronized (mLock) {
336 int index = findViewLocked(view, true);
337 ViewRootImpl root = mRoots.get(index);
338 mParams.remove(index);
339 mParams.add(index, wparams);
340 root.setLayoutParams(wparams, false);
341 }
342 }
該方法首先通過view.setLayoutParams(wparams);來更新view的LayoutParams,然後通過root.setLayoutParams(wparams, false);更新ViewRootImpl 的LayoutParams。root.setLayoutParams(wparams, false);方法內部回通過scheduleTraversals方法來對view重新佈局,包括測量,佈局,重繪這三個過程,如下:
void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
synchronized (this) {
final int oldInsetLeft = mWindowAttributes.surfaceInsets.left;
final int oldInsetTop = mWindowAttributes.surfaceInsets.top;
final int oldInsetRight = mWindowAttributes.surfaceInsets.right;
final int oldInsetBottom = mWindowAttributes.surfaceInsets.bottom;
final int oldSoftInputMode = mWindowAttributes.softInputMode;
final boolean oldHasManualSurfaceInsets = mWindowAttributes.hasManualSurfaceInsets;
//......
// Restore old surface insets.
mWindowAttributes.surfaceInsets.set(
oldInsetLeft, oldInsetTop, oldInsetRight, oldInsetBottom);
mWindowAttributes.hasManualSurfaceInsets = oldHasManualSurfaceInsets;
//......
mWindowAttributesChanged = true;
//view重繪
scheduleTraversals();
}
}
在scheduleTraversals方法中調用pokeDrawLockIfNeeded通過IPC調用WMS來更新,如下
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 最終調用了 doTraversal 來執行 測量、佈局、重繪操作
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
// 這裏最終通過 IPC 機制調用 wms 來更新視圖
pokeDrawLockIfNeeded();
}
}
參考 Android開發藝術探索