第8章 理解Window和WindowManager

Window表示一個(gè)窗口疚鲤,是View的實(shí)際管理者客年。在第4章的事件分發(fā)中已經(jīng)知道了屡谐,點(diǎn)擊事件是通過Window->DecorView->View來傳遞的氨鹏。

Window是一個(gè)抽象類颜屠,具體實(shí)現(xiàn)是PhoneWindow類辰妙。我們可以通過WindowManager來操作Window,具體實(shí)現(xiàn)是在WindowManagerService中實(shí)現(xiàn)的甫窟;WindowManager和WindowManagerService通過IPC連接密浑。

8.1 Window和WindowManager

8.1.1 創(chuàng)建一個(gè)Window

首先,通過WindowManager添加一個(gè)窗口:

        Button button = new Button(this);
        button.setText("button");
        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_APPLICATION, 0,
                PixelFormat.TRANSPARENT);
        lp.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
        lp.gravity = Gravity.LEFT | Gravity.TOP;
        lp.x = 100;
        lp.y = 300;
        WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
        wm.addView(button, lp);

這里注意粗井,原書中代碼報(bào)錯(cuò)尔破,必須給窗口指定一個(gè)type,我這里指定的WindowManager.LayoutParams.TYPE_APPLICATION浇衬;不清楚是否是系統(tǒng)版本的問題懒构。

8.1.2 Type

Type有三種類型:

  • 應(yīng)用Window
    應(yīng)用類Window對(duì)應(yīng)一個(gè)Activity。取值在1~99之間耘擂。

  • 子Window
    子Window附屬于一個(gè)特定的父Window胆剧,而不能單獨(dú)存在。取值在1000~1999之間醉冤。

  • 系統(tǒng)Window
    系統(tǒng)Window需要系統(tǒng)權(quán)限才能創(chuàng)建秩霍。取值在2000~2999之間。

8.1.3 Flag

Flag參數(shù)表示W(wǎng)indow的屬性蚁阳,可以控制Window的顯示特性铃绒。

  • FLAG_NOT_FOCUSABLE
    表示W(wǎng)indow不需要獲取焦點(diǎn),也不需要接受各種輸入事件螺捐。

  • FLAG_NOT_TOUCH_MODAL
    表示W(wǎng)indow會(huì)處理當(dāng)前區(qū)域以內(nèi)的單擊事件颠悬,同時(shí)把當(dāng)前Window區(qū)域以外的單擊事件傳遞到底層。

  • FLAG_SHOW_WHEN_LOCKED
    表示W(wǎng)indow可以顯示在鎖屏的界面上定血。

8.1.4 權(quán)限

想要顯示系統(tǒng)級(jí)的Window是需要申請(qǐng)權(quán)限的赔癌。例如當(dāng)type = TYPE_SYSTEM_ERROR的時(shí)候,需要在AndroidManifest中添加<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />澜沟。

在Android 6.0及以后届榄,需要手動(dòng)開啟懸浮窗權(quán)限:

    /**
     * 檢查是否有懸浮窗權(quán)限
     */
    @TargetApi(Build.VERSION_CODES.M)
    private void checkOverlay() {
        if (!Settings.canDrawOverlays(this)) {
            AlertDialog dialog = new AlertDialog.Builder(this)
                    .setTitle("權(quán)限禁止")
                    .setMessage("懸浮窗權(quán)限被禁止,點(diǎn)擊確定前往設(shè)置")
                    .setPositiveButton("確定", 
                        (dialog1, which) -> jumpToOverlaySetting())
                    .setCancelable(false)
                    .create();
            dialog.show();
        }
    }

    private void jumpToOverlaySetting() {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
            startActivityForResult(intent, 1);
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 1) {
            checkOverlay();
        }
    }

然后在onActivityResult()中倔喂,再判斷是否開啟權(quán)限铝条。

如果不想申請(qǐng)權(quán)限靖苇,使用type = TYPE_TOAST也可以創(chuàng)建一個(gè)懸浮窗。但是在Android 7.1.1(API 25)開始班缰,TYPE_TOAST被限制使用[1]贤壁。如果想要在7.1.1以后使用懸浮窗,必須開啟權(quán)限埠忘。

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

Window是一個(gè)抽象概念脾拆,而不是實(shí)際存在的。每一個(gè)Window都對(duì)應(yīng)著一個(gè)View和一個(gè)ViewRootImpl莹妒,Window和View是通過ViewRootImpl來建立聯(lián)系的名船。Window實(shí)際存在的形式是View。
對(duì)Window的操作只能通過WindowManager來進(jìn)行旨怠。WindowManager主要提供了三個(gè)方法:

  • addView(View, LayoutParams)
  • removeView(View)
  • updateView(View, LayoutParams)

可以看到渠驼,WindowManager的操作對(duì)象是View。

8.2.1 Window的添加過程

在WindowManager的實(shí)現(xiàn)類WindowManagerImpl中鉴腻,找到addView()方法:

    public void addView(View view, ViewGroup.LayoutParams params) {
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

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

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

而mGlobal的定義:

    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

這個(gè)單例類迷扇,在第四章[2]的時(shí)候已經(jīng)接觸過了。這里我們看到爽哎,WindowManager的添加蜓席、刪除、更新三個(gè)方法课锌,其實(shí)都全權(quán)交給了WindowManagerGlobal厨内。

轉(zhuǎn)到WindowManagerGlobal的addView()方法中:

    public void addView(View view, 
        ViewGroup.LayoutParams params, 
        Display display, 
        Window parentWindow) {
        // ... 省略若干代碼

        // 創(chuàng)建ViewRoot
        ViewRootImpl root;
        root = new ViewRootImpl(view.getContext(), display);

        view.setLayoutParams(wparams);

        // 將View添加到列表中
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);

        // 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.
            if (index >= 0) {
                removeViewLocked(index, true);
            }
            throw e;
         }
    }

這里主要是將待添加的View、ViewRoot渺贤、LayoutParams保存到列表中雏胃,創(chuàng)建了ViewRoot,以及調(diào)用了ViewRoot的setView方法癣亚。
接下來繼續(xù)跟蹤setView方法丑掺。

    public void setView(View view, 
          WindowManager.LayoutParams attrs, 
          View panelParentView) {
          // ...省略若干代碼

          // Schedule the first layout -before- adding to the window
          // manager, to make sure we do the relayout before receiving
          // any other events from the system.
          requestLayout();
          // ...
          res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                   getHostVisibility(), mDisplay.getDisplayId(),
                   mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                   mAttachInfo.mOutsets, mInputChannel);
          // ...
    }

requestLayout()之前有一個(gè)注釋:

// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.

大概意思是說获印,為了保證在接收到其他系統(tǒng)事件之前布局好述雾,我們?cè)趯iew添加到Window之前進(jìn)行第一次的布局。

然后調(diào)用了WindowSession.addToDisplay()方法兼丰。mWindowSession是通過WindowManagerGlobal.getWindowSession()來獲取的玻孟,而跟蹤進(jìn)去發(fā)現(xiàn)發(fā)現(xiàn)這是一個(gè)Binder對(duì)象,進(jìn)行了和WindowManagerService之間的IPC鳍征。也就是說黍翎,Window的添加最后交給了WindowManagerService來進(jìn)行。

8.2.2 Window的刪除過程

Window的刪除的過程和添加類似艳丛,通過WindowManager.removeView() -> ViewRoot.die() -> WindowSession.remove()最終交付WindowManagerService來處理匣掸。
在這個(gè)過程中趟紊,會(huì)調(diào)用View的onDetachedFromWindow()回調(diào)。然后會(huì)調(diào)用WindowManager.doRemoveView()來將之前添加到列表中的View碰酝、ViewRoot霎匈、LayoutParams(8.2.1)給刪掉。

8.2.3 Window的更新過程

更新過程相較添加和刪除比較簡單送爸,WindowManager.updateViewLayout() -> ViewRoot.setLayoutParams() -> ViewRoot.scheduleTraversals()
最終會(huì)調(diào)用ViewRoot.performTraversals()方法铛嘱,在這里會(huì)對(duì)View進(jìn)行measure、layout袭厂、draw的步驟墨吓。同時(shí)會(huì)調(diào)用relayoutWindow -> WindowSession.relayoutWindow(),通過WindowSession來更新Window視圖纹磺,這個(gè)過程仍然是經(jīng)過IPC帖烘,由WindowManagerService來實(shí)現(xiàn)的。

8.2.* 小結(jié)

  • Window是一個(gè)抽象概念爽航,實(shí)際上是不存在的蚓让,通過View來體現(xiàn)。View不能單獨(dú)存在讥珍,而必須依托于Window历极;而Window的具體呈現(xiàn)方式則是通過View。
  • 一個(gè)Window對(duì)應(yīng)一個(gè)ViewRoot對(duì)應(yīng)一個(gè)View衷佃,ViewRoot是WindowManager和View之間的紐帶趟卸。而ViewRoot所代表的意義,則是這個(gè)Window的ViewTree的根節(jié)點(diǎn)氏义。
    WindowManager ----> ViewRoot --(IPC)-> WindowManagerService 這是操作Window的一般流程锄列。而所有關(guān)于Window的操作,最終都是在WindowManagerService中實(shí)現(xiàn)的惯悠。

8.3 Window的創(chuàng)建過程

8.3.1 Activity的Window

在第四章里面邻邮,我分析過Activity的啟動(dòng)流程:第4章 View的工作原理

當(dāng)系統(tǒng)將要啟動(dòng)一個(gè)Activity的時(shí)候克婶,會(huì)調(diào)用ActivityThread.performLaunchActivity()方法筒严,在這個(gè)方法中會(huì)創(chuàng)建Activity實(shí)例,并調(diào)用Activity的attach方法情萤。在attach方法中鸭蛙,會(huì)賦予Activity上下文,創(chuàng)建Window并將其和Activity綁定筋岛。Activity實(shí)現(xiàn)了Window.Callback接口娶视,所以Activity可以收到Window的若干回調(diào),比如onAttachedToWindow睁宰、onDetachedFromWindow肪获、dispatchTouchEvent等寝凌。

Window實(shí)例的創(chuàng)建,是通過PolicyManager的工廠方法makeNewWindow(Context)來進(jìn)行的孝赫。
當(dāng)我們?cè)贏ctivity調(diào)用setContentView方法時(shí)硫兰,會(huì)創(chuàng)建DecorView并將xml的布局加載進(jìn)content中。DecorView雖然已經(jīng)創(chuàng)建完畢寒锚,但是他還沒有被WindowManager添加到Window中劫映。Window的概念,更多表示的是一種抽象的功能集合刹前。在Window和DecorView都創(chuàng)建完畢之后泳赋,ActivityThread會(huì)調(diào)用handleResumeActivity方法->makeVisible方法,在其中會(huì)調(diào)用WindowManager.addView()喇喉,這就回到了8.2.1的流程了祖今,通過addView將DecorView添加到Window。
(有點(diǎn)吃驚拣技,書中的內(nèi)容和我之前在第四章中分析的幾乎一模一樣千诬,不過這也說明我沒有誤入歧途。)

8.3.2 Dialog的Window

Dialog和Activity創(chuàng)建Window的方式類似膏斤。

1徐绑、創(chuàng)建Window
2、初始化DecorView并將Dialog的視圖添加進(jìn)去
3莫辨、將DecorView添加到Window中

在Dialog被關(guān)閉時(shí)傲茄,他會(huì)通過WindowManager來移除DecorView。

8.3.3 Toast的Window

Toast也是基于Window來實(shí)現(xiàn)的沮榜,不過過程和Dialog不同盘榨。
總的來說,Toast是通過IPC過程調(diào)用NotificationManagerService來進(jìn)行顯示和隱藏的蟆融。

    public void show() {
        if (mNextView == null) {
            throw new RuntimeException("setView must have been called");
        }

        INotificationManager service = getService();
        String pkg = mContext.getOpPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;

        try {
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末草巡,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子型酥,更是在濱河造成了極大的恐慌山憨,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冕末,死亡現(xiàn)場(chǎng)離奇詭異萍歉,居然都是意外死亡侣颂,警方通過查閱死者的電腦和手機(jī)档桃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來憔晒,“玉大人藻肄,你說我怎么就攤上這事蔑舞。” “怎么了嘹屯?”我有些...
    開封第一講書人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵攻询,是天一觀的道長。 經(jīng)常有香客問我州弟,道長钧栖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任婆翔,我火速辦了婚禮拯杠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘啃奴。我一直安慰自己潭陪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開白布最蕾。 她就那樣靜靜地躺著依溯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瘟则。 梳的紋絲不亂的頭發(fā)上黎炉,一...
    開封第一講書人閱讀 51,488評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音醋拧,去河邊找鬼拜隧。 笑死,一個(gè)胖子當(dāng)著我的面吹牛趁仙,可吹牛的內(nèi)容都是我干的洪添。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼雀费,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼干奢!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起盏袄,我...
    開封第一講書人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤忿峻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后辕羽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逛尚,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年刁愿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了绰寞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖滤钱,靈堂內(nèi)的尸體忽然破棺而出觉壶,到底是詐尸還是另有隱情,我是刑警寧澤件缸,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布铜靶,位于F島的核電站,受9級(jí)特大地震影響他炊,放射性物質(zhì)發(fā)生泄漏争剿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一痊末、第九天 我趴在偏房一處隱蔽的房頂上張望秒梅。 院中可真熱鬧,春花似錦舌胶、人聲如沸捆蜀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辆它。三九已至,卻和暖如春履恩,著一層夾襖步出監(jiān)牢的瞬間锰茉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來泰國打工切心, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留飒筑,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓绽昏,卻偏偏與公主長得像协屡,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子全谤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

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