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

1. Window和WindowManager

Window表示一個(gè)窗口的概念,如在創(chuàng)建對(duì)話(huà)框時(shí)就需要Window來(lái)進(jìn)行益楼。Window通過(guò)WindowManager來(lái)操作Window猾漫,但WIndow的具體實(shí)現(xiàn)都需要使用WindowManagerService

1.1 Window

  1. WindowFlags
參數(shù) 作用
FLAG_NOT_TOUCH_MODAL 表示Window不需要獲取焦點(diǎn),也不需要接受各種輸入時(shí)間感凤,此標(biāo)記會(huì)同時(shí)啟用FLAG_NOT_TOUCHABLE
FLAG_NOT_TOUCHABLE 在此模式下静袖,系統(tǒng)會(huì)將當(dāng)前Window區(qū)域以外的單擊事件傳遞給底層的Window,當(dāng)前Window區(qū)域的點(diǎn)擊由自己處理
FLAG_SHOW_WHEN_LOCKED Window顯示在鎖屏上
  1. WindowType
  • 應(yīng)用Window:對(duì)應(yīng)著一個(gè)Activity
  • Window:不能單獨(dú)存在俊扭,需要附屬在特定的父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的層級(jí)范圍1-99萨惑,子Window范圍1000-1999捐康,系統(tǒng)Window層級(jí)范圍是2000-2999

  1. WindowManager
  • 增刪改Window實(shí)際上是操作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每一個(gè)都對(duì)應(yīng)著每一個(gè)View和每一個(gè)ViewRootImpl,因此Window不是實(shí)際存在的庸蔼,而是以View的形式存在的

2.1 Window的添加過(guò)程

@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的實(shí)現(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,則會(huì)調(diào)整參數(shù)
 if (parentWindow != null) {
     parentWindow.adjustLayoutParamsForSubWindow(wparams);
 }

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

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

addView中通過(guò)如下方式將Window的一系列對(duì)象添加到列表中:

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

view.setLayoutParams(wparams);

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

步驟三:通過(guò)ViewRootImpl來(lái)更新界面并完成Window的添加過(guò)程

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

接著會(huì)通過(guò)mWindowSession最終來(lái)完成Window的添加過(guò)程掏膏。mWindowSession的類(lèi)型是IWindowSession劳翰,它是一個(gè)Binder對(duì)象,真正的實(shí)現(xiàn)類(lèi)是Session馒疹,也就是Window的添加過(guò)程是一次IPC調(diào)用佳簸。

Session內(nèi)部會(huì)通過(guò)WindowManagerService來(lái)實(shí)現(xiàn)Window的添加

2.2 Window的刪除過(guò)程

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ù)組中找到對(duì)應(yīng)的root
     View view = root.getView(); // 找到對(duì)應(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是通過(guò)ViewRootImpl來(lái)完成刪除操作的。在WindowManager中提供了兩種刪除接口removeViewremoveViewImmediate颖变,它們分別表示異步刪除同步刪除
  • removeViewImmediate需要特別注意生均,一般來(lái)說(shuō)不需要使用此方法來(lái)刪除Window以免發(fā)生意外的錯(cuò)誤听想。這里主要說(shuō)異步刪除的情況,具體的刪除操作由ViewRootImpldie方法來(lái)完成马胧。在異步刪除的情況下汉买,die方法只是發(fā)送了一個(gè)請(qǐng)求刪除的消息后就立刻返回了,這個(gè)時(shí)候View并沒(méi)有完成刪除操作佩脊,所以最后會(huì)將其添加到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)部會(huì)調(diào)用dispatchDetachedFromWindow方法
異步刪除:發(fā)送消息mHandler.sendEmptyMessage(MSG_DIE);
dispatchDetachedFromWindow方法:

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

2.3 Window的更新過(guò)程

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耿眉,這一步是通過(guò)ViewRootImplsetLayoutParams方法來(lái)實(shí)現(xiàn)的。在ViewRootImpl中會(huì)通過(guò)scheduleTraversals方法來(lái)對(duì)View重新布局鱼响,包括測(cè)量鸣剪,布局,重繪這三個(gè)過(guò)程丈积。除了View本身的重繪以外筐骇,ViewRootImpl還會(huì)通過(guò)WindowSession來(lái)更新Window的視圖,這個(gè)過(guò)程最終是由WindowManagerServicerelayoutWindow()來(lái)具體實(shí)現(xiàn)的江滨,它同樣是一個(gè)IPC過(guò)程铛纬。

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

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

3.1 Activity的Window啟動(dòng)過(guò)程

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

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

  • TN是一個(gè)Binder類(lèi),在NMS處理Toast請(qǐng)求時(shí)唬滑,需要Handler將其從Binder線(xiàn)程池中切換到發(fā)送Toast的線(xiàn)程告唆,因此Toast無(wú)法在沒(méi)有Looper的線(xiàn)程中彈出
  • enqueueToastToast請(qǐng)求封裝成ToastRecord加入mToastQueuemToastQueue最多存在50個(gè)彈出請(qǐng)求
  • ToastRecordNMS之間通過(guò)TN對(duì)象Callbackshow,hide方法完成Toast的顯示和隱藏功能晶密,都是IPC
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末擒悬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子稻艰,更是在濱河造成了極大的恐慌茄螃,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件连锯,死亡現(xiàn)場(chǎng)離奇詭異归苍,居然都是意外死亡用狱,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)拼弃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)夏伊,“玉大人,你說(shuō)我怎么就攤上這事吻氧∧缬牵” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵盯孙,是天一觀(guān)的道長(zhǎng)鲁森。 經(jīng)常有香客問(wèn)我,道長(zhǎng)振惰,這世上最難降的妖魔是什么歌溉? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮骑晶,結(jié)果婚禮上痛垛,老公的妹妹穿的比我還像新娘。我一直安慰自己桶蛔,他們只是感情好匙头,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著仔雷,像睡著了一般蹂析。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上碟婆,一...
    開(kāi)封第一講書(shū)人閱讀 51,688評(píng)論 1 305
  • 那天识窿,我揣著相機(jī)與錄音,去河邊找鬼脑融。 笑死喻频,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的肘迎。 我是一名探鬼主播甥温,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼妓布!你這毒婦竟也來(lái)了姻蚓?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤匣沼,失蹤者是張志新(化名)和其女友劉穎狰挡,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡加叁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年倦沧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片它匕。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡展融,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出豫柬,到底是詐尸還是另有隱情告希,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布烧给,位于F島的核電站燕偶,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏础嫡。R本人自食惡果不足惜指么,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望驰吓。 院中可真熱鬧,春花似錦系奉、人聲如沸檬贰。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)翁涤。三九已至,卻和暖如春萌踱,著一層夾襖步出監(jiān)牢的瞬間葵礼,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工并鸵, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鸳粉,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓园担,卻偏偏與公主長(zhǎng)得像届谈,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子弯汰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容