溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

關(guān)于Window的問題有哪些

發(fā)布時間:2021-10-19 17:29:45 來源:億速云 閱讀:157 作者:iii 欄目:編程語言

本篇內(nèi)容主要講解“關(guān)于Window的問題有哪些”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“關(guān)于Window的問題有哪些”吧!

如果你遇到這些問題

  • Window是什么?和View的關(guān)系?
  • WindowManager是什么?和WMS的關(guān)系?
  • 怎么添加一個Window?
  • Window怎樣可以顯示到鎖屏界面
  • Window三種類型都存在的情況下,顯示層級是怎樣。
  • Window就是指PhoneWindow嗎?
  • PhoneWindow什么時候被創(chuàng)建的?
  • 要實現(xiàn)可以拖動的View該怎么做?
  • Window的添加、刪除和更新過程。
  • Activity、PhoneWindow、DecorView、ViewRootImpl 的關(guān)系?
  • Window中的token是什么,有什么用?
  • Application中可以直接彈出Dialog嗎?
  • 關(guān)于事件分發(fā),事件到底是先到DecorView還是先到Window的? 

Window是什么

窗口。你可以理解為手機上的整個畫面,所有的視圖都是通過Window呈現(xiàn)的,比如Activity、dialog都是附加在Window上的。Window類的唯一實現(xiàn)是PhoneWindow,這個名字就更加好記了吧,手機窗口唄。

Window到底在哪里呢?我們看到的View是Window嗎?是也不是。

  • 如果說的只是Window概念的話,那可以說是的,View就是Window的存在形式,Window管理著View。

  • 如果說是Window類的話,那確實不是View,唯一實現(xiàn)類PhoneWindow管理著當(dāng)前界面上的View,包括根布局——DecorView,和其他子view的添加刪除等等。

不知道你暈沒有,我總結(jié)下,Window是個概念性的東西,你看不到他,如果你能感知它的存在,那么就是通過View,所以View是Window的存在形式,有了View,你才感知到View外層有一個皇帝的新衣——window。 

WindowManager是什么?和WMS的關(guān)系?

WindowManager就是用來管理Window的,實現(xiàn)類為WindowManagerImpl,實際工作會委托給WindowManagerGlobal類中完成。

而具體的Window操作,WM會通過Binder告訴WMS,WMS做最后的真正操作Window的工作,會為這個Window分配Surface,并繪制到屏幕上。 

怎么添加一個Window?

    var windowParams: WindowManager.LayoutParams = WindowManager.LayoutParams()
    windowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
    windowParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG
    var btn = Button(this)
    windowManager.addView(btn, windowParams)
 

簡單貼了下代碼,加了一個Button。

有的朋友可能會疑惑了,這明明是個Button,是個View啊,咋成了Window?

剛才說過了,View是Window的表現(xiàn)形式,在實際實現(xiàn)中,添加window其實就是添加了一個你看不到的window,并且里面有View才能讓你感覺得到這個是一個Window。

所以通過windowManager添加的View其實就是添加Window的過程。

這其中還有兩個比較重要的屬性:flags和type,下面會依次說到。 

Window怎樣可以顯示到鎖屏界面

Window的flag可以控制Window的顯示特性,也就是該怎么顯示、touch事件處理、與設(shè)備的關(guān)系、等等。所以這里問的鎖屏界面顯示也是其中的一種Flag。


// Window不需要獲取焦點,也不接受各種輸入事件。
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;

// @deprecated Use {@link android.R.attr#showWhenLocked} or
// {@link android.app.Activity#setShowWhenLocked(boolean)} instead to prevent an
// unintentional double life-cycle event.


// 窗口可以在鎖屏的 Window 之上顯示
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
   

Window三種類型都存在的情況下,顯示層級是怎樣。

Type表示W(wǎng)indow的類型,一共三種:

  • 應(yīng)用Window。對應(yīng)著一個Activity,Window層級為1~99,在視圖最下層。
  • 子Window。不能單獨存在,需要附屬在特定的父Window之中(如Dialog就是子Window),Window層級為1000~1999。
  • 系統(tǒng)Window。需要聲明權(quán)限才能創(chuàng)建的Window,比如Toast和系統(tǒng)狀態(tài)欄,Window層級為2000-2999,處在視圖最上層。

可以看到,區(qū)別就是有個Window層級(z-ordered),層級高的能覆蓋住層級低的,離用戶更近。

 

Window就是指PhoneWindow嗎?

如果有人問我這個問題,我肯定心里要大大的疑惑了????。

可不就是PhoneWindow嗎?都唯一實現(xiàn)類了,凈問些奇怪問題。

但是面試的時候遇到這種問題總要答???這時候就要扯出Window的概念了。

  • 如果指的Window類,那么PhoneWindow作為唯一實現(xiàn)類,一般指的就是PhoneWindow。

  • 如果指的Window這個概念,那肯定不是指PhoneWindow,而是存在于界面上真實的View。當(dāng)然也不是所有的View都是Window,而是通過WindowManager添加到屏幕的view才是Window,所以PopupWindow是Window,上述問題中添加的單個View也是Window。 

PhoneWindow什么時候被創(chuàng)建的?

熟悉Activity啟動流程的朋友應(yīng)該知道,啟動過程會執(zhí)行到ActivityThread的handleLaunchActivity方法,這里初始化了WindowManagerGlobal,也就是WindowManager實際操作Window的類,待會會看到:

 public Activity handleLaunchActivity(ActivityClientRecord r,
                                         PendingTransactionActions pendingActions, Intent customIntent) {
        //...
        WindowManagerGlobal.initialize();
        //...
        final Activity a = performLaunchActivity(r, customIntent);
        //...
        return a;
    }
 

然后會執(zhí)行到performLaunchActivity中創(chuàng)建Activity,并調(diào)用attach方法進行一些數(shù)據(jù)的初始化(偽代碼):

    final void attach() {
        //初始化PhoneWindow
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        mWindow.setCallback(this);

        //和WindowManager關(guān)聯(lián)
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);

        mWindowManager = mWindow.getWindowManager();
}
 

可以看到,在Activity的attach方法中,創(chuàng)建了PhoneWindow,并且設(shè)置了callback,windowManager

這里的callback待會會說到,跟事件分發(fā)有關(guān)系,可以說是當(dāng)前Activity和PhoneWindow建立聯(lián)系。

 

要實現(xiàn)可以拖動的View該怎么做?

還是接著剛才的btn例子,如果要修改btn的位置,使用updateViewLayout即可,然后在ontouch方法中傳入移動的坐標(biāo)即可。

        btn.setOnTouchListener { v, event ->
            val index = event.findPointerIndex(0)
            when (event.action) {
                ACTION_MOVE -> {
                    windowParams.x = event.getRawX(index).toInt()
                    windowParams.y = event.getRawY(index).toInt()
                    windowManager.updateViewLayout(btn, windowParams)
                }
                else -> {
                }
            }
            false

        }
   

Window的添加、刪除和更新過程。

Window的操作都是通過WindowManager來完成的,而WindowManager是一個接口,他的實現(xiàn)類是WindowManagerImpl,并且全部交給WindowManagerGlobal來處理。下面具體說下addView,updateViewLayout,和removeView。

 

1)addView

//WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {

        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        }
            
            ViewRootImpl root;
            View panelParentView = null;

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

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

            try {
                root.setView(view, wparams, panelParentView);
            } 
        }
    }
 
  • 這里可以看到,創(chuàng)建了一個ViewRootImpl實例,這樣就說明了每個Window都對應(yīng)著一個ViewRootImpl。
  • 然后通過add方法修改了     WindowManagerGlobal中的一些參數(shù),比如mViews—存儲了所有Window所對應(yīng)的View,mRoots——所有Window所對應(yīng)的ViewRootImpl,mParams—所有Window對應(yīng)的布局參數(shù)。
  • 最后調(diào)用了ViewRootImpl的setView方法,繼續(xù)看看。
final IWindowSession mWindowSession;

mWindowSession = WindowManagerGlobal.getWindowSession();

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    //
    requestLayout();

    res = mWindowSession.addToDisplay(mWindow,);
}
 

setView方法主要完成了兩件事,一是通過requestLayout方法完成異步刷新界面的請求,進行完整的view繪制流程。其次,會通過IWindowSession進行一次IPC調(diào)用,交給到WMS來實現(xiàn)Window的添加。

其中mWindowSession是一個Binder對象,相當(dāng)于在客戶端的代理類,對應(yīng)的服務(wù)端的實現(xiàn)為Session,而Session就是運行在SystemServer進程中,具體就是處于WMS服務(wù)中,最終就會調(diào)用到這個Session的addToDisplay方法,從方法名就可以猜到這個方法就是具體添加Window到屏幕的邏輯,具體就不分析了,下次說到屏幕繪制的時候再細(xì)談。

 

2)updateViewLayout

 public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
//...
        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);
        }
    }
 

這里更新了WindowManager.LayoutParamsViewRootImpl.LayoutParams,然后在ViewRootImpl內(nèi)部同樣會重新對View進行繪制,最后通過IPC通信,調(diào)用到WMS的relayoutWindow完成更新。 

3)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);
        }
    }
    
    
    private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();

        if (view != null) {
            InputMethodManager imm = view.getContext().getSystemService(InputMethodManager.class);
            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);
            }
        }
    }    
 

該方法中,通過view找到mRoots中的對應(yīng)索引,然后同樣走到ViewRootImpl中進行View刪除工作,通過die方法,最終走到dispatchDetachedFromWindow()方法中,主要做了以下幾件事:

  • 回調(diào)onDetachedFromeWindow。
  • 垃圾回收相關(guān)操作;
  • 通過Session的remove()在WMS中刪除Window;
  • 通過Choreographer移除監(jiān)聽器 

Activity、PhoneWindow、DecorView、ViewRootImpl 的關(guān)系?

看完上面的流程,我們再來理理這四個小伙伴之間的關(guān)系:

  • PhoneWindow 其實是 Window 的唯一子類,是     Activity 和 View 交互系統(tǒng)的中間層,用來管理View的,并且在Window創(chuàng)建(添加)的時候就新建了ViewRootImpl實例。
  • DecorView 是整個 View 層級的最頂層,     ViewRootImpl是DecorView 的parent,但是他并不是一個真正的 View,只是繼承了ViewParent接口,用來掌管View的各種事件,包括requestLayout、invalidate、dispatchInputEvent 等等。
關(guān)于Window的問題有哪些  
 

Window中的token是什么,有什么用?

token?又是個啥呢?剛才window操作過程中也沒出現(xiàn)啊。

token其實大家應(yīng)該工作中會發(fā)現(xiàn)一點蹤跡,比如application的上下文去創(chuàng)建dialog的時候,就會報錯:

unable to add window --token null 

所以這個token跟window操作是有關(guān)系的,翻到剛才的addview方法中,還有個細(xì)節(jié)我們沒說到,就是adjustLayoutParamsForSubWindow方法。

//Window.java
    void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
        if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
            //子Window
            if (wp.token == null) {
                View decor = peekDecorView();
                if (decor != null) {
                    wp.token = decor.getWindowToken();
                }
            }
        } else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&
                wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
            //系統(tǒng)Window
        } else {
            //應(yīng)用Window
            if (wp.token == null) {
                wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
            }
            
        }
    }
 

上述代碼分別代表了三個Window的類型:

  • 子Window。需要從decorview中拿到token。
  • 系統(tǒng)Window。不需要token。
  • 應(yīng)用Window。直接拿mAppToken,mAppToken是在setWindowManager方法中傳進來的,也就是新建Window的時候就帶進來了token。

然后在WMS中的addWindow方法會驗證這個token,下次說到WMS的時候再看看。

所以這個token就是用來驗證是否能夠添加Window,可以理解為權(quán)限驗證,其實也就是為了防止開發(fā)者亂用context創(chuàng)建window。

擁有token的context(比如Activity)就可以操作Window。沒有token的上下文(比如Application)就不允許直接添加Window到屏幕(除了系統(tǒng)Window)。 

Application中可以直接彈出Dialog嗎?

這個問題其實跟上述問題相關(guān):

  • 如果直接使用     Application的上下文是不能創(chuàng)建Window的,而Dialog的Window等級屬于子Window,必須依附與其他的父Window,所以必須傳入Activity這種有window的上下文。
  • 那有沒有其他辦法可以在     Application中彈出dialog呢?有,改成系統(tǒng)級Window:
//檢查權(quán)限
if (!Settings.canDrawOverlays(this)) {
    val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
    intent.data = Uri.parse("package:$packageName")
    startActivityForResult(intent, 0)
}

dialog.window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG)

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
 
  • 另外還有一種辦法,在Application類中,可以通過     registerActivityLifecycleCallbacks監(jiān)聽Activity生命周期,不過這種辦法也是傳入了Activity的context,只不過在Application類中完成這個工作。 

關(guān)于事件分發(fā),事件到底是先到DecorView還是先到Window的?

經(jīng)過上述一系列問題,是不是對Window印象又深了點呢?最后再看一個問題,這個是wanandroid論壇上看到的,

這里的window可以理解為PhoneWindow,其實這道題就是問事件分發(fā)在Activity、DecorView、PhoneWindow中的順序。

當(dāng)屏幕被觸摸,首先會通過硬件產(chǎn)生觸摸事件傳入內(nèi)核,然后走到FrameWork層(具體流程感興趣的可以看看參考鏈接),最后經(jīng)過一系列事件處理到達ViewRootImpl的processPointerEvent方法,接下來就是我們要分析的內(nèi)容了:

//ViewRootImpl.java
 private int processPointerEvent(QueuedInputEvent q) {
            final MotionEvent event = (MotionEvent)q.mEvent;
            ...
            //mView分發(fā)Touch事件,mView就是DecorView
            boolean handled = mView.dispatchPointerEvent(event);
            ...
        }

//DecorView.java
    public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            //分發(fā)Touch事件
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //cb其實就是對應(yīng)的Activity
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }


//Activity.java
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

//PhoneWindow.java
    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

//DecorView.java
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }    

 

事件的分發(fā)流程就比較清楚了:

ViewRootImpl——>DecorView——>Activity——>PhoneWindow——>DecorView——>ViewGroup

(這其中就用到了getCallback參數(shù),也就是之前addView中傳入的callback,也就是Activity本身)

但是這個流程確實有些奇怪,為什么繞來繞去的呢,光DecorView就走了兩遍。

參考鏈接中的說法我還是比較認(rèn)同的,主要原因就是解耦。

  • ViewRootImpl并不知道有Activity這種東西存在,它只是持有了DecorView。所以先傳給了DecorView,而DecorView知道有AC,所以傳給了AC。
  • Activity也不知道有DecorView,它只是持有PhoneWindow,所以這么一段調(diào)用鏈就形成了。

到此,相信大家對“關(guān)于Window的問題有哪些”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI