Android QQ音乐/酷狗音乐锁屏控制实现原理,酷狗锁屏

我实现的效果css

相似效果

混乱的锁屏控制

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音乐/酷狗音乐那样, 本身作一个锁屏控制页面java

题外话: 给RemoteControlClient设置封面时, 参数是一个Bitmap, 这个参数传入后, 千万不要在其余地方使用这个Bitmap, 也不要持有它的引用, 更不要自做聪明调用它的recycle方法.android

实现思路

锁屏应用: 大炮打蚊子

首先想到的因该是作一个锁屏, 也就是使用Android的API, 作一个锁屏应用, 和输入法等应用同样, 但这个方法成本很高. 国内的那些锁屏应用, 首先要作的就是引导用户设置锁屏应用, 步骤至关繁杂, 只是为了一个播放控制就用一个锁屏应用, 没有哪一个用户会这么有耐心.程序员

悬浮窗: 黑魔法

得益于我国程序员的脑洞, 咱们有了第二种思路: 悬浮窗. 
悬浮窗的一个比较严谨的名字叫系统警告窗口, 国内外的一些流氓厂商, 常常用悬浮窗弹一些广告, 这个悬浮窗是浮在正常的app的上面的, 因此若是它不消失, 极可能你连正常使用手机都有问题. 
这是一个比较打扰用户的东西, 并且也有必定的安全风险. MIUI的权限管理默认是将悬浮窗关闭的, 而有道词典的复制查词功能, 就是用悬浮窗作的, 若是你没给有道词典打开这个权限, 复制查词这个功能就废了.安全

普通Activity伪造锁屏

文章开头的GIF图片展现的效果, 就是用一个普通Activity作的. 
国内的app们, 最终都选择了这条道路, 不知道他们是谁抄的谁, 第一个想到使用普通Activity伪造一个锁屏的开发者, 我只能说很是有创造力.app

监听锁屏事件

准确来讲咱们监听的是屏幕熄灭事件, 关屏事件的Intent是Intent.ACTION_SCREEN_OFF, 不须要任何权限就能够监听, 可是必须使用代码注册, 也就是说咱们必须有一个Service在后台监听才行, 对音乐类app来讲, 这不是问题, 音乐app自己就是使用Service来控制MediaPlayer的. 只须要在Service中注册监听Intent.ACTION_SCREEN_OFF就行. 监听到这个事件, 咱们就启动一个Activity, 这就是咱们的锁屏Activity.ide

@Override
public 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之类的.this

解锁屏幕与显示在锁屏之上

在显示咱们的假锁屏的时候, 咱们应当帮用户解锁, 这样咱们才能冒充锁屏, 而不会出现用户”解锁”两次的状况, 但咱们只能要求系统解锁没有密码的锁屏, 有密码的状况下, 咱们是不能解锁屏幕的, 这时咱们应该覆盖在锁屏界面上, 幸亏, 在API level 5中就引入了两个Flag,FLAG_DISMISS_KEYGUARDFLAG_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方法便可

@Override
public 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>

就不会有这种状况发生了, 可是这样的话入场动画也没了, 总之如何取舍看你们了.