在没有服务的情况下始终保持计时器 运行

Keep the chronometer always running without a service

我正在使用计时器,它需要一直运行直到停止。

我在下面使用的代码运行良好,计时器保持 运行ning,但是,当设备重新启动时,我得到负值。

SharedPreferences sharedPreferences = getSharedPreferences("chronometer", MODE_PRIVATE);

    //Check if the start time was saved before
    long elapsedTime = sharedPreferences.getLong("elapsed_time", 0);
    
    if (elapsedTime != 0) {
        //It means the chronometer was running before, so we set the time from SharedPreferences
        chronometer.setBase(elapsedTime);
        chronometer.start();
    }

    //Code below starts the chronometer, after the user manually starts it

    Button startButton = findViewById(R.id.btnStart);
    startButton.setOnClickListener(v -> {
        //Save the starting time
        long elapsedRealtime = SystemClock.elapsedRealtime();
        sharedPreferences.edit().putLong("elapsed_time", elapsedRealtime).apply();

        chronometer.setBase(SystemClock.elapsedRealtime());
        chronometer.start();
    });

为什么天文钟在重启后显示负值,我该如何解决?

您应该将 elapsedMillis 保存到共享首选项中,当设备重新启动并设置计时表的基础时,您应该执行以下操作


chronometer.setBase(SystemClock.elapsedRealtime() - millis)

其中 millis 是您在首选项中的值

来自 Android Documentation

Chronometer

Class that implements a simple timer.

You can give it a start time in the SystemClock#elapsedRealtime timebase, and it counts up from that.

这个class的时基基于SystemClock.elapsedRealtime()

elapsedRealtime

public static long elapsedRealtime()

Returns milliseconds since boot, including time spent in sleep.

回到你的问题:

Why does the chronometer display negative values after a re-boot?

下面是一个演示此场景的示例。

假设你的 phone 是 运行 距离上次启动大约 10 分钟,此时用户打开 activity,elapsedRealtime() 方法将 return 600000L(10 分钟 = 10 * 60 * 1000 毫秒)。当用户单击“开始”按钮时,您将 Chronometer 的时间设置为基准时间,并使用 elapsed_time 键将 600000L 保存到 SharePreferences。

  • 现在:elapsedRealTime() = 10 分钟 |计时器 = 00:00

  • 1 分钟后:elapsedRealtime() = 11 分钟 |计时器 = 01:00

  • 10 分钟后:elapsedRealTime() = 20 分钟 |计时器 = 10:00

此时用户按返回键退出应用并重启phone。

phone 有点慢,所以从启动到花了 2 分钟。用户再次打开 activity。此时。

  • elapsedRealtime() returns 120000L(2 分钟 = 2 * 60 * 1000 毫秒)

  • 您在 SharePreferences (elapsed_time) 中保存的值为 600000L

因为您设置的 Chronometer basetime 的值早于 elapsedRealtime(),所以 Chronometer 将显示 -08:00 并从该值开始向上计数。

How can I sort it out?

您可以保存 Chronometer 的运行时间以及从用户上次离开 activity 到用户下次打开 activity 的运行时间。然后使用这两个值来计算 Chronometer 的基准时间。

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <Chronometer
        android:id="@+id/chronometer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="20dp"
        android:textColor="#F00"
        android:textSize="24sp" />

    <Button
        android:id="@+id/btnStart"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Start" />
</LinearLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private static final String KEY_CHRONOMETER_ELAPSED_TIME = "chronometerElapsedTime";
    private static final String KEY_CHRONOMETER_STOPPED_TIME = "chronometerStoppedTime";

    private Chronometer chronometer;
    private SharedPreferences prefs;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        chronometer = findViewById(R.id.chronometer);

        // Code below starts the chronometer, after the user manually starts it
        Button startButton = findViewById(R.id.btnStart);
        startButton.setOnClickListener(v -> {
            setElapsedTime(-1);
            setStoppedTime(-1);
            chronometer.setBase(SystemClock.elapsedRealtime());
            chronometer.start();
        });
    }

    @Override
    protected void onStart() {
        super.onStart();
        prefs = getSharedPreferences("chronometer", MODE_PRIVATE);
        if (prefs.contains(KEY_CHRONOMETER_ELAPSED_TIME)
                && prefs.contains(KEY_CHRONOMETER_STOPPED_TIME)) {
            long chronometerElapsedTime = prefs.getLong(KEY_CHRONOMETER_ELAPSED_TIME, -1);
            long chronometerStoppedTime = prefs.getLong(KEY_CHRONOMETER_STOPPED_TIME, -1);
            if (chronometerElapsedTime != -1 && chronometerStoppedTime != -1) {
                long now = System.currentTimeMillis();
                long elapsedTimeFromLastStop = now - chronometerStoppedTime; // Including restart time

                long elapsedRealTime = SystemClock.elapsedRealtime();
                long base = elapsedRealTime - (chronometerElapsedTime + elapsedTimeFromLastStop);

                chronometer.setBase(base);
                chronometer.start();
            }
        }
    }

    @Override
    protected void onStop() {
        setElapsedTime(getChronometerTimeMs());
        setStoppedTime(System.currentTimeMillis());
        super.onStop();
    }

    private void setElapsedTime(long elapsedTimeMs) {
        prefs.edit().putLong(KEY_CHRONOMETER_ELAPSED_TIME, elapsedTimeMs).apply();
    }

    private void setStoppedTime(long stoppedTimeMs) {
        prefs.edit().putLong(KEY_CHRONOMETER_STOPPED_TIME, stoppedTimeMs).apply();
    }

    private long getChronometerTimeMs() {
        long chronometerTimeMs = 0;

        // Regex for HH:MM:SS or MM:SS
        String regex = "([0-1]?\d|2[0-3])(?::([0-5]?\d))?(?::([0-5]?\d))?";

        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(chronometer.getText());
        if (matcher.find()) {
            boolean isHHMMSSFormat = matcher.groupCount() == 4;
            if (isHHMMSSFormat) {
                int hour = Integer.valueOf(matcher.group(1));
                int minute = Integer.valueOf(matcher.group(2));
                int second = Integer.valueOf(matcher.group(3));
                chronometerTimeMs = (hour * DateUtils.HOUR_IN_MILLIS)
                        + (minute * DateUtils.MINUTE_IN_MILLIS)
                        + (second * DateUtils.SECOND_IN_MILLIS);
            } else {
                int minute = Integer.valueOf(matcher.group(1));
                int second = Integer.valueOf(matcher.group(2));
                chronometerTimeMs = (minute * DateUtils.MINUTE_IN_MILLIS)
                        + (second * DateUtils.SECOND_IN_MILLIS);
            }
        }

        return chronometerTimeMs;
    }
}