注册

Android自定义锁屏实践总结

1. 背景


在我们的业务场景中,用户在完成下单后大部分的概率不再需要进入App做其他的操作,只需要知道当前的订单状态,为了方便用户在不解锁的情况下也能实时查看当前订单的状态,货拉拉用户 iOS端上线了灵动岛功能,用户的接受度较高,由于Android暂不支持灵动岛,所以我们自定义了一个锁屏页面。


2. 实践


  2.1 方案选择


  实现锁屏的方式有多种(锁屏应用、悬浮窗、普通Activity伪造锁屏等等),由于我们的业务场景简单只展示我们的订单状态,且不需要很强的保活干扰用户的操作,采用了普通的Activity伪造锁屏。


  2.2 方案原理


  锁屏的大概实现原理都很简单,监听系统的亮屏广播,在亮屏的时候展示自己的锁屏界面,自定义的锁屏界面会覆盖在系统的锁屏界面上,用户在自定义锁屏界面上进行一系列的动作后进入系统的解锁界面。



  2.3 代码实现


    2.3.1 锁屏页面


    锁屏页Activity在普通的Activity需要加上一些配置


      1. 在onCreate中设置添加Flags,让当前Activity可以在锁屏时显示



  • FLAG_SHOW_WHEN_LOCKED:使Activity在锁屏时仍然能够显示
  • FLAG_DISMISS_KEYGUARD:去掉系统锁屏页,设置了系统锁屏密码是没有办法去掉的,现在手机一般都会设置锁屏密码,该配置可基本忽略。

    this.window.addFlags(
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
)

      2. 在AndroidManifest.xml中进行对锁屏页Activity进行配置



  • 主题配置,

      主要是配置锁屏Activity的背景为透明和去除过度动画,让锁屏Activity过渡到系统锁屏更自然






  • 启动模式配置



    • BroadcastReceiver中启动锁屏页Activity,需要添加Intent.FLAG_ACTIVITY_NEW_TASKflag,造成锁屏Activity单独创建一个history stack,会在最近任务中显示出来,通过配置excludeFromRecentsnoHistorytaskAffinity来规避这个问题。

      name=".lockscreen.LockScreenActivity"
    android:configChanges="uiMode"
    android:excludeFromRecents="true"
    android:exported="false"
    android:launchMode="singleInstance"
    android:noHistory="true"
    android:screenOrientation="portrait"
    android:taskAffinity="com.xxx.lockscreen"
    android:theme="@style/LockScreenTheme">


    ```



      3. Home键,Back键和Menu键事件的处理



  • Home键,由于不是用来替代系统锁屏的锁屏软件,不需要处理Home键事件.
  • Back/Menu键,重写onKeyDown让锁屏页不处理这两个事件


      override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
    return when (event?.keyCode) {
    KeyEvent.KEYCODE_BACK -> true

    KeyEvent.KEYCODE_MENU -> true

    else -> super.onKeyDown(keyCode, event)
    }
    }
    ```



    2.3.2 广播


    LockScreenBroadcastReceiver是普通的BroadcastReceiver,不做其他的配置,需要注意两点:



  1. 动态注册/注销
  2. 在广播中启动Activity,需要添加FLAG_ACTIVITY_NEW_TASK,否则会出现“Calling startActivity() from outside of an Activity”的运行时异常


class LockScreenBroadcastReceiver : BroadcastReceiver() {

override fun onReceive(context: Context?, intent: Intent?) {
intent?.let { handleCommandIntent(context, it) }
}

private fun handleCommandIntent(context: Context?, intent: Intent) {
when (intent.action) {
Intent.ACTION_SCREEN_OFF -> {
val lockScreen = Intent(this, LockScreenActivity::class.java)
lockScreen.setPackage("com.xxx.xxx")
lockScreen.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK )
context?.startActivity(lockScreen)
}
Intent.ACTION_USER_PRESENT -> {
// 处理解锁后才显示自定义锁屏Activity
}
}
}
}

    2.3.3 实现效果





3. 注意点


以下是在实践过程中的一些问题小结,供大家参考。


  3.1 权限相关


  不同手机系统上权限的名称,大体分为5种:



  • 后台弹窗
  • 悬浮窗
  • 显示在其他应用的上层
  • 锁屏展示
  • 后台弹出界面

    以及不同的组合效果也不同,以下是已测试过的手机,


品牌型号系统系统版本相关权限权限截图权限截图
华为P50HarmonyOSHarmonyOS 4.0.01. 悬浮窗 2.后台弹窗
oppoOPPO K9 5GColorOS 13Android 131. 悬浮窗 2. 锁屏显示
vivoY52sFuntouch OS 10.5Android 101. 悬浮窗 2. 锁屏显示 3. 后台弹出界面
一加OnePlus Ace ProColorOS 13Android 131. 悬浮窗 2. 锁屏显示
荣耀honor 60magic ui 6.1Android 121. 显示在其他应用的上层
iQOONeo3Origin OSAndroid 121. 悬浮窗 2. 锁屏显示 3. 后台弹出界面
Hi novaHi nova 9Emui 12Android 121. 后台弹窗 2. 悬浮窗 3. 显示在其他应用的上层

  OPPO/一加 手机特殊说明:在默认状态下在系统设置下找不到“锁屏显示”的入口,需要先授权“悬浮窗”权限再次启动应用会在应用启动时弹窗提示授权在锁屏上显示,然后在系统设置中会出现“锁屏显示”的入口。


  3.2 有些手机在未授权时,应用在前台时锁屏可以展示,但是应用退到后台不展示。


  Android 10 (API 级别 29) 及更高版本对后台应用可启动Activity的时间施加限制。这些限制有助于最大限度地减少对用户造成的中断,并且可以让用户更好地控制其屏幕上显示的内容。具体见官方文档


  3.3 在部分手机上,点亮屏幕后不会立即展示自定义的锁屏界面,在解锁系统锁屏后才会展示自定义的锁屏。1. 监听解锁事件主动finish自定义的锁屏页面


    Intent.ACTION_USER_PRESENT->{
ActivityUtils.getActivityList()?.forEach {
if ("com.xxx.lockscreen.LockScreenActivity" == it.componentName.className) {
it.finish()
}
}
}

2. 在自定义锁屏ActivityonResume中监听设备是否已解锁并finish锁屏页


override fun onResume() {
super.onResume()
val isInteractive = (getSystemService(Context.POWER_SERVICE) as PowerManager).isInteractive
val isKeyguardLocked = (getSystemService(KEYGUARD_SERVICE) as KeyguardManager).isKeyguardLocked
if (isInteractive && !isKeyguardLocked) {
finish()
}
}

3.4 当在自定义锁屏页触发Home键事件后,锁屏页Activity不再显示


提示用户根据自己的系统去授予对应的权限,不同系统所需的权限参考上面第1点


3.5 Android 8.0 透明主题造成闪退


  在Android 8.0系统上Activity满足了以下条件:



  1. targetSdkVersion > 26
  2. 透明主题
  3. 固定屏幕方向

会出现java.lang.IllegalStateException: Only fullscreen activities can request orientation


    // ActivityRecord.java
void setRequestedOrientation(int requestedOrientation) {
if (ActivityInfo.isFixedOrientation(requestedOrientation) && !fullscreen
&& appInfo.targetSdkVersion > O) {
throw new IllegalStateException("Only fullscreen activities can request orientation");
}
....
}

  建议针对Android 8.0以外的系统才固定屏幕方向,可参考Android 8.0系统透明主题适配解决办法


4. 总结


从线上最新的数据来看,接近60%的订单在锁屏后可以通过自定义锁屏查看到订单状态。


功能上线后发现比较少用户会主动选择关闭,从最开始的出发点就是为用户提供一个便捷的状态查看的入口,用户下完单等待司机接单以及接单后司机的状态都是用户会重点关注的,同时我们会过滤掉一些不太重要的状态的显示避免对用户带来不必要的干扰。


从实现的角度上来说整体较简单,较麻烦的是国内的ROM对权限的管控越来越严,且不同的系统同一权限的命名和授予方式差异较大,需要用更吸引用户的体验去引导用户授权。


作者:货拉拉技术
来源:juejin.cn/post/7316806159008841767

0 个评论

要回复文章请先登录注册