一臺(tái) Android 手機(jī)屏幕上顯示的內(nèi)容就是由一個(gè)個(gè) Window 組合而成的。頂部的狀態(tài)欄是一個(gè) Window,底部的導(dǎo)航欄也是一個(gè) Window,中間自己的應(yīng)用顯示區(qū)域也是一塊大 Window,Toast、Dialog 也都對(duì)應(yīng)一個(gè)自己的 Window。而 Android 中對(duì)這些 Window 的管理是通過(guò) 一個(gè)框架的服務(wù),叫 WMS(WindowManagerService)。這些 Window 是如何被管理,然后如何呈現(xiàn)出一個(gè)完整的顯示的呢?下面我們就來(lái)簡(jiǎn)單說(shuō)說(shuō)這個(gè)過(guò)程吧。
簡(jiǎn)單了解幾個(gè)概念
Window:屏幕上的某塊顯示區(qū)域,用來(lái)承載 View。
WindowManagerService(WMS):Android 框架層的一個(gè)服務(wù)進(jìn)程,用來(lái)管理 Window。
Surface:對(duì)應(yīng)一塊屏幕緩沖區(qū),每個(gè) window 對(duì)應(yīng)一個(gè) Surface。
Canvas:提供了一系列繪圖接口,用來(lái)在 Surface 上進(jìn)行繪制操作。
SurfaceFlinger:Android 的一個(gè)服務(wù)進(jìn)程,負(fù)責(zé)管理 Surface。
WMS 和 SurfaceFlinger 在框架中的位置
如下圖,我們可以看下 SurfaceFlinger(對(duì)應(yīng)圖中 SurfaceManager)和 WindowManagerService 在 Android 框架中的。

WMS 和 Window
WMS 中除了可以增加、刪除外,還會(huì)通過(guò)一個(gè) Z-order 概念來(lái)管理 Window 的覆蓋關(guān)系,Z-order 大的會(huì)覆蓋在小的上面。
| Window | 層級(jí)(Z-order) |
|---|---|
| normal application windows | 1~99 |
| sub-windows | 1000~1999 |
| system-specific windows | 2000~2999 |
我們?cè)趧?chuàng)建一個(gè) Window 時(shí),會(huì)通過(guò) WindowManager.LayoutParams 的 type 參數(shù)來(lái)設(shè)置此 Window 的 Z-order 。目前已經(jīng)定義的 Z-order 值可以在 android.view.WindowManager 類中查找,比如狀態(tài)欄的層級(jí)為:
/**
* 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 管理下,我們知道當(dāng)前的屏幕有哪些顯示出來(lái)的 Window,哪些被隱藏的 Window,或哪些被半遮蓋的 Window。而因?yàn)槊總€(gè) Window 都對(duì)應(yīng)了一個(gè)屏幕緩沖區(qū)中的值(Surface)。 SurfaceFlinger 就會(huì)根據(jù)當(dāng)前的所有存在的 Surface 計(jì)算出一個(gè)適配當(dāng)前屏幕的緩沖區(qū)的值,然后把它渲染出來(lái)。
創(chuàng)建一個(gè)懸浮的 View
理解上面內(nèi)容后,我們就不難做出一個(gè)懸浮的 View 了。只要我們創(chuàng)建一個(gè) Z-Order 比較大的 Window 就 OK 了。但這種行為是一個(gè)敏感操作,比如某個(gè)惡意應(yīng)用創(chuàng)建了一個(gè)層級(jí)很高的透明 Window ,覆蓋在了其它可信應(yīng)用上,然后攔截點(diǎn)擊行為,引導(dǎo)用戶到一個(gè)惡意網(wǎng)站上。這被稱為 Tapjacking(觸屏劫持攻擊)。
所以在 Android 6.0 之前,如果要?jiǎng)?chuàng)建高層級(jí)的 Window,我們需要聲明 SYSTEM_ALERT_WINDOW 的權(quán)限,但這樣依然不安全,因?yàn)樵?6.0 之前的權(quán)限獲取,只是在應(yīng)用安裝時(shí)說(shuō)明下,大多數(shù)用戶可能并不在意。所以從 6.0 開(kāi)始,該操作被定為了敏感權(quán)限,直接聲明 SYSTEM_ALERT_WINDOW 并不會(huì)獲得權(quán)限,而是在應(yīng)用的設(shè)置頁(yè)面,會(huì)出現(xiàn)一個(gè)是否允許顯示在其它應(yīng)用的上層的選項(xiàng)。在編程時(shí)必須引導(dǎo)用戶手動(dòng)打開(kāi)該開(kāi)關(guān)才有效。

請(qǐng)求用戶開(kāi)啟此權(quán)限代碼如下:
@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
}
}
}
作者簡(jiǎn)介
彭濤(@彭濤me) 致力于讓技術(shù)變得易懂且有趣
個(gè)人博客:http://pengtao.me
簡(jiǎn)書(shū):http://www.itdecent.cn/u/f9246f41945e
GitHub:https://github.com/CPPAlien