Android实战进阶 - 启动页

场景:当启动页处于倒计时阶段,用户将其切换为后台的多任务卡片状态,倒计时会继续执行,直到最后执行相关逻辑(一般会跳转引导页、进入主页等)
期望:而综合市场来看,一般我们期望的是当其处于多卡片状态,需要暂停倒计时,只有恢复前台状态后继续计时。而不是重新计时或已计时完毕

关于启动页的一些基础内容,之前已经做过总结了,此篇主要用于解决上方提到的业务场景

  • Android进阶之路 - Splash、Welcome欢迎页面简单实现(当年入门时候写的,Demo级入门示例)
  • Android进阶之路 - 快速实现启动页(基础版,可用于实战项目)

业务实战

    • 项目实战
    • AI提供方案
      • CountDownTimer + 生命周期感知
      • ViewModel + LiveData

项目实战

以下是我从项目中剥离的伪代码,主要用于解决不同生命周期,计时器带来的影响,核心思想有以下几点

  • 倒计时长根据当前计时器的变化而实时变更
  • 当处于onPause(后台)时,取消计时器
  • 当处于onResume(前台)时,将计时器剩余时长传入计时器中

因为我们不考虑横竖屏切换场景,所以在 AndroidMainfest 中直接为启动页 Activityandroid:screenOrientation="portrait"

   <activityandroid:name=".loading.SplashActivity"android:exported="true"android:screenOrientation="portrait"android:theme="@style/SplashTheme"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity>

实现方式

package cn.xxxximport android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.CountDownTimer
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.*
import me.jessyan.autosize.internal.CancelAdapt
import timber.log.Timber
import javax.inject.Inject@SuppressLint("CustomSplashScreen")
@AndroidEntryPoint
class SplashActivity : AppCompatActivity(), CancelAdapt {lateinit var tvJump: TextView// 倒计时长var remainingTimeInMillis: Long? = null//计时器private var countDownTimer: CountDownTimer? = null@SuppressLint("SourceLockedOrientationActivity", "MissingInflatedId")override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_splash_l)//右上角跳过的视图tvJump = findViewById<TextView>(R.id.tv_jump)//初始化为计时器时间remainingTimeInMillis = 5 * 1000//跳过逻辑tvJump.onClick {countDownTimer?.cancel()next()}}override fun onResume() {super.onResume()//之所以写是因为在项目里广告时间是后台返回,如果只是单纯固定时长可去除该判断if (!remainingTimeInMillis.isNull()) {// Activity回到前台时,检查剩余时间if ((remainingTimeInMillis ?: 0) <= 0) {// 如果时间已经耗尽,直接跳转next()} else {// 如果时间还有剩余,重新启动一个计时器,从剩余时间开始startTimer(remainingTimeInMillis ?: 0)}}}override fun onPause() {super.onPause()// Activity进入后台时,立即取消计时器(防止onFinish在后台被调用),remainingTimeInMillis还保存着最新的剩余时间countDownTimer?.cancel()}//计时器private fun startTimer(time: Long) {countDownTimer?.cancel()countDownTimer = object : CountDownTimer(time, 1000) {override fun onTick(millisUntilFinished: Long) {//实时更新剩余的倒计时长remainingTimeInMillis = millisUntilFinishedmainHandler.post { tvJump.text = "${millisUntilFinished / 1000 + 1}s跳过" }}override fun onFinish() {//倒计时结束,进入对应逻辑next()}}.start()}override fun onDestroy() {super.onDestroy()countDownTimer?.cancel()countDownTimer = null}private fun next() {//可自行根据业务场景,决定跳转逻辑	// 以下为项目伪代码:判断是否首次登录,运行过帮助引导val landingVersion = SPUtils.AppSP().get(ConstValue.VER_IS_THIS_VERSION_OPEN_BEFORE, 0) as? Int?if ((landingVersion ?: 0) >= 4) {RouterPath.APP_MAIN_ACT //首页} else {RouterPath.MAIN_LANDING_ACT //引导页}.also {startRouterAndFinish(it) { putBoolean("firstStart", true) }}}
}

activity_splash_l

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/white"android:orientation="vertical"tools:ignore="MissingDefaultResource"><ImageViewandroid:id="@+id/background_type_1"android:layout_width="match_parent"android:layout_height="match_parent"android:scaleType="fitXY"android:src="@drawable/drawable_app_launch"android:visibility="visible" /><RelativeLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="right"android:layout_marginTop="62dp"android:layout_marginEnd="15dp"android:background="@drawable/shape_splash_btn_jump_bg"android:paddingHorizontal="12dp"android:paddingVertical="4dp"><TextViewandroid:id="@+id/tv_jump"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="跳过"android:textColor="#FFFFFF"android:textSize="12dp" /></RelativeLayout>
</FrameLayout>

AI提供方案

自从使用AI后,觉得很多基础性的知识没有了记Blog的必要,更多的可能还行记录项目中遇到的问题

CountDownTimer + 生命周期感知

考虑到了横竖屏场景,兼容场景更多一些

核心要点

  1. onPause()中取消计时器:阻止它在后台触发 onFinish()跳转。
  2. 保存剩余时间:在 onTick()中持续更新 remainingTimeInMillis变量。
  3. onResume()中恢复计时:根据保存的剩余时间重新开始计时。如果时间已到,直接跳转。
  4. 处理配置变更:通过 onSaveInstanceState保存数据,防止屏幕旋转等问题。
class CountdownActivity : AppCompatActivity() {private var countDownTimer: CountDownTimer? = nullprivate var remainingTimeInMillis: Long = 10000 // 总计时时间,例如10秒private val totalTimeInMillis: Long = 10000 // 保存总时间用于恢复override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_countdown)// 从保存的状态中恢复时间(防止旋转屏幕等配置变更)if (savedInstanceState != null) {remainingTimeInMillis = savedInstanceState.getLong("REMAINING_TIME", totalTimeInMillis)}startCountdown(remainingTimeInMillis)}override fun onSaveInstanceState(outState: Bundle) {super.onSaveInstanceState(outState)// 保存当前剩余时间,防止配置变更(如屏幕旋转)导致时间重置outState.putLong("REMAINING_TIME", remainingTimeInMillis)}private fun startCountdown(millisInFuture: Long) {// 每次启动新计时器前,取消旧的countDownTimer?.cancel()countDownTimer = object : CountDownTimer(millisInFuture, 1000) {override fun onTick(millisUntilFinished: Long) {// 更新UI,显示剩余时间remainingTimeInMillis = millisUntilFinishedval seconds = millisUntilFinished / 1000textView_countdown.text = "剩余时间: ${seconds}秒"}override fun onFinish() {// 只有在Activity处于前台时,才执行跳转逻辑proceedToNextStep()}}.start()}private fun proceedToNextStep() {// 执行你的下一步操作,例如跳转页面val intent = Intent(this, NextActivity::class.java)startActivity(intent)finish()}override fun onPause() {super.onPause()// Activity进入后台时,立即取消计时器(防止onFinish在后台被调用)countDownTimer?.cancel()// 注意:这里我们只是取消了计时器,并没有改变remainingTimeInMillis的值// 所以remainingTimeInMillis还保存着最新的剩余时间}override fun onResume() {super.onResume()// Activity回到前台时,检查剩余时间if (remainingTimeInMillis <= 0) {// 如果时间已经耗尽,直接跳转proceedToNextStep()} else {// 如果时间还有剩余,重新启动一个计时器,从剩余时间开始startCountdown(remainingTimeInMillis)}}override fun onDestroy() {super.onDestroy()// 彻底销毁Activity时,释放计时器资源countDownTimer?.cancel()}
}

ViewModel + LiveData

符合当下主流框架、组件,适用性、兼容性高,但是对于未使用过的朋友,需要一点时间学下组件

Android Architecture Components 架构组件

  • 组件化之路 - Lifecycle一知半解
  • 组件化之路 - LiveData一知半解
  • 组件化之路 - ViewModel一知半解
  • 组件化之路 - LiveData + ViewModel一知半解

优势

  • 生命周期感知: ViewModel独立于UI生命周期,配置变更时数据不会丢失。
  • 关注点分离:计时逻辑在 ViewModel中,UI控制只在Activity中。
  • 更健壮:使用 Coroutines处理后台任务,更加现代和安全。

创建 ViewModel

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launchclass CountdownViewModel : ViewModel() {private val _remainingTime = MutableLiveData<Long>()val remainingTime: LiveData<Long> = _remainingTimeprivate val _countdownFinished = MutableLiveData<Boolean>()val countdownFinished: LiveData<Boolean> = _countdownFinishedprivate var countdownJob: Job? = nullprivate var initialDuration: Long = 0Lfun startCountdown(duration: Long) {initialDuration = duration_remainingTime.value = duration_countdownFinished.value = falsecountdownJob?.cancel() // 取消之前的任务countdownJob = viewModelScope.launch {var timeLeft = durationwhile (timeLeft > 0 && isActive) {delay(1000)timeLeft -= 1000_remainingTime.postValue(timeLeft) // 使用postValue确保在主线程更新}if (isActive && timeLeft <= 0) {_countdownFinished.postValue(true)}}}fun pauseCountdown() {countdownJob?.cancel()}// 获取当前剩余时间,用于在UI层判断fun getCurrentTime(): Long = _remainingTime.value ?: initialDurationoverride fun onCleared() {super.onCleared()pauseCountdown()}
}

在 Activity/Fragment 中使用 ViewModel

class CountdownActivity : AppCompatActivity() {private lateinit var viewModel: CountdownViewModeloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_countdown)// 初始化ViewModelviewModel = ViewModelProvider(this).get(CountdownViewModel::class.java)// 观察剩余时间并更新UIviewModel.remainingTime.observe(this) { timeMillis ->val seconds = timeMillis / 1000textView_countdown.text = "剩余时间: ${seconds}秒"}// 观察倒计时是否结束viewModel.countdownFinished.observe(this) { isFinished ->if (isFinished) {proceedToNextStep()}}// 如果是第一次创建,开始计时if (savedInstanceState == null) {viewModel.startCountdown(10000)}}private fun proceedToNextStep() {val intent = Intent(this, NextActivity::class.java)startActivity(intent)finish()}override fun onPause() {super.onPause()// 进入后台时暂停计时viewModel.pauseCountdown()}override fun onResume() {super.onResume()val currentTime = viewModel.getCurrentTime()if (currentTime <= 0) {// 如果ViewModel中记录的时间已经用完,直接跳转proceedToNextStep()} else {// 否则,重新开始计时(从剩余时间开始)viewModel.startCountdown(currentTime)}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/96186.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/96186.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

无标记点动捕技术:重塑展厅展馆的沉浸式数字交互新时代

在元宇宙浪潮的持续推进下&#xff0c;虚拟数字人正逐渐成为连接虚实世界的重要媒介。在展厅展馆中&#xff0c;数字人不仅能够扮演导览员、讲解员角色&#xff0c;更可通过情感化交互提升参观体验&#xff0c;使文化传播更具感染力和沉浸感。虚拟人的引入&#xff0c;为传统展…

轻松Linux-7.Ext系列文件系统

天朗气清&#xff0c;惠风和煦&#xff0c;今日无事&#xff0c;遂来更新。 1.概述 总所周知&#xff0c;我们存的数据都是在一个叫硬盘的东西里面&#xff0c;这个硬盘又像个黑盒&#xff0c;这章就来简单解析一下Linux中文件系统。 现在我们用的大都是固态硬盘&#xff0c;…

Matlab机器人工具箱使用4 蒙特卡洛法绘制工作区间

原理&#xff1a;利用rand随机数&#xff0c;给各个关节设置随机关节变量&#xff0c;通过正运动学得到末端位姿变换矩阵&#xff0c;然后利用变换矩阵2三维坐标标记出末端坐标&#xff0c;迭代多次就可以构成点云。教程视频&#xff1a;【MATLAB机器人工具箱10.4 机械臂仿真教…

【项目】在AUTODL上使用langchain实现《红楼梦》知识图谱和RAG混合检索(三)知识图谱和路由部分

首先在数据集 - 开放知识图谱下载红楼梦的知识图谱&#xff0c;这个网站上有各种各样的知识图谱&#xff0c;可以挑你感兴趣的做( • ̀ω•́ ) 这个知识图谱的作者们已经将三元组抽取出来了&#xff0c;我们可以直接用&#xff0c;如果你对三元组是如何生成的感兴趣&#xf…

pycharm 最新版上一次编辑位置

2025nipycharm方法一&#xff1a;用快捷键&#xff08;最方便&#xff09;跳回上一次编辑位置&#xff1a;Windows/Linux: Ctrl Alt ←macOS: ⌘ Option ←跳到前一次位置&#xff1a;Windows/Linux: Ctrl Alt →macOS: ⌘ Option →方法二&#xff1a;显示工具栏按钮在…

前端性能监控与优化:从 Lighthouse 到 APM

在当今竞争激烈的数字环境中&#xff0c;用户对Web应用性能的要求日益提高。一个缓慢或响应迟钝的应用不仅会流失用户&#xff0c;更可能损害品牌形象和商业价值。因此&#xff0c;前端性能的监控与优化已成为前端开发不可或缺的关键环节。本文将深入探讨从基础的性能评估工具L…

TC_Motion多轴运动-电子齿轮

目录 电子齿轮 【基本概念】 【应用示例】 【开发总结】 END 电子齿轮 【基本概念】 定义:通过软件方法实现机械齿轮的速比调节功能(两个轴成线性比例旋转) 优点 免维护,告别机械损耗 易调节,任意修改齿轮比 精度高,无机械背隙 应用场景 多台电机拖动同一负载,要求多台…

CentOS 7 下载教程

访问阿里云镜像站 阿里巴巴开源镜像站 选择centos 点这个 选择7版本 进入isos目录 点这个 选择这个版本 因为这个镜像的日期更新 推荐下载 DVD 版&#xff1a;包含完整系统和常用软件&#xff0c;无需额外联网安装组件Minimal 版&#xff1a;精简版&#xff0c;仅包含基础系…

MAC在home下新建文件夹报错“mkdir: test: Operation not supported”

在Mac电脑中&#xff0c;home文件夹下不能直接mkdir&#xff0c;sudo 也还是不行&#xff0c;提示“mkdir: test: Operation not supported”。网上找的解决方案不好使&#xff0c;因为没有关闭系统完整性保护关闭系统完整性保护查看SIP当前的状态csrutil status如果是开启状态…

交叉导轨从测试仪到工作台的精密运动控制

在精密仪器领域微米级的运动精度与纳米级的稳定性往往是决定设备性能上限的核心指标。而支撑这一技术鸿沟跨越的&#xff0c;往往隐匿于机械结构的“毫厘之间”——交叉导轨。以下是其在不同精密仪器中的具体应用&#xff1a;光学测试仪&#xff1a;光学测试仪主要用于各种高精…

内网穿透的应用-Navidrome与cpolar本地搭建跨网络访问的云音乐服务器

文章目录前言1. 安装Docker2. 创建并启动Navidrome容器3. 公网远程访问本地Navidrome3.1 内网穿透工具安装3.2 创建远程连接公网地址3.3 使用固定公网地址远程访问前言 音乐收藏存在平台版权限制、音质压缩和访问不便等问题。Navidrome 开源音乐服务器与 cpolar 内网穿透服务的…

FastAPI 访问不了API文档或配置不生效的解决方法

FastAPI中文教程 本文背景 FastAPI框架自带交互式api文档,通过路由/docs或者/redoc 访问&#xff0c;但是FastAPI 的文档界面&#xff08;如 /docs 和 /redoc&#xff09;依赖于外部的 JavaScript 和 CSS 库&#xff0c;如果项目部署环境网络不佳或者无法访问外网的时候&…

IAR 集成开发环境入门指南:字体设置与调试实战

一、IAR 的基本使用教程1. IAR 颜色字体大小设置打开设置路径&#xff1a;点击顶部菜单栏 Tools → 选择 Options&#xff0c;打开 IDE 配置窗口。进入字体颜色设置界面&#xff1a;在弹出的 “IDE Options” 窗口中&#xff0c;双击展开 Editor 选项&#xff0c;然后点击 Colo…

10:00开始面试,10:06就出来了,问的问题有点变态。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到8月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40%,这…

Flink 状态管理的核心能力

我们来看一个复杂的实际案例&#xff1a;阿里巴巴菜鸟的实时物流追踪系统。 该系统处理来自多个电商平台&#xff08;天猫、淘宝、速卖通&#xff09;的订单包裹&#xff0c;通过一个复杂的处理流程&#xff1a; 合并与去重&#xff1a;通过聚合操作将不同来源的订单合并并去重…

基于go语言的云原生TodoList Demo 项目,验证云原生核心特性

以下是一个基于 Go 语言 的云原生 TodoList Demo 项目&#xff0c;涵盖 容器化、Kubernetes 编排、CI/CD、可观测性、弹性扩缩容 等核心云原生特性&#xff0c;代码简洁且附详细操作指南&#xff0c;适合入门学习。项目概览 目标&#xff1a;实现一个支持增删改查&#xff08;C…

手机能看、投屏 / 车机不能看与反向链接验证类似吗?

有一定关联&#xff0c;但两者的技术逻辑并非完全等同 ——“手机能看、投屏 / 车机不能看” 的核心原因更复杂&#xff0c;反向链接验证是其中一种可能的限制手段&#xff0c;但不是唯一甚至不是最主要的手段。要理清这个问题&#xff0c;需要先拆解 “投屏 / 车机播放受限” …

25年9月通信基础知识补充1:NTN-TDL信道建模matlab代码(satellite-communications toolbox学习)

看文献过程中不断发现有太多不懂的基础知识&#xff0c;故长期更新这类blog不断补充在这过程中学到的知识。由于这些内容与我的研究方向并不一定强相关&#xff0c;故记录不会很深入请见谅。 【通信基础知识补充10】25年9月通信基础知识补充1&#xff1a;NTN-TDL信道建模matlab…

洛谷P3370 【模板】字符串哈希 (哈希表)详解

题目如下&#xff1a;&#xff08;注&#xff1a;解此题我只需左手一根指头&#xff0c;哈哈哈哈哈哈哈&#xff09;注意&#xff0c;哈希表的好处是能大幅度减少寻找遍历的时间可能有人不理解哈希值&#xff0c; 这里哈希的模的值一般得是比较大的质数&#xff0c;如标准的100…

光子芯片驱动的胰腺癌早期检测:基于光学子空间神经网络的高效分割方法(未做完)

光子芯片驱动的胰腺癌早期检测:基于光学子空间神经网络的高效分割方法 1 论文核心概念 本文提出了一种基于集成光子芯片的光学子空间神经网络(Optical Subspace Neural Network, OSNN),用于胰腺癌的早期检测与图像分割。其核心思想是利用光子芯片的高并行性、低延迟和低能…