Android 中的 Window

一臺(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容