自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Window十二問(快扶我起來,我還能問)

系統(tǒng) Windows
Window是個概念性的東西,你看不到他,如果你能感知它的存在,那么就是通過View,所以View是Window的存在形式,有了View,你才感知到View外層有一個皇帝的新衣——window。

[[378290]]

前言

關(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?

  1. var windowParams: WindowManager.LayoutParams = WindowManager.LayoutParams() 
  2.     windowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 
  3.     windowParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG 
  4.     var btn = Button(this) 
  5.     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。

  1. // Window不需要獲取焦點,也不接受各種輸入事件。 
  2. public static final int FLAG_NOT_FOCUSABLE = 0x00000008; 
  3.  
  4. // @deprecated Use {@link android.R.attr#showWhenLocked} or 
  5. // {@link android.app.Activity#setShowWhenLocked(boolean)} instead to prevent an 
  6. // unintentional double life-cycle event. 
  7.  
  8.  
  9. // 窗口可以在鎖屏的 Window 之上顯示 
  10. 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的類,待會會看到:

  1. public Activity handleLaunchActivity(ActivityClientRecord r, 
  2.                                          PendingTransactionActions pendingActions, Intent customIntent) { 
  3.         //... 
  4.         WindowManagerGlobal.initialize(); 
  5.         //... 
  6.         final Activity a = performLaunchActivity(r, customIntent); 
  7.         //... 
  8.         return a; 
  9.     } 

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

  1. final void attach() { 
  2.      //初始化PhoneWindow 
  3.      mWindow = new PhoneWindow(this, window, activityConfigCallback); 
  4.      mWindow.setWindowControllerCallback(mWindowControllerCallback); 
  5.      mWindow.setCallback(this); 
  6.  
  7.      //和WindowManager關(guān)聯(lián) 
  8.      mWindow.setWindowManager( 
  9.              (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), 
  10.              mToken, mComponent.flattenToString(), 
  11.              (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); 
  12.  
  13.      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方法中傳入移動的坐標即可。

  1. btn.setOnTouchListener { v, event -> 
  2.             val index = event.findPointerIndex(0) 
  3.             when (event.action) { 
  4.                 ACTION_MOVE -> { 
  5.                     windowParams.x = event.getRawX(index).toInt() 
  6.                     windowParams.y = event.getRawY(index).toInt() 
  7.                     windowManager.updateViewLayout(btn, windowParams) 
  8.                 } 
  9.                 else -> { 
  10.                 } 
  11.             } 
  12.             false 
  13.  
  14.         } 

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

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

1)addView

  1. //WindowManagerGlobal.java 
  2. public void addView(View view, ViewGroup.LayoutParams params, 
  3.             Display display, Window parentWindow) { 
  4.  
  5.         if (parentWindow != null) { 
  6.             parentWindow.adjustLayoutParamsForSubWindow(wparams); 
  7.         } 
  8.              
  9.             ViewRootImpl root; 
  10.             View panelParentView = null
  11.  
  12.             root = new ViewRootImpl(view.getContext(), display); 
  13.             view.setLayoutParams(wparams); 
  14.  
  15.             mViews.add(view); 
  16.             mRoots.add(root); 
  17.             mParams.add(wparams); 
  18.  
  19.             try { 
  20.                 root.setView(view, wparams, panelParentView); 
  21.             }  
  22.         } 
  23.     } 
  • 這里可以看到,創(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ù)看看。
  1. final IWindowSession mWindowSession; 
  2.  
  3. mWindowSession = WindowManagerGlobal.getWindowSession(); 
  4.  
  5. public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { 
  6.     // 
  7.     requestLayout(); 
  8.  
  9.     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到屏幕的邏輯,具體就不分析了,下次說到屏幕繪制的時候再細談。

2)updateViewLayout

  1. public void updateViewLayout(View view, ViewGroup.LayoutParams params) { 
  2. /... 
  3.        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params; 
  4.  
  5.        view.setLayoutParams(wparams); 
  6.  
  7.        synchronized (mLock) { 
  8.            int index = findViewLocked(viewtrue); 
  9.            ViewRootImpl root = mRoots.get(index); 
  10.            mParams.remove(index); 
  11.            mParams.add(index, wparams); 
  12.            root.setLayoutParams(wparams, false); 
  13.        } 
  14.    } 

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

3)removeView

  1. public void removeView(View view, boolean immediate) { 
  2.         if (view == null) { 
  3.             throw new IllegalArgumentException("view must not be null"); 
  4.         } 
  5.  
  6.         synchronized (mLock) { 
  7.             int index = findViewLocked(viewtrue); 
  8.             View curView = mRoots.get(index).getView(); 
  9.             removeViewLocked(index, immediate); 
  10.             if (curView == view) { 
  11.                 return
  12.             } 
  13.  
  14.             throw new IllegalStateException("Calling with view " + view 
  15.                     + " but the ViewAncestor is attached to " + curView); 
  16.         } 
  17.     } 
  18.      
  19.      
  20.     private void removeViewLocked(int index, boolean immediate) { 
  21.         ViewRootImpl root = mRoots.get(index); 
  22.         View view = root.getView(); 
  23.  
  24.         if (view != null) { 
  25.             InputMethodManager imm = view.getContext().getSystemService(InputMethodManager.class); 
  26.             if (imm != null) { 
  27.                 imm.windowDismissed(mViews.get(index).getWindowToken()); 
  28.             } 
  29.         } 
  30.         boolean deferred = root.die(immediate); 
  31.         if (view != null) { 
  32.             view.assignParent(null); 
  33.             if (deferred) { 
  34.                 mDyingViews.add(view); 
  35.             } 
  36.         } 
  37.     }     

該方法中,通過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 等等。

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

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

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

  1. unable to add window --token null 

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

  1. //Window.java 
  2.     void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) { 
  3.         if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && 
  4.                 wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { 
  5.             //子Window 
  6.             if (wp.token == null) { 
  7.                 View decor = peekDecorView(); 
  8.                 if (decor != null) { 
  9.                     wp.token = decor.getWindowToken(); 
  10.                 } 
  11.             } 
  12.         } else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW && 
  13.                 wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) { 
  14.             //系統(tǒng)Window 
  15.         } else { 
  16.             //應(yīng)用Window 
  17.             if (wp.token == null) { 
  18.                 wp.token = mContainer == null ? mAppToken : mContainer.mAppToken; 
  19.             } 
  20.              
  21.         } 
  22.     } 

上述代碼分別代表了三個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:
  1. //檢查權(quán)限 
  2. if (!Settings.canDrawOverlays(this)) { 
  3.     val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION) 
  4.     intent.data = Uri.parse("package:$packageName"
  5.     startActivityForResult(intent, 0) 
  6.  
  7. dialog.window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG) 
  8.  
  9. <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)容了:

  1. //ViewRootImpl.java 
  2.  private int processPointerEvent(QueuedInputEvent q) { 
  3.             final MotionEvent event = (MotionEvent)q.mEvent; 
  4.             ... 
  5.             //mView分發(fā)Touch事件,mView就是DecorView 
  6.             boolean handled = mView.dispatchPointerEvent(event); 
  7.             ... 
  8.         } 
  9.  
  10. //DecorView.java 
  11.     public final boolean dispatchPointerEvent(MotionEvent event) { 
  12.         if (event.isTouchEvent()) { 
  13.             //分發(fā)Touch事件 
  14.             return dispatchTouchEvent(event); 
  15.         } else { 
  16.             return dispatchGenericMotionEvent(event); 
  17.         } 
  18.     } 
  19.  
  20.     @Override 
  21.     public boolean dispatchTouchEvent(MotionEvent ev) { 
  22.         //cb其實就是對應(yīng)的Activity 
  23.         final Window.Callback cb = mWindow.getCallback(); 
  24.         return cb != null && !mWindow.isDestroyed() && mFeatureId < 0 
  25.                 ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev); 
  26.     } 
  27.  
  28.  
  29. //Activity.java 
  30.     public boolean dispatchTouchEvent(MotionEvent ev) { 
  31.         if (ev.getAction() == MotionEvent.ACTION_DOWN) { 
  32.             onUserInteraction(); 
  33.         } 
  34.         if (getWindow().superDispatchTouchEvent(ev)) { 
  35.             return true
  36.         } 
  37.         return onTouchEvent(ev); 
  38.     } 
  39.  
  40. //PhoneWindow.java 
  41.     @Override 
  42.     public boolean superDispatchTouchEvent(MotionEvent event) { 
  43.         return mDecor.superDispatchTouchEvent(event); 
  44.     } 
  45.  
  46. //DecorView.java 
  47.     public boolean superDispatchTouchEvent(MotionEvent event) { 
  48.         return super.dispatchTouchEvent(event); 
  49.     }     

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

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

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

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

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

ViewRootImpl并不知道有Activity這種東西存在,它只是持有了DecorView。所以先傳給了DecorView,而DecorView知道有AC,所以傳給了AC。

Activity也不知道有DecorView,它只是持有PhoneWindow,所以這么一段調(diào)用鏈就形成了。

參考

《Android開發(fā)藝術(shù)探索》 《Android進階解密》 https://mp.weixin.qq.com/s/wy9V4wXUoEFZ6ekzuLJySQ https://blog.csdn.net/weixin_43766753/article/details/108350589 https://wanandroid.com/wenda/show/12119

 

責(zé)任編輯:武曉燕 來源: 碼上積木
相關(guān)推薦

2022-04-01 08:37:07

SpringAPI前端

2021-07-21 09:15:27

MySQL數(shù)據(jù)庫面試

2020-04-14 08:40:50

碼農(nóng)bug編程

2021-08-27 14:14:39

ThreadLocal源碼操作

2022-01-24 14:08:16

Redis面試命令

2019-12-19 09:23:45

Java多線程數(shù)據(jù)

2021-07-30 16:16:54

網(wǎng)絡(luò)面試TCP

2022-11-04 08:47:52

底層算法數(shù)據(jù)

2011-10-24 22:17:56

SQL ServerDBA

2021-03-24 10:25:24

優(yōu)化VUE性能

2021-02-02 08:21:28

網(wǎng)絡(luò)面試通信

2025-02-03 11:27:59

2020-09-02 07:00:42

ZooKeeper分布式

2009-03-19 19:04:44

2022-02-16 14:20:46

HashTableHashMap線程安全

2009-05-04 16:09:04

2023-03-01 20:18:05

ChatGPTPython

2012-05-29 10:18:05

組策略

2024-03-13 13:39:21

2024-09-05 13:02:41

點贊
收藏

51CTO技術(shù)棧公眾號