嘗試利用 WindowManager 新增浮窗的方式實現
想在鎖屏上面實現彈窗,第一個想法就是利用 WindowManager
設定 Window
的 Flag
,通過設定 Flag
的顯示優先順序來讓視窗顯示在鎖屏的上面。接下來就是試驗可能相關的 Window Type
屬性,驗證該方案是否可行。
在嘗試各個 Window Type
屬性之前需要明確各個 Type
所需要的許可權,下面是 com.android.internal.policy.impl.PhoneWindowManager.checkAddPermission
的原始碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
public int checkAddPermission(WindowManager.LayoutParams attrs) { int type = attrs.type; if (type < WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW || type > WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) { return WindowManagerImpl.ADD_OKAY; } String permission = null; switch (type) { case TYPE_TOAST: // XXX right now the app process has complete control over // this... should introduce a token to let the system // monitor/control what they are doing. break; case TYPE_INPUT_METHOD: case TYPE_WALLPAPER: // The window manager will check these. break; case TYPE_PHONE: case TYPE_PRIORITY_PHONE: case TYPE_SYSTEM_ALERT: case TYPE_SYSTEM_ERROR: case TYPE_SYSTEM_OVERLAY: permission = android.Manifest.permission.SYSTEM_ALERT_WINDOW; break; default: permission = android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; } if (permission != null) { if (mContext.checkCallingOrSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { return WindowManagerImpl.ADD_PERMISSION_DENIED; } } return WindowManagerImpl.ADD_OKAY; } |
明顯不適合的 Type
:TYPE_TOAST
, TYPE_INPUT_METHOD
, TYPE_WALLPAPER
; 可能適合的 Type
:TYPE_PHONE
, TYPE_PRIORITY_PHONE
, TYPE_SYSTEM_ALERT
, TYPE_SYSTEM_ERROR
, TYPE_SYSTEM_OVERLAY
; 其它型別的 Type
:需要系統簽名許可權:
1 |
android.Manifest.permission.INTERNAL_SYSTEM_WINDOW |
而申請該許可權需要系統簽名,所以我們是無法獲取許可權的。
TYPE_PHONE
1 2 3 4 5 6 7 8 |
/** * Window type: phone. These are non-application windows providing * user interaction with the phone (in particular incoming calls). * These windows are normally placed above all applications, but behind * the status bar. * In multiuser systems shows on all users' windows. */ public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2; |
TYPE_PHONE
型別的視窗可以顯示在其它 APP 的上面,但不能顯示在鎖屏的上面,所以 PASS。
TYPE_PRIORITY_PHONE
1 2 3 4 5 6 7 |
/** * Window type: priority phone UI, which needs to be displayed even if * the keyguard is active. These windows must not take input * focus, or they will interfere with the keyguard. * In multiuser systems shows on all users' windows. */ public static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7; |
TYPE_PRIORITY_PHONE
型別的視窗可以顯示在其它 APP 的上面,但不能顯示在鎖屏的上面,所以 PASS。而且實際的行為和註釋並不相符,該型別的視窗是可以獲取互動事件的,具體原因待查。
TYPE_SYSTEM_ALERT
1 2 3 4 5 6 |
/** * Window type: system window, such as low power alert. These windows * are always on top of application windows. * In multiuser systems shows only on the owning user's window. */ public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3; |
TYPE_SYSTEM_ALERT
型別的視窗可以顯示在其它 APP 的上面,但不能顯示在鎖屏的上面,所以 PASS。
TYPE_SYSTEM_OVERLAY
1 2 3 4 5 6 7 |
/** * Window type: system overlay windows, which need to be displayed * on top of everything else. These windows must not take input * focus, or they will interfere with the keyguard. * In multiuser systems shows only on the owning user's window. */ public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6; |
TYPE_SYSTEM_OVERLAY
型別的視窗可以顯示在所有其它視窗的上面,包括鎖屏,而且不會影響它下面視窗的互動事件響應,但是該屬性視窗不能獲得焦點,無法進行互動(如果該視窗可以獲取焦點,那麼就可以用來抓取使用者的鎖屏密碼,出於安全考慮,系統是不會允許的),所以只能用來簡單的展示內容,如果需要互動的鎖屏彈窗,那麼該屬性 PASS。
TYPE_SYSTEM_ERROR
1 2 3 4 5 6 |
/** * Window type: internal system error windows, appear on top of * everything they can. * In multiuser systems shows only on the owning user's window. */ public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10; |
在原生 ROM 5.1 下試驗是可以顯示出來的,但根據註釋來看(appear on top of everything they can)不是在所有情況下都可以顯示在鎖屏上面的,而且像 MIUI 和 Flyme 等 ROM 預設是遮蔽浮窗許可權的,考慮到這點,利用 WindowManager
新增浮窗的方式實現鎖屏彈窗的方案基本 PASS。
使用 Activity 的方式實現
首先需要對 Activity 進行如下設定
1 2 3 4 5 6 7 8 |
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final Window win = getWindow(); win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); } |
其中最主要也是必須要設定的就是:FLAG_SHOW_WHEN_LOCKED
,顧名思義就是鎖屏下顯示該 Activity
。而其它幾個 Flag
包括:解鎖、保持螢幕常亮、點亮螢幕可以根據具體的需求選擇設定。
在 AndroidManifest.xml 中宣告 Activity
同樣該 Activity
也需要在 AndroidManifest.xml
中宣告,宣告時需注意新增 android:excludeFromRecents="true"
屬性,是為了將該 Activity
從最近任務列表中去除,否則使用者會覺得很奇怪。還有因為這個 Activity
會整個蓋在鎖屏上面,而且就算設定成背景透明,鎖屏介面也不會顯示在下面(系統主要是出於安全考慮),所以需要考慮下該 Activity
的背景,這裡為了顯示不要太突兀將主題設為桌布。
1 2 3 4 |
<activity android:name=".LockScreenActivity" android:launchMode="singleInstance" android:excludeFromRecents="true" android:theme="@android:style/Theme.Wallpaper.NoTitleBar"/> |
啟動 Activity
由於該 Activity
是為了在鎖屏的情況下顯示的,所以啟動 Activity
時不要忘了判斷手機是否處於鎖屏狀態,可以通過下面這種方式判斷鎖屏狀態:
1 2 3 4 |
KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); if (km.inKeyguardRestrictedInputMode()) { // 處於鎖屏狀態 } |