QQ音樂/酷狗音樂鎖屏控制實現原理,酷狗鎖屏
我實現的效果
混亂的鎖屏控制
Android自4.0版本, 也就是API level 14開始, 加入了鎖屏控制的功能, 相關的類是RemoteControlClient, 這個類在API level 21中被標記為deprecated, 被新的類MediaSession所替代. 我們的音樂App中最開始使用的是原生鎖屏控制API, 說實話這個API不好用, 遇到了一些小坑, 最要命的是不同品牌的手機, 鎖定畫面長的還不一樣, 就連我自己都沒見過原生4.0的鎖屏控制介面是什麼樣的. 國內的手機廠商都自以為自己的審美很強, 設計了千奇百怪的鎖屏控制介面, MIUI更奇怪, MIUI 6是在原生4.4.4的基礎上改的, 竟然有一段時間都沒有鎖屏控制介面, 後來更新才有. 而原生Android在5.0時, 將鎖屏和通知欄控制合并, 整個邏輯非常混亂. 我們還是決定像QQ音樂/酷狗音樂那樣, 自己做一個鎖屏控制頁面
題外話: 給RemoteControlClient設定封面時, 參數是一個Bitmap, 這個參數傳入後, 千萬不要在其他地方使用這個Bitmap, 也不要持有它的引用, 更不要自作聰明調用它的recycle方法.
實現思路鎖定畫面 app: 大炮打蚊子
首先想到的因該是做一個鎖屏, 也就是使用Android的API, 做一個鎖定畫面 app, 和IME等應用一樣, 但這個方法成本很高. 國內的那些鎖定畫面 app, 首先要做的就是引導使用者佈建鎖定畫面 app, 步驟相當繁雜, 只是為了一個播放控制就用一個鎖定畫面 app, 沒有哪個使用者會這麼有耐心.
懸浮窗: 黑魔法
得益於我國程式員的腦洞, 我們有了第二種思路: 懸浮窗.
懸浮窗的一個比較嚴謹的名字叫系統警告視窗, 國內外的一些流氓廠商, 經常用懸浮窗彈一些廣告, 這個懸浮窗是浮在正常的app的上面的, 所以如果它不消失, 很可能你連正常使用手機都有問題.
這是一個比較打擾使用者的東西, 而且也有一定的安全風險. MIUI的許可權管理預設是將懸浮窗關閉的, 而有道詞典的複製查詞功能, 就是用懸浮窗做的, 如果你沒給有道詞典開啟這個許可權, 複製查詞這個功能就廢了.
普通Activity偽造鎖屏
文章開頭的GIF圖片展示的效果, 就是用一個普通Activity做的.
國內的app們, 最終都選擇了這條道路, 不知道他們是誰抄的誰, 第一個想到使用普通Activity偽造一個鎖屏的開發人員, 我只能說非常有創造力.
監聽鎖屏事件
準確來說我們監聽的是螢幕熄滅事件, 關屏事件的Intent是Intent.ACTION_SCREEN_OFF, 不需要任何許可權就可以監聽, 但是必須使用代碼註冊, 也就是說我們必須有一個Service在後台監聽才行, 對音樂類app來說, 這不是問題, 音樂app本身就是使用Service來控制MediaPlayer的. 只需要在Service中註冊監聽Intent.ACTION_SCREEN_OFF就行. 監聽到這個事件, 我們就啟動一個Activity, 這就是我們的鎖屏Activity.
@Overridepublic void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(Intent.ACTION_SCREEN_OFF)) { Intent lockscreen = new Intent(PlaybackService.this, LockScreenActivity.class); lockscreen.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(lockscreen); }}
注意我們在Service中啟動一個Activity, 需要加上Intent.FLAG_ACTIVITY_NEW_TASK這個flag才行.
透明背景
要想做到鎖屏那樣滑動解鎖, 比如像圖中的樣子, 我們除了要根據手勢移動View以外, 還要讓Activity的背景透明, 比如將theme設定成下面這樣.
<style name="LockScreenBase" parent="AppBaseTheme"> <item name="android:windowIsTranslucent">true</item> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:colorBackgroundCacheHint">@null</item> <item name="android:windowNoTitle">true</item> <item name="android:backgroundDimEnabled">false</item> <item name="android:windowAnimationStyle">@null</item> <item name="android:windowContentOverlay">@null</item></style>
這個style其實做了很多東西, 大家根據自己的需要可以刪減一部分, 比如狀態列透明, 不使用TitleBar之類的.
解鎖螢幕與顯示在鎖屏之上
在顯示我們的假鎖屏的時候, 我們應當幫使用者解鎖, 這樣我們才能冒充鎖屏, 而不會出現使用者”解鎖”兩次的情況, 但我們只能要求系統解鎖沒有密碼的鎖屏, 有密碼的情況下, 我們是不能解鎖螢幕的, 這時我們應該覆蓋在鎖定畫面上, 幸好, 在API level 5中就引入了兩個Flag, FLAG_DISMISS_KEYGUARD和FLAG_SHOW_WHEN_LOCKED
在鎖屏Activity的onCreate方法中給Activity加上兩個Flag
this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
一定要兩個一起用, 否則效果不大好, 當時測試了好久, 後來看了一下QQ音樂的實現, 才發現兩個一起用效果才好, 否則會有一些奇怪的問題.
隱藏蹤跡與獨立的Task
作為一個鎖定畫面, 應當是獨立的, 也就是說, 我們這個Activity應當獨立於我們的App存在, 至少看起來是這樣. 從Android的角度來看, 我們app的主介面裡的所有Activity, 應當在一個Task裡, 而鎖屏Activity, 應當在一個獨立的Task裡, 因此我們需要給鎖屏Activity一個獨立的Task, 而且無論何時, 都只有一個鎖屏Activity執行個體存在.
另外, Android有查看近期任務的功能, 我們不希望鎖定畫面這個獨立的Task顯示在裡面, 所以鎖屏Activity不能顯示在近期任務中.
說了這麼多, 要做很簡單, 只需要在Manifest裡面聲明Activity時加入幾個屬性即可
<activity android:excludeFromRecents="true" android:exported="false" android:launchMode="singleInstance" android:name=".view.lockscreen.LockScreenActivity" android:screenOrientation="portrait" android:taskAffinity="com.package.name.lockscreen" android:theme="@style/LockScreenTheme"></activity>
上面的屬性中android:excludeFromRecents="true"讓鎖屏Activity不顯示在近期任務中, android:launchMode="singleInstance"和android:taskAffinity="com.package.name.lockscreen"保證鎖屏Activity有一個單獨的Task, 且這個Task裡永遠只有它一個執行個體.
不響應Back按鍵
鎖定畫面當然不響應Back按鍵, 只需要重寫Activity的onBackPressed方法即可
@Overridepublic void onBackPressed() { // do nothing}
對Home按鍵的處理
我們無法監聽Home按鍵, 但是可以改變因Home進入後台時的處理, 比如在Manifest的activity聲明中加上
android:noHistory="true"
這樣如果使用者通過Home按鍵讓我們的應用進入後台, 我們會讓這個activity銷毀, 就像我們被滑動關閉一樣.
如果不加, 最好重寫Activity的onNewIntent來應對因Home進入後台, 然後Service再次啟動鎖屏Activity的情況.
處理黑色閃屏
我們的鎖屏Activity在滑動”解鎖”之後, 理論上是直接進入下面的介面, 但有時如果下面不是launcher, 而是一個app, 有可能會閃一下黑屏, 這個其實是底下activity的入場動畫導致的, 某些Android版本會對頂部activity透明時處理有些奇怪, 我們不能保證其他的應用不閃黑屏, 但是對自己的的應用還是可以的, 只需要在我們的主體activity的style中加上
<item name="android:windowAnimationStyle">@null</item>
就不會有這種情況發生了, 但是這樣的話入場動畫也沒了, 總之如何取捨看大家了.
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。