Android開發(fā)藝術(shù)探索(7) --- 理解Window和WindowManager

1. Window和WindowManager

Window表示一個窗口的概念,如在創(chuàng)建對話框時就需要Window來進(jìn)行。Window通過WindowManager來操作Window,但WIndow的具體實現(xiàn)都需要使用WindowManagerService

1.1 Window

  1. WindowFlags
參數(shù) 作用
FLAG_NOT_TOUCH_MODAL 表示Window不需要獲取焦點,也不需要接受各種輸入時間,此標(biāo)記會同時啟用FLAG_NOT_TOUCHABLE
FLAG_NOT_TOUCHABLE 在此模式下,系統(tǒng)會將當(dāng)前Window區(qū)域以外的單擊事件傳遞給底層的Window,當(dāng)前Window區(qū)域的點擊由自己處理
FLAG_SHOW_WHEN_LOCKED Window顯示在鎖屏上
  1. WindowType
  • 應(yīng)用Window:對應(yīng)著一個Activity
  • Window:不能單獨存在,需要附屬在特定的父Window
  • 系統(tǒng)Window:需要聲明權(quán)限才能創(chuàng)建的Window,例如Toast和系統(tǒng)狀態(tài)欄

如果采用TYPE_SYSTEM_ERROR,需要聲明權(quán)限<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

  1. Window分層

Z-index,應(yīng)用Window的層級范圍1-99,子Window范圍1000-1999,系統(tǒng)Window層級范圍是2000-2999

  1. WindowManager
  • 增刪改Window實際上是操作Window里的View
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btn = new Button(this);
        btn.setText("TEST");
        WindowManager.LayoutParams params = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
        params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
        params.gravity = Gravity.LEFT| Gravity.TOP;
        params.x = 100;
        params.y = 300;
        params.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
        getWindowManager().addView(btn, params);
    }
}
btn.setOnTouchListener(new View.OnTouchListener() {
                                   @Override
                                   public boolean onTouch(View view, MotionEvent motionEvent) {
                                       int rawX = (int) motionEvent.getX();
                                       int rawY = (int) motionEvent.getY();
                                       switch (motionEvent.getAction()) {
                                           case MotionEvent.ACTION_MOVE:
                                               params.x = rawX;
                                               params.y = rawY;
                                               mWindow.updateViewLayout(btn, params);
                                       }
                                       return false;
                                   }
                               }
        );
Window機(jī)制

2. Window的內(nèi)部機(jī)制

Window每一個都對應(yīng)著每一個View和每一個ViewRootImpl,因此Window不是實際存在的,而是以View的形式存在的

2.1 Window的添加過程

@Override
public void addView(View view, ViewGroup.LayoutParams params) {
    mGlobal.addView(view, params, mDisplay, mParentWindow);
}

@Override
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
    mGlobal.updateViewLayout(view, params);
}

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

WindowManagerGlobal的實現(xiàn)步驟:
步驟一:檢查參數(shù)是否合法,如果是子Window則需要調(diào)整一些參數(shù)

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;
// 如果是子View,則會調(diào)整參數(shù)
 if (parentWindow != null) {
     parentWindow.adjustLayoutParamsForSubWindow(wparams);
 }

步驟二:創(chuàng)建ViewRootImpl并將View添加到列表中
WindowManagerGlobal內(nèi)部有如下幾個列表比較重要:

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>();
  • mViews存儲的是所有Window所對應(yīng)的View,
  • mRoots存儲的是所有Window所對應(yīng)的ViewRootImpl
  • mParams存儲的是所有Window所對應(yīng)的布局參數(shù)
  • mDyingViews則存儲了那些正在被刪除的View對象,或者說是那些已經(jīng)調(diào)用removeView方法但是刪除操作還未完成的Window對象。

addView中通過如下方式將Window的一系列對象添加到列表中:

root = new ViewRootImpl(view.getContext(), display);

view.setLayoutParams(wparams);

mViews.add(view);
mRoots.add(root);
mParams.add(wparams);

步驟三:通過ViewRootImpl來更新界面并完成Window的添加過程

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

接著會通過mWindowSession最終來完成Window的添加過程。mWindowSession的類型是IWindowSession,它是一個Binder對象,真正的實現(xiàn)類是Session,也就是Window的添加過程是一次IPC調(diào)用。

Session內(nèi)部會通過WindowManagerService來實現(xiàn)Window的添加

2.2 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的Index
        View curView = mRoots.get(index).getView(); // 找到待刪除的View
        removeViewLocked(index, immediate); // 進(jìn)行刪除
        if (curView == view) {
            return;
        }

        throw new IllegalStateException("Calling with view " + view
                + " but the ViewAncestor is attached to " + curView);
    }
}

WindowManagerGlobal#removeViewLocked

private void removeViewLocked(int index, boolean immediate) {
     ViewRootImpl root = mRoots.get(index); // 從mRoots數(shù)組中找到對應(yīng)的root
     View view = root.getView(); // 找到對應(yīng)的View

     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來完成刪除操作的。在WindowManager中提供了兩種刪除接口removeViewremoveViewImmediate,它們分別表示異步刪除同步刪除
  • removeViewImmediate需要特別注意,一般來說不需要使用此方法來刪除Window以免發(fā)生意外的錯誤。這里主要說異步刪除的情況,具體的刪除操作由ViewRootImpldie方法來完成。在異步刪除的情況下,die方法只是發(fā)送了一個請求刪除的消息后就立刻返回了,這個時候View并沒有完成刪除操作,所以最后會將其添加到mDyingViews中,mDyingViews表示待刪除的View列表。ViewRootImpldie方法如下所示:
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()方法,內(nèi)部會調(diào)用dispatchDetachedFromWindow方法
異步刪除:發(fā)送消息mHandler.sendEmptyMessage(MSG_DIE);
dispatchDetachedFromWindow方法:

  • 垃圾回收相關(guān)工作,比如清楚數(shù)據(jù)和消息、移除回調(diào)等
  • 通過Sessionremove方法刪除Window,最終會調(diào)用WindowManagerServiceremoveWindow方法
  • 調(diào)用ViewdispatchDetachedFromWindow
  • 調(diào)用WindowManagerGlobaldoRemoveView方法刷新數(shù)據(jù),包括mRoots,mParams以及mDyingViews,需要將當(dāng)前Window所關(guān)聯(lián)的這三類對象從列表中刪除。

2.3 Window的更新過程

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);//替換掉老的LayoutParams

    synchronized (mLock) {
        int index = findViewLocked(view, true);
        ViewRootImpl root = mRoots.get(index);
        mParams.remove(index); //先刪除再增加
        mParams.add(index, wparams);
        root.setLayoutParams(wparams, false);
    }
}

首先它需要更新ViewLayoutParams并替換掉老的LayoutParams,接著再更新ViewRootImpl中的LayoutParams,這一步是通過ViewRootImplsetLayoutParams方法來實現(xiàn)的。在ViewRootImpl中會通過scheduleTraversals方法來對View重新布局,包括測量,布局,重繪這三個過程。除了View本身的重繪以外,ViewRootImpl還會通過WindowSession來更新Window的視圖,這個過程最終是由WindowManagerServicerelayoutWindow()來具體實現(xiàn)的,它同樣是一個IPC過程。

第八章(2)---Window的內(nèi)部機(jī)制

3. Window的創(chuàng)建過程

3.1 Activity的Window啟動過程

3.2 Dialog的Window創(chuàng)建過程

3.3 Toast的Window創(chuàng)建過程

  • TN是一個Binder類,在NMS處理Toast請求時,需要Handler將其從Binder線程池中切換到發(fā)送Toast的線程,因此Toast無法在沒有Looper的線程中彈出
  • enqueueToastToast請求封裝成ToastRecord加入mToastQueuemToastQueue最多存在50個彈出請求
  • ToastRecordNMS之間通過TN對象Callbackshow,hide方法完成Toast的顯示和隱藏功能,都是IPC
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容