Android懸浮窗TYPE_TOAST小結: 原始碼分析

發表於2016-02-19

前言

Android無需許可權顯示懸浮窗, 兼談逆向分析app這篇文章閱讀量很大, 這篇文章是通過逆向分析UC瀏覽器的實現和相容性處理來得到一個懸浮窗的實現小技巧, 但有很多問題沒有弄明白, 比如為什麼在API 18及以下TYPE_TOAST的懸浮窗無法接受觸控事件, 為什麼使用TYPE_TOAST就不需要許可權.

期間@廖祜秋liaohuqiu_秋百萬和我有較多探討, 原文貼的一個demo android-UCToast也是他做的, 他也有寫Android 懸浮窗的小結. 這幾篇關於懸浮窗的文章, 是我和他共同探索的結果, 非常感謝.

思路

老實說一開始我是想看看整個事件的傳播過程, 從EventHub開始, 到View.onTouchEvent, 想看看Android系統內事件分發, 不過由於絕大部分程式碼在Native層, 我並沒有搞清楚.

其實要想知道原因很簡單, 只要grep一下TYPE_TOAST, 把每個用到的地方看一看, 自然就知道了, 但是恰好週末我手上沒有原始碼, 只能在grepcode上面一個一個的查, 所以也花了不少時間.

正文

還是從最簡單的地方開始, 我們呼叫了WindowManager.addView, WindowManager是個介面, 我們使用的是他的實現類WindowManagerImpl, 看看它的addView方法:

mGlobalWindowManagerGlobal的例項, 再看看WindowManagerGlobal.addView:

程式碼中建立了一個ViewRootImpl, 呼叫了它的setView, 將我們要新增的view傳入. 繼續看ViewRootImpl.setView:

對我們的分析來說最關鍵的程式碼是

mWindowSession的型別是IWindowSession, mWindow的型別是IWindow.Stub, 這句程式碼就是利用AIDL進行IPC, 實際被呼叫的是Session.addToDisplay:

mServiceWindowManagerService, 繼續往下跟:

mPolicy是標記為final的成員變數:

繼續看PolicyManager.makeNewWindowManager:

這裡sPolicy是com.android.internal.policy.impl.Policy物件, 再看看它的makeNewWindowManager方法返回的是什麼:

現在我們知道mPolicy實際上是PhoneWindowManager, 那麼

實際呼叫的程式碼是:

我擷取的是4.4_r1的程式碼, 我們最關心的部分其實一直沒有變, 那就是TYPE_TOAST根本沒有做許可權檢查, 直接break出去了, 最後返回WindowManagerGlobal.ADD_OKAY.

不需要許可權顯示懸浮窗的原因已經找到了, 接著剛才addWindow方法的分析, 繼續看下面一句:

也就是PhoneWindowManager.adjustWindowParamsLw, 注意這裡我給出了三個版本的實現, 一個是2.0到2.3.7實現的版本, 一個是4.0.1到4.3.1實現的版本, 一個是4.4實現的版本:

grepcode上沒有3.x的程式碼, 我也沒查具體是什麼, 沒必要考慮3.x.
可以看到, 在4.0.1以前, 當我們使用TYPE_TOAST, Android會偷偷給我們加上FLAG_NOT_FOCUSABLEFLAG_NOT_TOUCHABLE, 4.0.1開始, 會額外再去掉FLAG_WATCH_OUTSIDE_TOUCH, 這樣真的是什麼事件都沒了. 而4.4開始, TYPE_TOAST被移除了, 所以從4.4開始, 使用TYPE_TOAST的同時還可以接收觸控事件和按鍵事件了, 而4.4以前只能顯示出來, 不能互動.

API level 18及以下使用TYPE_TOAST無法接收觸控事件的原因也找到了.

尾聲

原文發的時候很多事情沒搞清楚, 後來文章編輯了十幾次, 加上這篇文章, 基本上把所有的疑問都搞明白了. 嗯, 關於這個神奇的懸浮窗的事情應該到這裡就結束了.

本人水平有限, 如有錯誤, 歡迎指正, 以免誤導他人

相關文章