You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

安卓应用启动崩溃(<App Name>已停止)问题排查求助

应用启动崩溃(NullPointerException)排查与修复

问题概况

应用启动即崩溃,提示「The has Stopped」,多项目中均出现该问题。LogCat关键报错显示崩溃由MainActivityViewModel.kt第24行的NullPointerException引发,导致无法启动MainActivity。

报错根源分析

查看MainActivityViewModel.kt第24行代码:

timer = object : CountDownTimer(timerValue.value!!.toLong(),1000) {

核心问题:

  • timerValue是MutableLiveData<Long>,初始化时未设置默认值,默认value为null
  • 使用!!强制非空拆包,当timerValue.value为null时直接抛出NPE
  • MainActivity的onCreate方法中直接调用mAVieModel.startTimer(),此时用户尚未输入任何值,timerValue必然为空

另外存在潜在风险:timer用lateinit声明,但未做初始化判断,若用户先点击Stop按钮,会触发另一个NPE;原代码中CountDownTimer创建后未调用start(),即使修复NPE,定时器也不会运行。

修复方案

1. 给timerValue设置默认初始值

在ViewModel中初始化时为timerValue赋值,避免空值:

var timerValue = MutableLiveData<Long>(0L)

2. 安全处理timerValue的取值

修改startTimer方法,避免强制拆包,增加空值判断,同时补充定时器启动方法:

fun startTimer(){
    val targetTime = timerValue.value ?: return
    // 先取消已有定时器,避免重复创建
    if(::timer.isInitialized){
        timer.cancel()
    }
    timer = object : CountDownTimer(targetTime,1000) {
        override fun onTick(millisUntilFinished: Long) {
            val remainingTime = millisUntilFinished/1000
            remaining_seconds.value = remainingTime.toInt()
        }

        override fun onFinish() {
            _finished.value = true
        }
    }.start()
}

3. 移除Activity中提前调用的startTimer

删除MainActivity中onCreate里的mAVieModel.startTimer(),仅在用户点击Start按钮时启动定时器。

4. 安全处理stopTimer方法

给timer增加初始化判断,避免未初始化时调用cancel():

fun stopTimer (){
    if(::timer.isInitialized){
        timer.cancel()
    }
}

修改后的完整代码

MainActivityViewModel.kt

package com.shahid.timerlivedata

import android.os.CountDownTimer
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class MainActivityViewModel : ViewModel() {

    private lateinit var timer : CountDownTimer

    private val remaining_seconds = MutableLiveData<Int>()
    private val _finished = MutableLiveData<Boolean>()

    val finished : LiveData<Boolean>
        get() = _finished

    val seconds : LiveData<Int>
        get() = remaining_seconds

    var timerValue = MutableLiveData<Long>(0L) // 设置默认初始值

    fun startTimer(){
        val targetTime = timerValue.value ?: return
        // 先取消已有定时器,避免重复创建
        if(::timer.isInitialized){
            timer.cancel()
        }
        timer = object : CountDownTimer(targetTime,1000) {
            override fun onTick(millisUntilFinished: Long) {
                val remainingTime = millisUntilFinished/1000
                remaining_seconds.value = remainingTime.toInt()
            }

            override fun onFinish() {
                _finished.value = true
            }
        }.start() // 启动定时器
    }

    fun stopTimer (){
        if(::timer.isInitialized){
            timer.cancel()
        }
    }

    override fun onCleared() {
        super.onCleared()
        // ViewModel销毁时取消定时器,避免内存泄漏
        stopTimer()
    }
}

MainActivity.kt

package com.shahid.timerlivedata

import android.annotation.SuppressLint
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider

class MainActivity : AppCompatActivity() {
    @SuppressLint("MissingInflatedId")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_main)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }

        val txtCount = findViewById<TextView>(R.id.txtCount)
        val edtCount = findViewById<EditText>(R.id.edtCount)
        val btnStart = findViewById<Button>(R.id.btnStart)
        val btnStop = findViewById<Button>(R.id.btnStop)

        val mAVieModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)

        mAVieModel.seconds.observe(this) { seconds ->
            txtCount.text = "$seconds"
        }

        mAVieModel.finished.observe(this) { check ->
            if(check){
                Toast.makeText(this,"Time Completed!!", Toast.LENGTH_LONG).show()
            }
        }

        btnStart.setOnClickListener{
            val input = edtCount.text.toString()
            if(input.isEmpty()){
                Toast.makeText(this,"Please Fill the Time", Toast.LENGTH_LONG).show()
            }else{
                try {
                    val seconds = input.toLong()
                    mAVieModel.timerValue.value = seconds * 1000
                    mAVieModel.startTimer()
                } catch (e: NumberFormatException) {
                    Toast.makeText(this,"Please enter a valid number", Toast.LENGTH_LONG).show()
                }
            }
        }

        btnStop.setOnClickListener{
            mAVieModel.stopTimer()
        }
    }
}

额外优化:增加了输入格式异常捕获,避免用户输入非数字时崩溃;修正了控件类型强转错误(原代码把Button转成TextView)

内容的提问来源于stack exchange,提问作者Shahid Faried

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 01:38:12