应用程序关闭时的 CountDownTimer 问题

CountDownTimer problem when app is closed

我制作了一个 CountDownTimer 代码,我想在倒计时结束时重新启动 CountDownTimer,即使应用已关闭,但它只会在应用 运行 或应用重新启动时重新启动。因此,如果我在倒计时为 00:10(分钟:秒)时关闭应用程序并在 30 秒后重新打开应用程序,计数器应该为 00:40,但它从 1 分钟开始......但是如果我关闭00:40 中的应用程序并在 10 秒后重新打开,它从 00:30 开始所以它很好,但问题仅在应用程序关闭时从 1 分钟重新启动....有人可以帮助我吗?

我的代码:

package com.example.countdown_implement;

import android.content.SharedPreferences;
import android.os.CountDownTimer;
import android.os.Bundle;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import java.util.Locale;

public class MainActivity extends AppCompatActivity {
    private static final long START_TIME_IN_MILLIS = 60000;
    private TextView mTextViewCountDown;
    private CountDownTimer mCountDownTimer;
    private boolean mTimerRunning;
    private long mTimeLeftInMillis;
    private long mEndTime;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mTextViewCountDown = findViewById(R.id.text_view_countdown);

}

private void startTimer() {
    mEndTime = System.currentTimeMillis() + mTimeLeftInMillis;

    mCountDownTimer = new CountDownTimer(mTimeLeftInMillis, 1000) {
        @Override
        public void onTick(long millisUntilFinished) {
            mTimeLeftInMillis = millisUntilFinished;
            updateCountDownText();
        }

        @Override
        public void onFinish() {
            //mTimerRunning = false;
            //updateButtons();

            updateCountDownText();
            resetTimer();
            startTimer();

        }
    }.start();

    //mTimerRunning = true;

}


private void resetTimer() {
    mTimeLeftInMillis = START_TIME_IN_MILLIS;
    updateCountDownText();

}

private void updateCountDownText() {
    int minutes = (int) (mTimeLeftInMillis / 1000) / 60;
    int seconds = (int) (mTimeLeftInMillis / 1000) % 60;

    String timeLeftFormatted = String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds);

    mTextViewCountDown.setText(timeLeftFormatted);
}


@Override
protected void onStop() {
    super.onStop();

    SharedPreferences prefs = getSharedPreferences("prefs", MODE_PRIVATE);
    SharedPreferences.Editor editor = prefs.edit();

    editor.putLong("millisLeft", mTimeLeftInMillis);
    editor.putBoolean("timerRunning", mTimerRunning);
    editor.putLong("endTime", mEndTime);

    editor.apply();

}

@Override
protected void onStart() {
    super.onStart();

    SharedPreferences prefs = getSharedPreferences("prefs", MODE_PRIVATE);

    mTimeLeftInMillis = prefs.getLong("millisLeft", START_TIME_IN_MILLIS);
    mTimerRunning = prefs.getBoolean("timerRunning", false);
    mEndTime = prefs.getLong("endTime", 0);
    mTimeLeftInMillis = mEndTime - System.currentTimeMillis();

    updateCountDownText();
    startTimer();
    if (mTimeLeftInMillis < 0) {
        updateCountDownText();
        startTimer();
    }
  }
}

已更新

以下是您的代码转换为 CountdownTimer 的代码片段,即使在应用程序关闭、推送到后台或重新启动时,它也会继续工作。

设置START_TIME_IN_MILLIS为定时器开始时间,在下面的例子中设置为15秒。

import android.content.SharedPreferences;
import android.os.CountDownTimer;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import java.util.Locale;

public class MainActivity2 extends AppCompatActivity {
    private static final long START_TIME_IN_MILLIS = 15000;
    private TextView mTextViewCountDown;
    private CountDownTimer mCountDownTimer;
    private boolean mTimerRunning;
    private long mTimeLeftInMillis;
    private long mEndTime;
    private long remainingTimeInMillis;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_new);

        mTextViewCountDown = findViewById(R.id.tv);
    }

    private void startTimer() {
        mCountDownTimer = new CountDownTimer(remainingTimeInMillis, 1000) {
            @Override
            public void onTick(long millisUntilFinished) {
                remainingTimeInMillis = millisUntilFinished;
                mTimeLeftInMillis = millisUntilFinished;
                updateCountDownText();
            }

            @Override
            public void onFinish() {
                //mTimerRunning = false;
                //updateButtons();

                updateCountDownText();
                resetTimer();
                startTimer();

            }
        }.start();

        //mTimerRunning = true;

    }


    private void resetTimer() {
        remainingTimeInMillis = START_TIME_IN_MILLIS;
        updateCountDownText();

    }

    private void updateCountDownText() {


        int minutes = (int) (remainingTimeInMillis / 1000) / 60;
        int seconds = (int) (remainingTimeInMillis / 1000) % 60;

        String timeLeftFormatted = String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds);

        mTextViewCountDown.setText(timeLeftFormatted);
    }


    @Override
    protected void onStop() {
        super.onStop();

        SharedPreferences prefs = getSharedPreferences("prefs", MODE_PRIVATE);
        SharedPreferences.Editor editor = prefs.edit();

        editor.putLong("millisLeft", mTimeLeftInMillis);
        editor.putBoolean("timerRunning", mTimerRunning);
        editor.putLong("endTime", System.currentTimeMillis());
        editor.apply();

    }

    @Override
    protected void onStart() {
        super.onStart();

        SharedPreferences prefs = getSharedPreferences("prefs", MODE_PRIVATE);

        mTimeLeftInMillis = prefs.getLong("millisLeft", START_TIME_IN_MILLIS);
        mTimerRunning = prefs.getBoolean("timerRunning", false);
        mEndTime = prefs.getLong("endTime", 0);
        if (mEndTime == 0L) {
            remainingTimeInMillis = (mTimeLeftInMillis);
        } else {
            Long timeDiff = (mEndTime - System.currentTimeMillis());
            //to convert into positive number
            timeDiff = Math.abs(timeDiff);

            long timeDiffInSeconds = (timeDiff / 1000) % 60;
           long timeDiffInMillis = timeDiffInSeconds * 1000;
            Long timeDiffInMillisPlusTimerRemaining = remainingTimeInMillis = mTimeLeftInMillis - timeDiffInMillis;

            if (timeDiffInMillisPlusTimerRemaining < 0) {
                timeDiffInMillisPlusTimerRemaining = Math.abs(timeDiffInMillisPlusTimerRemaining);
                remainingTimeInMillis = START_TIME_IN_MILLIS - timeDiffInMillisPlusTimerRemaining;
            }
        }
        updateCountDownText();
        startTimer();
    }
}

先看这里:Understand the Activity Lifecycle

您需要 onResumeonPauseonDestroy,以便涵盖所有场景os。

对于onResume,原因是,当您将您的应用程序放到background,并通过将其设为foreground来恢复该应用程序时,可以进一步应用操作,例如, 从 SharedPreferences 获取最后保存的状态以确保条件被执行。

对于onPause,这是至关重要的,因为当你点击你的phone主页按钮时,这个状态将被执行。因此,它确保在销毁之前保存所有状态(保险)。

对于onDestroy来说,是most的关键部分,因为对于一些低端的phone来说,资源有限,os就可以了'cleaning'通过杀死应用程序,所以对于你的应用程序,它会被杀死,所以在它被杀死之前,你可以保存状态。

因此,每当您启动或使用您的应用程序时,请查询 SharedPreferences,并进行一些计算以确保一切正确。

使用 servicebroadcast,这允许您的程序 运行 后台。

文档中的一些详细解释:

  1. onResume()

当activity进入Resumed状态时,它来到前台,然后系统调用onResume()回调。这是应用程序与用户交互的状态。该应用程序会一直保持这种状态,直到发生某些事情使焦点从该应用程序上移开。例如,此类事件可能是接到 phone 电话、用户导航到另一个 activity 或设备屏幕关闭。

当 activity 进入恢复状态时,任何与 activity 的生命周期相关的生命周期感知组件都将收到 ON_RESUME 事件。这是生命周期组件可以在组件可见并位于前台时启用需要 运行 的任何功能的地方,例如启动相机预览。

当中断事件发生时,activity进入暂停状态,系统调用onPause()回调。

如果activityreturns从Paused状态到Resumed状态,系统会再次调用onResume()方法。因此,您应该实施 onResume() 来初始化您在 onPause() 期间释放的组件,并执行每次 activity 进入恢复状态时必须发生的任何其他初始化。

  1. onPause

系统调用此方法作为用户离开您的 activity 的第一个指示(尽管这并不总是意味着 activity 正在被销毁);它表示 activity 不再位于前台(尽管如果用户处于 multi-window 模式,它可能仍然可见)。使用 onPause() 方法暂停或调整在 Activity 处于 Paused 状态时不应继续(或应适度继续)并且您希望很快恢复的操作。 activity 可能会进入此状态的原因有多种。

  1. onDestroy()

onDestroy() 在 activity 被销毁之前调用。系统调用此回调是因为:

  1. activity 正在完成(由于用户完全关闭了 activity 或由于在 activity 上调用了 finish()),或
  2. 系统暂时破坏 activity 由于 配置更改(例如设备旋转或多 window 模式)

当 activity 进入销毁状态时,任何与 activity 的生命周期相关的生命周期感知组件都将收到 ON_DESTROY 事件。这是生命周期组件可以在 Activity 被销毁之前清理它需要的任何东西的地方。

看这里:Background Execution Limits