应用程序关闭时的 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
您需要 onResume
、onPause
和 onDestroy
,以便涵盖所有场景os。
对于onResume
,原因是,当您将您的应用程序放到background
,并通过将其设为foreground
来恢复该应用程序时,可以进一步应用操作,例如, 从 SharedPreferences 获取最后保存的状态以确保条件被执行。
对于onPause
,这是至关重要的,因为当你点击你的phone主页按钮时,这个状态将被执行。因此,它确保在销毁之前保存所有状态(保险)。
对于onDestroy
来说,是most的关键部分,因为对于一些低端的phone来说,资源有限,os就可以了'cleaning'通过杀死应用程序,所以对于你的应用程序,它会被杀死,所以在它被杀死之前,你可以保存状态。
因此,每当您启动或使用您的应用程序时,请查询 SharedPreferences
,并进行一些计算以确保一切正确。
使用 service
或 broadcast
,这允许您的程序 运行 后台。
文档中的一些详细解释:
onResume()
当activity进入Resumed状态时,它来到前台,然后系统调用onResume()回调。这是应用程序与用户交互的状态。该应用程序会一直保持这种状态,直到发生某些事情使焦点从该应用程序上移开。例如,此类事件可能是接到 phone 电话、用户导航到另一个 activity 或设备屏幕关闭。
当 activity 进入恢复状态时,任何与 activity 的生命周期相关的生命周期感知组件都将收到 ON_RESUME
事件。这是生命周期组件可以在组件可见并位于前台时启用需要 运行 的任何功能的地方,例如启动相机预览。
当中断事件发生时,activity进入暂停状态,系统调用onPause()
回调。
如果activityreturns从Paused状态到Resumed状态,系统会再次调用onResume()
方法。因此,您应该实施 onResume()
来初始化您在 onPause()
期间释放的组件,并执行每次 activity 进入恢复状态时必须发生的任何其他初始化。
onPause
系统调用此方法作为用户离开您的 activity 的第一个指示(尽管这并不总是意味着 activity 正在被销毁);它表示 activity 不再位于前台(尽管如果用户处于 multi-window 模式,它可能仍然可见)。使用 onPause() 方法暂停或调整在 Activity 处于 Paused 状态时不应继续(或应适度继续)并且您希望很快恢复的操作。 activity 可能会进入此状态的原因有多种。
onDestroy()
onDestroy()
在 activity 被销毁之前调用。系统调用此回调是因为:
- activity 正在完成(由于用户完全关闭了
activity 或由于在 activity 上调用了 finish()),或
- 系统暂时破坏 activity 由于
配置更改(例如设备旋转或多 window 模式)
当 activity 进入销毁状态时,任何与 activity 的生命周期相关的生命周期感知组件都将收到 ON_DESTROY 事件。这是生命周期组件可以在 Activity 被销毁之前清理它需要的任何东西的地方。
我制作了一个 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
您需要 onResume
、onPause
和 onDestroy
,以便涵盖所有场景os。
对于onResume
,原因是,当您将您的应用程序放到background
,并通过将其设为foreground
来恢复该应用程序时,可以进一步应用操作,例如, 从 SharedPreferences 获取最后保存的状态以确保条件被执行。
对于onPause
,这是至关重要的,因为当你点击你的phone主页按钮时,这个状态将被执行。因此,它确保在销毁之前保存所有状态(保险)。
对于onDestroy
来说,是most的关键部分,因为对于一些低端的phone来说,资源有限,os就可以了'cleaning'通过杀死应用程序,所以对于你的应用程序,它会被杀死,所以在它被杀死之前,你可以保存状态。
因此,每当您启动或使用您的应用程序时,请查询 SharedPreferences
,并进行一些计算以确保一切正确。
使用 service
或 broadcast
,这允许您的程序 运行 后台。
文档中的一些详细解释:
onResume()
当activity进入Resumed状态时,它来到前台,然后系统调用onResume()回调。这是应用程序与用户交互的状态。该应用程序会一直保持这种状态,直到发生某些事情使焦点从该应用程序上移开。例如,此类事件可能是接到 phone 电话、用户导航到另一个 activity 或设备屏幕关闭。
当 activity 进入恢复状态时,任何与 activity 的生命周期相关的生命周期感知组件都将收到 ON_RESUME
事件。这是生命周期组件可以在组件可见并位于前台时启用需要 运行 的任何功能的地方,例如启动相机预览。
当中断事件发生时,activity进入暂停状态,系统调用onPause()
回调。
如果activityreturns从Paused状态到Resumed状态,系统会再次调用onResume()
方法。因此,您应该实施 onResume()
来初始化您在 onPause()
期间释放的组件,并执行每次 activity 进入恢复状态时必须发生的任何其他初始化。
onPause
系统调用此方法作为用户离开您的 activity 的第一个指示(尽管这并不总是意味着 activity 正在被销毁);它表示 activity 不再位于前台(尽管如果用户处于 multi-window 模式,它可能仍然可见)。使用 onPause() 方法暂停或调整在 Activity 处于 Paused 状态时不应继续(或应适度继续)并且您希望很快恢复的操作。 activity 可能会进入此状态的原因有多种。
onDestroy()
onDestroy()
在 activity 被销毁之前调用。系统调用此回调是因为:
- activity 正在完成(由于用户完全关闭了 activity 或由于在 activity 上调用了 finish()),或
- 系统暂时破坏 activity 由于 配置更改(例如设备旋转或多 window 模式)
当 activity 进入销毁状态时,任何与 activity 的生命周期相关的生命周期感知组件都将收到 ON_DESTROY 事件。这是生命周期组件可以在 Activity 被销毁之前清理它需要的任何东西的地方。