Android 中的 Window

CPPAlien發表於2018-03-23

一臺 Android 手機螢幕上顯示的內容就是由一個個 Window 組合而成的。頂部的狀態列是一個 Window,底部的導航欄也是一個 Window,中間自己的應用顯示區域也是一塊大 Window,Toast、Dialog 也都對應一個自己的 Window。而 Android 中對這些 Window 的管理是通過 一個框架的服務,叫 WMS(WindowManagerService)。這些 Window 是如何被管理,然後如何呈現出一個完整的顯示的呢?下面我們就來簡單說說這個過程吧。

簡單瞭解幾個概念

Window:螢幕上的某塊顯示區域,用來承載 View。
WindowManagerService(WMS):Android 框架層的一個服務程式,用來管理 Window。
Surface:對應一塊螢幕緩衝區,每個 window 對應一個 Surface。
Canvas:提供了一系列繪圖介面,用來在 Surface 上進行繪製操作。
SurfaceFlinger:Android 的一個服務程式,負責管理 Surface。

WMS 和 SurfaceFlinger 在框架中的位置

如下圖,我們可以看下 SurfaceFlinger(對應圖中 SurfaceManager)和 WindowManagerService 在 Android 框架中的。

在框架中的位置

WMS 和 Window

WMS 中除了可以增加、刪除外,還會通過一個 Z-order 概念來管理 Window 的覆蓋關係,Z-order 大的會覆蓋在小的上面。

Window 層級(Z-order)
normal application windows 1~99
sub-windows 1000~1999
system-specific windows 2000~2999

我們在建立一個 Window 時,會通過 WindowManager.LayoutParams 的 type 引數來設定此 Window 的 Z-order 。目前已經定義的 Z-order 值可以在 android.view.WindowManager 類中查詢,比如狀態列的層級為:

/**
 * Window type: the status bar.  There can be only one status bar
 * window; it is placed at the top of the screen, and all other
 * windows are shifted down so they are below it.
 * In multiuser systems shows on all users' windows.
 */
public static final int TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW;
複製程式碼

SurfaceFlinger 和 Surface

在 WMS 管理下,我們知道當前的螢幕有哪些顯示出來的 Window,哪些被隱藏的 Window,或哪些被半遮蓋的 Window。而因為每個 Window 都對應了一個螢幕緩衝區中的值(Surface)。 SurfaceFlinger 就會根據當前的所有存在的 Surface 計算出一個適配當前螢幕的緩衝區的值,然後把它渲染出來。

建立一個懸浮的 View

理解上面內容後,我們就不難做出一個懸浮的 View 了。只要我們建立一個 Z-Order 比較大的 Window 就 OK 了。但這種行為是一個敏感操作,比如某個惡意應用建立了一個層級很高的透明 Window ,覆蓋在了其它可信應用上,然後攔截點選行為,引導使用者到一個惡意網站上。這被稱為 Tapjacking(觸屏劫持攻擊)。

所以在 Android 6.0 之前,如果要建立高層級的 Window,我們需要宣告 SYSTEM_ALERT_WINDOW 的許可權,但這樣依然不安全,因為在 6.0 之前的許可權獲取,只是在應用安裝時說明下,大多數使用者可能並不在意。所以從 6.0 開始,該操作被定為了敏感許可權,直接宣告 SYSTEM_ALERT_WINDOW 並不會獲得許可權,而是在應用的設定頁面,會出現一個是否允許顯示在其它應用的上層的選項。在程式設計時必須引導使用者手動開啟該開關才有效。

Android 中的 Window
請求使用者開啟此許可權程式碼如下:

@TargetApi(Build.VERSION_CODES.M)
public void checkDrawOverlayPermission() {
	if (!Settings.canDrawOverlays(this)) {
		Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, 
                             Uri.parse("package:" + getPackageName()));
		startActivityForResult(intent, REQUEST_CODE);
	}
}

@TargetApi(Build.VERSION_CODES.M)
@Override
protected void onActivityResult(int requestCode, int resultCode,  Intent data) {
	if (requestCode == REQUEST_CODE) {
		if (Settings.canDrawOverlays(this)) {
			// continue here - permission was granted
		}
	}
}
複製程式碼

作者簡介 彭濤(@彭濤me) 致力於讓技術變得易懂且有趣
個人部落格:http://pengtao.me
簡書:http://www.jianshu.com/u/f9246f41945e
GitHub:https://github.com/CPPAlien

相關文章