Android锁屏实现与总结

一、自定义锁屏基本原理

二、重要步骤

1、广播注册

2、Activity设置

3、按键的屏蔽

4、滑屏解锁

5、Event bus的使用

三、出现的问题

1、小米和魅族等手机锁屏权限问题

2、透明栏与沉浸模式

3、手机适配

4、处理黑色闪屏

5、线控耳机

6 Android 上的「安全音量」


一、自定义锁屏基本原理

先上效果图:

            

  实现锁屏的方式有多种(锁屏应用、悬浮窗、普通Activity伪造锁屏等等)。通过网络查找资料与反编译云音乐apk,本项目使用了国内比较主流并且被广泛应用的Activity伪造锁屏方式。

  Activity实现自定义锁屏页的思路很简单,即在听书模式开启时,启动一个service,在service中监听系统SCREEN_OFF的广播。当屏幕熄灭时service监听到广播,开启一个锁屏页Activity在屏幕最上层显示,该Activity创建的同时会去掉系统的锁屏(如果有密码是禁不掉的)。示意图如下:

 


二、重要步骤

1、广播注册

   LockScreenService是普通的Service,在用启动听书模式时候startService(ReadBookActivity),与用同一个程。

    此外,SCREEN_OFF广播听必动态注册的,如果在AndroidManifest.xml中静注册将无法接收到SCREEN_OFF广播。

    标志位FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS,是了避免在最近使用程序列表出Service所启Activity

    ActivityIntentFlag,如果不添加FLAG_ACTIVITY_NEW_TASK志位,会出“Calling startActivity() from outside of an Activity”的运行异常,因为是从ServiceActivityActivity要存在于activity中,而Service在启activity时必然不存在一个activity,所以要新起一个,并装入启activity。使用该标志位,也需要在AndroidManifest中声明taskAffinity,即新task的名称,否则锁Activity实质上还是在建立在原来Apptask中。

2、Activity设置

    锁屏的activity内部也要做相的配置,activity也能够显示,同去掉系统锁屏。当然如果置了系统锁屏密,系统锁屏是没有法去掉的,里考没有置密的情况。我在自定义锁ActivityonCreate()方法里定以下志位就能完全实现相同的功能:

    FLAG_DISMISS_KEYGUARD用于去掉系统锁FLAG_SHOW_WHEN_LOCKED使Activity仍然能够显示。另外需要在Manifest中加入适当的限:

 

3、按键的屏蔽

当自定义锁在手机上,我希望它像系统锁屹立不倒,所有的按都不能触它,只有通划屏或者指才能解,因此有必要键进行一定程度上的屏蔽。针对只有虚的手机,我可以通过隐藏虚的方式部分解决问题。但是当用底部滑藏后的虚键还是会滑出,而且如果用是物理按就必须进行屏蔽了。需要重写ActivityonBackPressed()方法即可。

    Home键 与Recent键的点击事件是在framework层进行处理的,因此onKeyDown()dispatchKeyEvent()都捕不到点事件。关于两个按的屏蔽方法,网上相关的料有很多,有的用到了反射,有的通Window志位和Type等,的来说这些方法只部分android版本有效,有的完全无法编译。其实这么做的目的无非是实现一个粹的,但是种做法容易造成的异常崩,我足的是用的快捷操作,Home键和Recent键无关痛痒,基本可以不管

 

4、滑屏解锁

    做完以上几步,当屏幕熄灭后,再打开屏幕就能够看到我们的自定义锁屏页了。接下来要实现划屏解锁。划瓶解锁的基本思路很简单,当手指在屏幕上滑动时,拦截并处理滑动事件,使锁屏页面随着手指运动,当运动到达一定的阈值时,用户手指松开手指,锁屏页自动滑动到屏幕边界消失,如果没有达到运动阀值,就会自动滑动到起始位置,重新覆盖屏幕。 为了将划屏逻辑与页面内容隔离开来,我们在锁屏页面布局中添加一个自定义的UnderView,这个UnderView填充整个屏幕,位于锁屏内容View(将其引用称之为mMoveView,并传入到UnderView中)的下方,所有划屏相关的事件都在这里拦截并处理。

        mMoveView示内容,除了理一些简单的点事件,其他非点事件序列都由底UnderView进行处理。只需要重写UnderViewonTouchEvent方法就能够实现


其中,mStartX记录滑动操作起始的xhandleMoveView方法控制mMoveView随手指的移doTriggerEvent处理手指离开后mMoveView的移动动画。两个方法的定如下:

 

    handleMoveView()中,首先算当前触点x与初始xmStartX的差movex,然后mMoveViewsetTranslationX方法移。此外,我可以通getBackground()获取UnderView的背景,并根据已划开屏幕占整个屏幕的百分比setAlpha方法改背景的透明度,做出抽拉开的光影化效果。

当手指离开屏幕,doTraiggerEvent方法会的距离与阀值进行一个比,此阀值为0.4*屏幕度,如果低于阀值ObjectAnimator0.25smMoveView到初始位置,同ObjectAnimatorAnimatorUpdateListeneronAnimationUpdate方法中更新背景透明度;如果低于阀值,以同的方式将mMoveView移出屏幕右界,然后将Activity干掉,具体做法是animator增加一个AnimatorListenerAdapter听器,在该监听器的onAnimationEnd方法中使用在Activity中定mHandler发送finish消息,完成解锁。

 

5、Event bus的使用

      锁屏Activity中的Buttun,可以通过EventBus远程控制ReadBookActivity中的播放、暂停、下一首等操作。



二、出现的问题


1、小米和魅族等手机锁屏权限问题

        例如小米手机(miui 6.8.18 开始)有锁屏权限的问题,权限未开的情况下锁屏会在系统锁屏的下方。目前只能手动打开(个别应用在MIUI是默认打开)。

        在设置中:


2、透明栏与沉浸模式

        透明栏与沉浸模式总共用到了5FlagSYSTEM_UI_FLAG_LAYOUT_STABLE保持整个View稳定,使View不会因为SystemUI的变化而做layoutSYSTEM_UI_FLAG_IMMERSIVE_STIKY,能够在隐藏的bar被呼出时(比如从屏幕下边缘开始向上做滑动手势),使bar在无相关操作的情况下自动再次隐藏。对于SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION,我们容易被其中的HIDE_NAVIGATION所迷惑,其实这个Flag没有隐藏导航栏的功能,只是控制导航栏浮在屏幕上层,不占据屏幕布局空间。SYSTEM_UI_FLAG_HIDE_NAVIGATION,才是能够隐藏导航栏的FlagSYSTEM_UI_FLAG_LAYOUT_FULLSCREEN也不能隐藏状态栏,只是使状态栏浮在屏幕上层。

    需要注意的是,这段代码除了需要加在ActivityOnCreate()方法中,也要加在重写的onWindowFocusChanged()方法中,在窗口获取焦点时再将Flag设置一遍,否则可能导致无法达到预想的效果。

     Android 5.0 之后状态栏和导航栏也有更多的特点。除了原有的“半透明”模式以外,还有“全透明”以及“变色”模式,一种会完全隐藏背景,另一种可以取色作为背景颜色等。对于Android 4.4以上5.0以下的版本,设置透明状态栏的方式如下:


      除了要清理掉4.4FLAG_TRANSLUCENT_STATUS外,还要配合SYSTEM_UI_FLAG_LAYOUT_FULLSCREENSYSTEM_UI_FLAG_LAYOUT_STABLE,添加标志位FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,并调用setStatusBarColor设置状态栏的颜色为透明。


3、手机适配

由于Android 手机屏幕高度差异比较大,所以有可能会存在要显示的控件高度比屏幕还高的问题。所以需要通过代码对布局中某些控件宽高进行等比例调整。

例如在本项目中通过固定音量键以上的高度和最下方滑动解锁的位置,来动态等比例动态调整中间的专辑封面图片。mMain.post(new Runnable()


4、处理黑色闪屏

    我们的锁屏Activity在滑动”解锁”之后, 理论上是直接进入下面的界面, 但有时如果下面不是launcher, 而是一个app, 有可能会闪一下黑屏, 这个其实是底下activity的入场动画导致的, 某些Android版本会对顶部activity透明时处理有些奇怪, 不能保证其他的应用不闪黑屏, 但是对自己的的应用还是可以的, 只需要在主体activitystyle中加上


5、线控耳机

       我们只要定义一个广播接收者来接收到这个广播。这个广播的意图是 android.intent.action.MEDIA_BUTTON 。注册普通的广播接收器有两个常见的办法,一种是在代码中动态注册,另一种是在项目Manifest里面注册。但是这个广播,要注册两遍、两遍、两遍,Manifest里一遍(常规办法),代码中一遍(借助多媒体服务注册),否则没有效果。


    两种注册方式缺一不可,所以解除了一种,它的听作用也就失效了。

 

6 Android 上的「安全音量」

    Android 设备插上耳机,为了避免音量过高伤害用户听力,会触发其安全音量”(Safe Media Volume)机制,如果在未使用大音量,且这时设置音量 newIndex 其推荐阈值则这段代码执行完你会发现毫无反,播放的声音依然不会很大。

  解决问题的关键在于被忽略的最后一个参数 flags 。只要在设置音量后,复查一次当前值是否相当,如果比较小,则交由系统来显示音量提示对话框。而此时因欲设定的值超过推荐值,一般会触发音量过高警告,提示用户用户确认后即可设置成功。


本文来自网易实践者社区,经作者吴思博授权发布。