小米 TYPE_TOAST 懸浮窗無效的原因
Android中的懸浮窗顯示是一個非常棘手的問題,網上已經有很多解決方案了,大致歸為下面兩類:
設定
WindowManager.LayoutParams.type = TYPE_SYSTEM_ALERT
,並引導使用者開啟懸浮窗許可權。
這種方法主要的難點在於引導使用者跳轉許可權設定頁面,由於各廠商定製的問題,需要針對許多裝置進行對應的適配,目前已有大神總結了部分機型的適配問題,詳情參見:設定
WindowManager.LayoutParams.type = TYPE_TOAST
,可以繞過系統許可權檢查,但是這種方法的問題在於:
懸浮窗在API 18及以下的系統無法接收Touch事件。
API 25中無法同時存在兩個Toast型別的懸浮窗,API 25以上系統直接禁止使用者使用TYPE_TOAST建立懸浮窗。
MiUI 8中修改了WindowManager中的程式碼,導致在該系統上任然無法展示出懸浮窗。
本文就針對MiUI 8上的問題進行分析。
原因分析
在實際的使用過程中我們發現,系統的Toast可以正常顯示,包括自定義的Toast,而透過WindowManger.addView
新增的TYPE_TOAST的無法顯示。
我們知道系統Toast實際也是透過呼叫WindowManger.addView
建立的,那麼我們可以試著模擬系統建立Toast時使用的引數,下面是在MiUI 8上建立系統Toast時的LayoutParams
。
系統Toast
其中flags
的值(主要還是為了遮蔽事件)
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
token
的值為WindowManager.LayoutParams.TYPE_TOAST
。
現在完全按照上面系統設定的值模擬一個LayoutParams
,並嘗試建立的懸浮窗。
結果仍然失敗了。
那麼從WindowManager.addView()
開始一步一步追蹤,看看在MiUI上到底發生了什麼。
addView
在向ViewRootImpl
設定View的過程中呼叫了ViewRootImplInjector
的transformWindowType
函式,在這之後LayoutParams
的type
欄位值就發生了改變,從 2005 變成了 2003,而 2003 這個值所對應的常量為WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
,在後續WindowManagerService.addWindow()
執行的時候又會進行懸浮窗的鑑權,所以自然會顯示失敗。
也就是說,我們繞了半天,又回到了原點。。。
想看看在ViewRootImplInjector
中發生了什麼,可是在系統 API 中搜尋不到這個類的存在,懷疑是 MiUI 自定義的,GitHub 上搜了一下,發現了 ViewRootImpInjector.smali 檔案,用Smali2Java轉化一下,得到如下程式碼:
package android.view;import miui.os.Build;import java.util.ArrayList;import android.util.Log;public class ViewRootImplInjector { private static final String TAG = "ViewRootImpl"; public static void transformWindowType(View p1, WindowManager.LayoutParams p2) { if(Build.IS_INTERNATIONAL_BUILD) { return; } if(p2.type == 0x7d5) { ArrayList stackTrace = new ArrayList(); stackTrace.add("android.view.ViewRootImplInjector.transformWindowType"); stackTrace.add("android.view.ViewRootImpl.setView"); stackTrace.add("android.view.WindowManagerGlobal.addView"); stackTrace.add("android.view.WindowManagerImpl.addView"); stackTrace.add("android.widget.Toast$TN.handleShow"); try { StackTraceElement[] stackTraceElements = new Exception().getStackTrace(); if(stackTraceElements.length > stackTrace.size()) { for(int i = 0x0; i可以看到程式碼中判斷
LayoutParams.type
等於0x7d5(TYPE_TOAST)時,獲取了當前方法執行棧的資訊,如果當前方法不是按照
android.widget.Toast$TN.handleShow ->
android.view.WindowManagerImpl.addView ->
android.view.WindowManagerGlobal.addView ->
android.view.ViewRootImpl.setView ->
android.view.ViewRootImplInjector.transformWindowType
這個流程執行的話,就將LayoutParams.type
的值設為0x7d3(TYPE_SYSTEM_ALERT),這個流程正是系統建立Toast的執行流程,所以這裡將所有非系統呼叫的TYPE_TOAST
型別都強制轉化成了TYPE_SYSTEM_ALERT
。解決方案
我們看到在
transformWindowType
方法的開頭,MiUI又設定了一個開關Build.IS_INTERNATIONAL_BUILD
,這個應該是MiUI國際版的標識,只要我們將這個值設為true
,那麼後續的程式碼就不再執行了,似乎問題就迎刃而解了。public static void addViewToWindow(WindowManager wm, View view, WindowManager.LayoutParams params) { beforeAddToWindow(params); wm.addView(view, params); afterAddToWindow(); }private static void beforeAddToWindow(WindowManager.LayoutParams params) { setMiUI_International(true); setMeizuParams(params); }private static void afterAddToWindow() { setMiUI_International(false); }private static void setMiUI_International(boolean flag) { try { Class BuildForMi = Class.forName("miui.os.Build"); Field isInternational = BuildForMi.getDeclaredField("IS_INTERNATIONAL_BUILD"); isInternational.setAccessible(true); isInternational.setBoolean(null, flag); } catch (Exception e) { e.printStackTrace(); } }透過反射,在addView執行之前修改
IS_INTERNATIONAL_BUILD
為true
,之後再將其還原。說明
在我的小米測試機上使用該方案,確實能夠展示出懸浮窗,但是該方案未經過大量的相容性測試,所以其他機型、系統上表現如何不得而知,但是可以用類似的方法去分析。
更新
適配魅族Pro 6s
private static void setMeizuParams(WindowManager.LayoutParams params) { try { Class MeizuParamsClass = Class.forName("android.view.MeizuLayoutParams"); Field flagField = MeizuParamsClass.getDeclaredField("flags"); flagField.setAccessible(true); Object MeizuParams = MeizuParamsClass.newInstance(); flagField.setInt(MeizuParams, 0x40); Field mzParamsField = params.getClass().getField("meizuParams"); mzParamsField.set(params, MeizuParams); } catch (Exception e) { e.printStackTrace(); } }
作者:Joe_H
連結:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4650/viewspace-2809973/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Android 懸浮窗Android
- 懸浮窗的一種實現 | Android懸浮窗Window應用Android
- Android懸浮窗的學習Android
- Android 懸浮窗 System Alert WindowAndroid
- 非侵入式無許可權應用內懸浮窗的實現
- QPM 之懸浮窗設定資訊
- QPM 之懸浮窗助力效能優化優化
- Android仿微信文章懸浮窗效果Android
- Android 攝像頭預覽懸浮窗Android
- 懸浮窗開發設計實踐
- 【轉載】使用WindowManage實現Android懸浮窗Android
- Android 輔助許可權與懸浮窗Android
- UICollectionView SelectItem方法無效的原因UIView
- 百度地圖新增懸浮窗搜尋功能地圖
- 直播原始碼,懸浮窗滾動漸變色效果原始碼
- 記一次懸浮窗的上線以及坑點總結
- 下沉式通知的一種實現 | Android懸浮窗Window應用Android
- 如何獲取Vivo系統的懸浮窗許可權狀態
- Android中的懸浮框Android
- Android懸浮框的實現Android
- 無效SSL證書有哪些原因
- ALaunch 0.8 := 懸浮提示
- vivo系統無許可權開啟懸浮框的問題
- 導致SSL證書無效的原因有哪些?
- ssl證書無效的原因是什麼?
- Android:會呼吸的懸浮氣泡Android
- 直播軟體原始碼,設定懸浮窗並可進行任意位置的移動原始碼
- Fluid Browser for Mac(多工懸浮透明視窗瀏覽器)1.6啟用版UIMac瀏覽器
- 直播平臺製作,Android 懸浮窗延時5秒返回APP問題AndroidAPP
- 電商直播主圖設計素材,輕鬆搞定直播懸浮窗設計!
- Android懸浮框的適配問題Android
- 短視訊系統原始碼,關於懸浮窗的縮放、拖動等應用原始碼
- 直播app開發,推出語音聊天室時保持懸浮窗存在狀態APP
- 底部導航欄懸浮效果
- css做個波浪懸浮球?CSS
- 小米、格力間的10億賭約即將到期,現在看來小米贏格力有無懸念?
- Android setVisibility(View.GONE)無效的問題及原因分析AndroidViewGo
- Android懸浮窗怎麼簡單實現?這樣用 kotlin編寫輕鬆搞定!AndroidKotlin