理解Window和WindowManager

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開發藝術探索

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章