Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 安卓學習筆記之理解WindowManger

安卓學習筆記之理解WindowManger

編輯:關於Android編程

Window的概念

Window表示的是一個窗口的概念,它是一個抽象類,它的具體實現是PhoneWindow。創建一個Window需要通過WindowManger來完成。WindowManger是外界訪問Window的入口,Window的具體實現位於WindowMangerService,WindowManger與WindowMangerService的交互是一個IPC過程。Android中所有的View都是Window來呈現的,不管是Activity、Toast還是Dialog,它們的視圖都是附加到Window上的,因此Window是View的直接管理者
View的事件分發機制中的事件傳遞:單擊事件由Activity內部的Window -> Decor View -> View

WindowManger

1. 添加view到Window示例

使用WindowManger添加一個view到Window
自定義浮窗 需要權限android.permission.SYSTEM_ALERT_WINDOW

    /**
     * 顯示浮窗
     * @param content   要填充的文本內容
     * @param layoutId   用於創建窗體View的布局
     */
    public  void show(String content,int layoutId) {
          wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
        params = new WindowManager.LayoutParams();
        screenHeight = AppInfoUtils.getScreenSize(context).height;
        screenWidth = AppInfoUtils.getScreenSize(context).width;
        // 加載布局
        view = View.inflate(mContext, layoutId, null);
        // 設置浮窗params屬性
        params.width = WindowManager.LayoutParams.WRAP_CONTENT;
        params.height = WindowManager.LayoutParams.WRAP_CONTENT;
        params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        params.type = WindowManager.LayoutParams.TYPE_PHONE;
        params.gravity = Gravity.TOP+Gravity.LEFT; // 將重心設置為左上方
        params.format = PixelFormat.TRANSLUCENT;    // 半透明
        params.x = sp.getInt("startX", 0);      // 設置顯示位置
        params.y = sp.getInt("startY", 0);
        TextView tvLocation =  (TextView) view.findViewById(R.id.tv_toast_location);
        tvLocation.setText(content);
        // 將View添加到窗體管理器
        wm.addView(view, params);
    }

2. 參數解析

1、LayoutParams.Flags參數表示Window的屬性,通過設置它的選項可以控制Window的顯示特性。如下幾種常見選項:

FLAG_NOT_FOCUSABLE

不許獲得焦點

FLAG_NOT_TOUCHABLE

不接受觸摸屏事件

FLAG_NOT_TOUCH_MODAL

當窗口可以獲得焦點(沒有設置 FLAG_NOT_FOCUSALBE 選項)時,仍然將窗口范圍之外的點設備事件(鼠標、觸摸屏)發送給後面的窗口處理。否則它將獨占所有的點設備事件,而不管它們是不是發生在窗口范圍內。

FLAG_SHOW_WHEN_LOCKED

當屏幕鎖定時,窗口可以被看到。這使得應用程序窗口優先於鎖屏界面。可配合FLAG_KEEP_SCREEN_ON選項點亮屏幕並直接顯示在鎖屏界面之前。可使用FLAG_DISMISS_KEYGUARD選項直接解除非加鎖的鎖屏狀態。此選項只用於最頂層的全屏幕窗口。

FLAG_DIM_BEHIND

  窗口之後的內容變暗

FLAG_BLUR_BEHIND

窗口之後的內容變模糊。

2、Type參數表示Window的類型,有3種主要類型:
1)Application_windows (應用Window):

    值在 FIRST_APPLICATION_WINDOW 和 LAST_APPLICATION_WINDOW 之間。
    是通常的、頂層的應用程序窗口。必須將 token 設置成 activity 的 token 。  

2)Sub_windows (子Window):

    取值在 FIRST_SUB_WINDOW 和 LAST_SUB_WINDOW 之間。與頂層窗口相關聯,token 必須設置為它所附著的宿主窗口的 token。

3)System_windows (系統Window):

    取值在 FIRST_SYSTEM_WINDOW 和 LAST_SYSTEM_WINDOW 之間。

3、Window的層次

每個Window都有對應的z-ordered,層次大的會覆蓋到層次小的Window上面。在三類Window中應用Window的層級范圍在1~99之間,子Window的范圍在1000~1999之間,系統Window的層級范圍在2000~2999之間。

要使Window位於所有Window的最頂層,采用較大的層級即可,系統Window的層級是最大的,一般選用TYPE_SYSTEM_OVERLAY或TYPE_SYSTEM_ERROR,同時要聲明權限android.permission.SYSTEM_ALERT_WINDOW。如下示例

    params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;

4、WindowManger提供的常用方法
WindowManger繼承自ViewManager,提供了添加view、刪除view和更新view,這三個方法都是定義在ViewManager。

public interface ViewManager
{
    /**
     * @param view The view to be added to this window.
     * @param params The LayoutParams to assign to view.
     */
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

Window的內部機制

Window是一個抽象的概念,每一個Window都對應著一個view和ViewRootImpl,Window與View通過ViewRootImpl建立起聯系,Window是以View作為實體存在,實際使用WindowMager訪問來Window,外部無法直接訪問Window。WindowManger提供了三個針對View的接口方法addView、updateViewLayout和removeView,分析Window的內部機制從Window的添加、更新和刪除開始。

Window的添加過程

Window的添加依賴於WindowManger,而WindowManger是一個接口,它的具體實現類是WindowMangerImpl,在WindowMangerImpl中實現了如下幾個操作view的方法
@Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }
@Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.updateViewLayout(view, params);
    }
@Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }

@Override
     public void removeViewImmediate(View view) {
        mGlobal.removeView(view, true);
    }

由上可知,WindowMangerImpl將操作view的實現都委托給了WindowManagerGlobal(即mGlobal),下面來看一下WindowManagerGlobal的addView方法,完整代碼如下

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
// 1、---
        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;
// 2、---
        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);
            }
// 3、---
            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.
            }
// 4、---
            // 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);
                    }
                }
            }

// 5、---
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }
// 6、---
        // 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;
        }
    }

上面addView方法大概做了如下幾件事:
1、檢查參數是否合法,並判斷當前添加的是否為子Window(parentWindow是否為空),若為子Window則為其做相關調整,否則為其開啟硬件加速
2、監視系統屬性的變化
3、通過findViewLocked獲取mViews中view的索引,看添加的view是否在mViews的集合裡,如果獲取的index>=0,此view存在,接著判斷要刪除的集合是否包含此view,若包含則直接執行doDie()刪除當前view,若不包含則會拋出異常(此view正在被刪除,還沒有完成)
4、判斷添加的是否為panel window,若是則找出以備後查
5、將Window的一系列參數添加到集合中,幾種集合如下:

    mViews:存儲了所有Window所對應的View
    mRoots:存儲了所有Window所對應的ViewRootImpl
    mParams:存儲了所有Window所對應的布局參數
    mDyingViews:存儲的是即將被刪除的View對象或正在被刪除的View對象

6、通過ViewRootImpl的setView方法來完成界面的更新,並完成Window的添加。

在setView內部會通過requestLayout方法來完成異步刷新請求,scheduleTraversals實際是View的繪制入口。

 @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

然後會接著執行如下代碼,WindowSession最終完成Window的添加,mWindowSession的類型是IWindowSession,它是一個Binder對象,真正的實現類是Session,因此Window的添加的過程是一個IPC調用

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

在Session內部會調用WindowManagerService的addWindow方法進行Window方法添加,具體的過程在WindowManagerService中實現了。WindowManagerService會為每個應用保留一個單獨的Session。

@Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outInputChannel);
    }

到此Window的添加就完成了。大致走了如下流程

WindowManger -> WindowMangerImpl -> WindowManagerGlobal>addView -> ViewRootImpl>setView>requestLayout -> (IPC)Session>addToDisplay -> WindowMangerService>addWindow

Window的刪除過程

刪除的過程與添加類似,通過WindowManagerGlobal來實現刪除,下面看它的removeView方法

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不為空的情況下,通過findViewLocked查找view在mViews(上述)中的索引,然後通過removeViewLocked進行刪除。看一下這兩個方法:

private int findViewLocked(View view, boolean required) {
        final int index = mViews.indexOf(view);
        if (required && index < 0) {
            throw new IllegalArgumentException("View=" + view + " not attached to window manager");
        }
        return index;
    }
private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();

        if (view != null) {
            InputMethodManager imm = InputMethodManager.getInstance();
            if (imm != null) {
                imm.windowDismissed(mViews.get(index).getWindowToken());
            }
        }
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                mDyingViews.add(view);
            }
        }
    }

從removeViewLocked方法可以看出,刪除操作是由ViewRootImpl來完成的,刪除分為兩種,分別為同步刪除(removeViewImmediate)和異步刪除(removeView),在ViewRootImpl的die(immediate)方法中進行判斷。如果為同步則直接調用doDie方法進行刪除,否則會發送一個消息進行異步處理,同時執行mDyingViews.add(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(TAG, "Attempting to destroy the window while drawing!\n" +
                    "  window=" + this + ", title=" + mWindowAttributes.getTitle());
        }
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }

在doDie方法內部調用dispatchDetachedFromWindow()方法刪除Window,最後調用WindowManagerGlobal的doRemoveView方法進行數據刷新,包括mRoots,mViews,mParams和mDyingViews,需要將當前Window所關聯的這三類對象從集合中刪除

void doDie() {
        checkThread();
        synchronized (this) {
            if (mRemoved) {
                return;
            }
            mRemoved = true;
            if (mAdded) {
                dispatchDetachedFromWindow();
            }

            ... 
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }

在dispatchDetachedFromWindow方法中真正執行刪除操作,內部作了如下幾件事:

1、垃圾回收的相關工作,如清理數據和消息、移除回調和監聽。
2、調用Wiew的dispatchDetachedFromWindow方法,它的方法內部會調用onDetachedFromWindow()方法,當view從Window被移除,此方法就會被調用,可以在此方法中做一些資源回收工作,諸如終止動畫、線程
3、通過Session的remove方法移除Window:mWindowSession.remove(mWindow),此過程是一個IPC過程,最終會調用WindowMangerService的removeWindow方法。

到此,Window的刪除過程就已經完成了,大致流程

WindowManger -> (實現類)WindowMangerImpl ->(委托類) WindowManagerGlobal>removeView>removeViewLocked  ->  ViewRootImpl>doDie>dispatchDetachedFromWindow  ->  (IPC) Session>remove  ->  WindowMangerService>removeWindow

Window的更新過程

同創建、刪除Window類似,更新Window的實施者依然是WindowManagerGlobal,下面看它的updateViewLayout方法

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

更新過程首先要替換舊的params,接著通過ViewRootImpl的setLayoutParams方法進行更新ViewRootImpl中的params,在setLayoutParams方法內通scheduleTraversals進行Vew的重新布局(測量、布局、繪制),並會通過如下流程來更新Window的視圖

scheduleTraversals-> doTraversal -> performTraversals-> relayoutWindow-> mWindowSession.relayout -> mService.relayoutWindow

到此Window的更新就完成了,大致流程如下:

WindowManger -> WindowMangerImpl  ->  WindowManagerGlobal>updateViewLayout -> ViewRootImpl>setLayoutParams>scheduleTraversals>doTraversal>performTraversals>relayoutWindow -> (IPC)Session>relayout  -> WindowMangerService>relayoutWindow

後記:此篇參考了安卓開發藝術探索,融入個人總結所成,如有錯誤請不吝賜教。特此說明。更多細節可查閱android源碼。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved