理解Window和WindowManagager

Window和WindowManager

為了分析Window的工作機制斩个,我們看下如何用 WindowManager 添加一個 Window驾霜。

mFloatingButton = new Button(this);
mFloatingButton.setText("button");
mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
mLayoutParams = new WindowManager.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
mLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL| LayoutParams.FLAG_NOT_FOCUSABLE| LayoutParams.FLAG_SHOW_WHEN_LOCKED;
mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ALERT;
mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
mLayoutParams.x = 100;
mLayoutParams.y = 300;
mWindowManager.addView(mFloatingButton, mLayoutParams);

以上代碼將一個Button添加到屏幕坐標為(100,300)的位置上,要在Manifest.xml中添加權(quán)限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />冠场。
WindowManager中的 flags缓窜、type 這兩個參數(shù)比較重要。

1. 幾個flag屬性

  • WindowManager.LayoutParams.FLAG_SECURE
    不允許截屏浪腐;設(shè)置了這個屬性的窗口纵揍,在窗口可見的情況下顿乒,是會禁用系統(tǒng)的截圖功能的。那么問題來了:假如有一天泽谨,你的公司要求寫一個類似于‘閱后即焚’功能的頁面的話璧榄,不妨在activity中獲得WindowManager.LayoutParams并添加該屬性,輕輕松松搞定吧雹。

  • WindowManager.LayoutParams.FLAG_BLUR_BEHIND
    背景模糊骨杂;假如你的窗口設(shè)置了這個屬性,并且這個窗口可見雄卷,在這窗口之后的所有背景都會被模糊化搓蚪,但我還沒有發(fā)現(xiàn)一個屬性是可以控制模糊程度的。

  • WindowManager.LayoutParams.FLAG_DIM_BEHIND
    背景變暗丁鹉;設(shè)置這個效果的窗口妒潭,在窗口可見的情況下悴能,窗口后方的背景會相應(yīng)的變暗,這個屬性需要配合參數(shù)dimAmount一起使用雳灾,dimAmount會在后文中介紹漠酿。

  • WindowManager.LayoutParams.FLAG_FULLSCREEN
    設(shè)置全屏;這個屬性也許是大家接觸的最多的一個屬性谎亩,很多應(yīng)用開發(fā)過程中會要求有些頁面需要動態(tài)設(shè)置Activity為全屏炒嘲,而我們只需要獲得Activity的WindowManager.LayoutParams并設(shè)置WindowManager.LayoutParams.FLAG_FULLSCREEN屬性就行。

  • WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
    設(shè)備常亮匈庭;設(shè)置這個屬性的窗口夫凸,在窗口可見的情況下,整個屏幕會處于常亮并且高亮度的狀態(tài)阱持,并且不受待機時間的約束寸痢。

  • WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
    布局不受限制;設(shè)置這個屬性的窗口紊选,將不再受設(shè)備顯示范圍邊界 的約束啼止,通俗點講,就是窗口可以出設(shè)備之外兵罢,然后移除部分不可見献烦。具體會在坐標參數(shù)中講到。

  • WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
    不設(shè)置聚焦卖词;關(guān)于焦點獲得我有必要說明一下巩那,如果窗口獲得焦點的話,只要窗口處于可視化狀態(tài)此蜈,當前設(shè)備的物理按鍵點擊事件都會被這個窗口接收即横,但是如果不設(shè)置窗口的焦點的話,直接傳遞到之后窗口進行接收裆赵。這就導致一個問題东囚,如果你的需求要求你寫的懸浮窗點擊返回鍵能夠關(guān)閉或是進行其他操作的話,你就必須讓你的窗口獲得焦點战授,并為當前View設(shè)置按鍵監(jiān)聽事件页藻。

  • WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
    取消觸摸事件; 設(shè)置這個屬性的窗口將不再處理任何Touch事件植兰,就算顯示的View設(shè)置了onTouch事件份帐,那么這個窗口就會是一個僵尸窗口。

  • WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
    不知道怎么去歸納楣导,這個屬性還是比較有意思的废境,設(shè)置這個屬性的窗口,在窗口可見的情況下,就算窗口沒有設(shè)置屬性FLAG_NOT_FOCUSABLE噩凹,也就是在窗口獲得焦點的情況下朦促,當觸摸事件是在窗口之外區(qū)域的時候,窗口不在攔截觸摸事件栓始,而是將事件往下傳遞务冕,也算是解決聚焦后的事件攔截問題吧。

  • WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER
    顯示壁紙幻赚;官方文檔說明是在窗口之后顯示系統(tǒng)壁紙禀忆,但是我親測,似乎并沒有這個想效果落恼,還是這個屬性需要配合其他的屬性設(shè)置一起使用箩退,希望有設(shè)置成功的小伙伴能夠在評論區(qū)分享你的結(jié)果。

  • WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
    鎖屏顯示佳谦;關(guān)于這個屬性官方文檔給出的說明是在鎖屏的時候顯示的窗口戴涝,但是,實在慚愧钻蔑,在下還是沒有能夠有一個實驗結(jié)果啥刻,不知道是需要給權(quán)限呢還是需要同時進行其他設(shè)置。同樣咪笑,還是很希望有知道的小伙伴能夠在評論區(qū)向大家分享可帽。

  • WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
    點亮屏幕;設(shè)置這個屬性的窗口窗怒,當窗口顯示的時候映跟,如果設(shè)備處于待機狀態(tài),會點亮設(shè)備扬虚。這個應(yīng)該在很多鎖屏窗口中用的比較多努隙,比如收到消息點亮屏幕。

  • WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
    這個也不知道怎么去歸納辜昵,也是一個比較有意思的屬性荸镊,之前我們說到FLAG_NOT_TOUCH_MODAL,在窗口獲得焦點的情況下路鹰,當觸摸事件是在窗口之外區(qū)域的時候贷洲,窗口不在攔截觸摸事件,而是將事件往下傳遞晋柱,而如果再設(shè)置這個屬性,窗口能在MotionEvent.ACTION_OUTSIDE中收獲窗口之外的點擊事件诵叁,遺憾的是不能進行屏蔽雁竞,也就是說事件依然會向下傳遞。

2. type表示W(wǎng)indow的類型

Window有三種類型,分別為應(yīng)用Window碑诉、子Window彪腔、系統(tǒng)Window。Window是分層的进栽,每個Window都有對應(yīng)的 z-ordered.層級大的覆蓋在層級小的上面德挣。

              | 說          明 | 層級(z-order)
 -----------| ---------------| ------------
應(yīng)用Window | 對應(yīng)一個Activity| 1-99
子Window   | 常見的Dialog    | 1000-1999
系統(tǒng)Window | Toast,系統(tǒng)狀態(tài)欄 | 2000-2999

Window的內(nèi)部機制

Window是一個抽象概念,每一個Window都對應(yīng)著一個View和ViewRootImpl快毛,Window 和 View 通過ViewRootImpl來建立聯(lián)系格嗅。
下圖顯示了Activity的Window和Wms的關(guān)系

Window的添加過程

Window 的添加需要 WindowManager的addView來實現(xiàn),WindowManager是一個接口唠帝,它的實現(xiàn)是在 WindowManagerImpl類屯掖。

public interface WindowManager extends ViewManager{}        //WindowManager 是一個接口
public interface ViewManager{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

WindowManagerImpl并沒有直接實現(xiàn) Window的三大操作,而是全部交給WindowManagerGlobal來處理,WindowManagerGlobal是典型的單例襟衰。

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    ...
    @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);
    }
}

WindowManagerGlobal 中

1. 檢查參數(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;
if (parentWindow != null) {
    parentWindow.adjustLayoutParamsForSubWindow(wparams);
} 
2. 創(chuàng)建ViewRootImpl并將View添加到列表中

WindowManagerGlobal 內(nèi)部有幾個列表比較重要:

  1. 存儲的是所有 window 所對應(yīng)的 View
    private final ArrayList<View> mViews = new ArrayList<View>();
  2. 所有的 Window 對應(yīng)的 ViewRootImpl
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
  3. 所有 Window 所對應(yīng)的布局參數(shù)
        private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();  
root = new ViewRootImpl(view.getContext(), display);

view.setLayoutParams(wparams);

mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
3.通過ViewRootImpl來更新界面并完成 Window的添加

這個步驟由ViewRootImpl的setView方法完成贴铜。

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

setView內(nèi)部會通過 requestLayout 來完成異步刷新請求。scheduleTraversals 其實就是View繪制的入口瀑晒。

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

接著會通過WindowSession 最終來完成 Window 的添加過程绍坝。在下面的代碼中 mWindowSession 類型是一個 IWindowSession,它是一個Binder對象苔悦,真正的實現(xiàn)是 Session陷嘴,也就是說Window的添加過程是一次IPC調(diào)用。

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

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

final class Session extends IWindowSession.Stub
        implements IBinder.DeathRecipient {
        ...
    @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }   
        
}

如此一來灾挨,Window的添加請求就交給了WindowManagerService處理了。在WindowManagerService內(nèi)部會為每一個應(yīng)用保留一個單獨的Session竹宋。

Window的創(chuàng)建過程

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

從源碼分析Activity的啟動過程
在ActivityThread 的 performLaunchActivity

/**
 * 4.創(chuàng)建 ContextImpl 對象并通過 Activity 的 attach 方法來完成一些數(shù)據(jù)的初始化
 * 將 appContext 對象 attach 到 Activity 中
 */
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
        + r.activityInfo.name + " with config " + config);
activity.attach(appContext, this, getInstrumentation(), r.token,
        r.ident, app, r.intent, r.activityInfo, title, r.parent,
        r.embeddedID, r.lastNonConfigurationInstances, config,
        r.referrer, r.voiceInteractor);

中劳澄,在Activity的 attach方法中,系統(tǒng)會創(chuàng)建Activity所屬的Window對象并為其設(shè)置回調(diào)接口蜈七。由于Activity實現(xiàn)了Window的Callback接口秒拔,因此當Window接受到外界的狀態(tài)改變時就會回調(diào)Activity方法。Callback接口中方法很多飒硅,常見的有:onAttachedToWindow砂缩、onDetachedFromWindow、onWindowFocusChanged等等三娩。Api23的代碼如下:

mWindow = new PhoneWindow(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
    mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
    mWindow.setUiOptions(info.uiOptions);
}

Activity視圖是如何依附在Window上面庵芭?

由于Activity視圖由setContentView方法提供,我們只要看setContentView即可雀监。

public void setContentView(View view, ViewGroup.LayoutParams params) {
    getWindow().setContentView(view, params);
    initWindowDecorActionBar();
}

從Activity的setContentView看出双吆,Activity將具體的實現(xiàn)交給了Window處理眨唬,而Window的具體的實現(xiàn)是PhoneWindow。來看PhoneWindow的setContentView好乐。

  1. 如果沒有DecorView匾竿,就創(chuàng)建它。
  2. 將View添加到DecorView的mContentParent中蔚万。
  3. 回調(diào)Activity的 onContentChanged方法岭妖,通知Activity視圖改變了。
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
    // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    // decor, when theme attributes and the like are crystalized. Do not check the feature
    // before this happens.
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        view.setLayoutParams(params);
        final Scene newScene = new Scene(mContentParent, view);
        transitionTo(newScene);
    } else {
        mContentParent.addView(view, params);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
}

經(jīng)過了上面的三個步驟反璃,到這里DecorView已經(jīng)創(chuàng)建并初始化完畢昵慌。Activity的布局文件也被成功添加到DecorView的mContentParent中,但這時候DecorView還沒被WindowManager正式添加到Window中版扩。在ActivityThread的handleResumeActivity中废离,首先會調(diào)用Activity的onResume方法,接著會調(diào)用Activity的makeVisible方法礁芦,正是這個makeVisible方法中拴曲,DecorView真正完成了添加和顯示這兩個過程拜银。

void makeVisible() {
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);
}

參考資料及推薦閱讀

  1. 本文主要參考了《Android開發(fā)藝術(shù)探索》
  2. 示例:做一個炫酷的懸浮迷你音樂盒
  3. 為什么Dialog不能用Application的Context
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末廉侧,一起剝皮案震驚了整個濱河市祥绞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌未状,老刑警劉巖俯画,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異司草,居然都是意外死亡艰垂,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門埋虹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來猜憎,“玉大人,你說我怎么就攤上這事搔课∫雀蹋” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵爬泥,是天一觀的道長柬讨。 經(jīng)常有香客問我,道長袍啡,這世上最難降的妖魔是什么踩官? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮葬馋,結(jié)果婚禮上卖鲤,老公的妹妹穿的比我還像新娘肾扰。我一直安慰自己畴嘶,他們只是感情好蛋逾,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著窗悯,像睡著了一般区匣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蒋院,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天亏钩,我揣著相機與錄音,去河邊找鬼欺旧。 笑死姑丑,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的辞友。 我是一名探鬼主播栅哀,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼称龙!你這毒婦竟也來了留拾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤鲫尊,失蹤者是張志新(化名)和其女友劉穎痴柔,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體疫向,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡咳蔚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了搔驼。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谈火。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖匙奴,靈堂內(nèi)的尸體忽然破棺而出堆巧,到底是詐尸還是另有隱情,我是刑警寧澤泼菌,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布谍肤,位于F島的核電站,受9級特大地震影響哗伯,放射性物質(zhì)發(fā)生泄漏荒揣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一焊刹、第九天 我趴在偏房一處隱蔽的房頂上張望系任。 院中可真熱鬧恳蹲,春花似錦、人聲如沸俩滥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽霜旧。三九已至错忱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間挂据,已是汗流浹背以清。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留崎逃,地道東北人掷倔。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像个绍,于是被迫代替她去往敵國和親勒葱。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

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

  • Window是一個抽象類障贸,它的具體實現(xiàn)是PhoneWindow错森。創(chuàng)建一個Window是很簡單的事,只需要通過Win...
    小柏不是大白閱讀 1,183評論 0 0
  • Window表示一個窗口的概念篮洁,Window是一個抽象類涩维,它的具體實現(xiàn)是PhoneWindow。創(chuàng)建一個Windo...
    斜杠時光閱讀 2,891評論 0 14
  • Window 表示一個窗口的概念袁波,是一個抽象類瓦阐,它的具體實現(xiàn)是 PhoneWindow。 8.1 Window 和...
    kongjn閱讀 1,470評論 0 3
  • 轉(zhuǎn)自:http://blog.csdn.net/feiduclear_up/article/details/492...
    1024HOPE閱讀 2,313評論 0 2
  • 當聊到某個學生的某方面缺點時篷牌,他總會特別理直氣壯的說睡蟋,老師那個誰誰誰還不如我呢,我比他好多了枷颊。學生做題的時候總是有...
    努力努力再努力hh閱讀 155評論 0 4