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:去掉系统锁屏页,设置了系统锁屏密码是没有办法去掉的,现在手机一般都会设置锁屏密码,该配置可基本忽略。
kotlin 复制代码
    this.window.addFlags(
        WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or
                WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
    )

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

  • 主题配置,

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

ini 复制代码
<style name="LockScreenTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
    <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>
  • 启动模式配置

    • BroadcastReceiver中启动锁屏页Activity,需要添加Intent.FLAG_ACTIVITY_NEW_TASKflag,造成锁屏Activity单独创建一个history stack,会在最近任务中显示出来,通过配置excludeFromRecentsnoHistorytaskAffinity来规避这个问题。
    ini 复制代码
      <activity
          android: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">
    
      </activity>
      ```

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

  • Home键,由于不是用来替代系统锁屏的锁屏软件,不需要处理Home键事件.

  • Back/Menu键,重写onKeyDown让锁屏页不处理这两个事件

    kotlin 复制代码
      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"的运行时异常
kotlin 复制代码
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种:

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

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

品牌 型号 系统 系统版本 相关权限 权限截图 权限截图
华为 P50 HarmonyOS HarmonyOS 4.0.0 1. 悬浮窗 2.后台弹窗
oppo OPPO K9 5G ColorOS 13 Android 13 1. 悬浮窗 2. 锁屏显示
vivo Y52s Funtouch OS 10.5 Android 10 1. 悬浮窗 2. 锁屏显示 3. 后台弹出界面
一加 OnePlus Ace Pro ColorOS 13 Android 13 1. 悬浮窗 2. 锁屏显示
荣耀 honor 60 magic ui 6.1 Android 12 1. 显示在其他应用的上层
iQOO Neo3 Origin OS Android 12 1. 悬浮窗 2. 锁屏显示 3. 后台弹出界面
Hi nova Hi nova 9 Emui 12 Android 12 1. 后台弹窗 2. 悬浮窗 3. 显示在其他应用的上层

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

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

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

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

scss 复制代码
    Intent.ACTION_USER_PRESENT->{
        ActivityUtils.getActivityList()?.forEach {
            if ("com.xxx.lockscreen.LockScreenActivity" == it.componentName.className) {
                it.finish()
            }
        }
    }
  1. 在自定义锁屏ActivityonResume中监听设备是否已解锁并finish锁屏页
kotlin 复制代码
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

arduino 复制代码
    // 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对权限的管控越来越严,且不同的系统同一权限的命名和授予方式差异较大,需要用更吸引用户的体验去引导用户授权。

相关推荐
生椰拿铁You2 分钟前
09 —— Webpack搭建开发环境
前端·webpack·node.js
狸克先生13 分钟前
如何用AI写小说(二):Gradio 超简单的网页前端交互
前端·人工智能·chatgpt·交互
baiduopenmap28 分钟前
百度世界2024精选公开课:基于地图智能体的导航出行AI应用创新实践
前端·人工智能·百度地图
loooseFish36 分钟前
小程序webview我爱死你了 小程序webview和H5通讯
前端
请叫我欧皇i1 小时前
html本地离线引入vant和vue2(详细步骤)
开发语言·前端·javascript
533_1 小时前
[vue] 深拷贝 lodash cloneDeep
前端·javascript·vue.js
闲暇部落1 小时前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
本当迷ya1 小时前
💖2025年不会Stream流被同事排挤了┭┮﹏┭┮(强烈建议实操)
后端·程序员
guokanglun1 小时前
空间数据存储格式GeoJSON
前端
zhang-zan1 小时前
nodejs操作selenium-webdriver
前端·javascript·selenium