【问题标题】:CountDownTimer problem when app is closed关闭应用程序时的CountDownTimer问题
【发布时间】:2020-03-28 14:47:21
【问题描述】:

我制作了一个 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();
    }
  }
}

【问题讨论】:

    标签: java android countdowntimer


    【解决方案1】:

    更新

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

    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();
        }
    }
    

    【讨论】:

    • 非常感谢。是否可以在不通知的情况下进行前台服务?我想做闲置游戏,但是我看到其他一些类似的游戏没有通知,例如“Simcity buildit”或“Holiday city”等……只需关闭应用程序,计时器就会在没有任何通知的情况下运行。
    • 如果游戏可以做到,你也可以:)。实际上我们把问题复杂化了。更新的答案包含有效的解决方案。你不需要任何类型的服务。
    • 其实你没有正确计算时差。
    • 非常感谢。现在效果很好:) 你帮了我很多,抱歉,我是新成员,我不能提供代表,但我会做的,当我的游戏发布时,我会给你一些 $$$。 :) 再次感谢。我将分析您给我的新代码,并尝试学习。谢谢
    • 做到了。谢谢:)
    【解决方案2】:

    首先,看看这里:Understand the Activity Lifecycle

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

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

    对于onPause来说,这很关键,因为当你点击手机的home键时,这个状态就会被执行。因此,它确保在销毁之前保存所有状态(保险)。

    对于onDestroy,这是最关键的部分,因为对于一些低端手机,资源有限,操作系统会通过杀死应用程序来进行“清理”,所以对于你的应用程序,它会被杀死,所以在它被杀死之前,你可以保存状态。

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

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

    文档中的一些详细解释:

    1. onResume()

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

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

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

    如果活动从暂停状态返回到恢复状态,系统再次调用onResume()方法。出于这个原因,您应该实现onResume() 来初始化您在onPause() 期间释放的组件,并执行每次活动进入Resumed 状态时必须发生的任何其他初始化。

    1. onPause

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

    1. onDestroy()

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

    1. 活动正在结束(由于用户完全关闭 活动或由于在活动上调用了完成()),或
    2. 系统正在临时销毁活动,原因是 配置更改(例如设备旋转或多窗口模式)

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

    看看这里:Background Execution Limits

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-12
      • 2013-03-09
      • 1970-01-01
      相关资源
      最近更新 更多