一臺 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
並不會獲得許可權,而是在應用的設定頁面,會出現一個是否允許顯示在其它應用的上層的選項。在程式設計時必須引導使用者手動開啟該開關才有效。
@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