System.currentTimeMillis()を使って、ミリ秒でカウントするストップウォッチ・タイマーを作ってみます。TimerTaskを使ったカウントアップタイマーと同じようにrun()やHandlerのpostを使います。
2021.2.1
System.currentTimeMillis()
java.lang.System.currentTimeMillis() は
ミリ秒で表される現在の時間を返します
Ref: currentTimeMillis() – java.lang.System クラス
これを使って
カウント時間 = 経過時間 – 開始時間
という形でタイマーを作ります
計測間隔は
public static void sleep(long millis) throws InterruptedException
を使って
Thread.sleep(10);
のようにミリ秒のウェイトをかけて表示させるようにします
但し、これはパフォーマンスに影響する可能性があるので長時間の計測はやめたほうがいいかもしれません。
ミリsecから 分:秒.ミリ秒 へ変換
時間の表示を [分:秒.ミリ秒] で表現したいときには
SimpleDateFormatを使うと簡単です
ミリ秒で表されるmillisecがあるとすると、分:秒.ミリ秒(2桁)
1 2 3 |
SimpleDateFormat dataFormat = new SimpleDateFormat("mm:ss.SS", Locale.US); String time = dataFormat.format(millisec); |
但し、これは時刻を表示するものを便宜的に使ったのでLocaleの設定をしないとWarningが出ます。
年月日も追加してみるとわかりますが、startしてからの時間計測はこの例では、起点である1970/1/1からの時間ということになってしまいます。
あるいは、以下のように計算でも出せます。
1 2 3 4 5 |
long hh = millisec / 1000 / 60 % 24; // 時 long mm = millisec / 1000 / 60; // 分 long ss = millisec / 1000 % 60; // 秒 long ms = (millisec - mm * 1000 * 60 - ss * 1000); // 残りのミリ秒 String time = String.format("%1$02d:%2$02d:%3$03d.%4$03d", hh, mm, ss, ms); |
今回は簡単にSimpleDateFormatを使います。
サンプルコード
以上の事を踏まえて簡単なサンプルを作ってみましょう。
MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
//package com.example.testsystemcurrenttimemillis; import androidx.appcompat.app.AppCompatActivity; import android.os.Handler; import android.os.Bundle; import android.os.Looper; import android.view.View; import android.widget.Button; import android.widget.TextView; import java.text.SimpleDateFormat; import java.util.Locale; public class MainActivity extends AppCompatActivity implements Runnable, View.OnClickListener { private long startTime; private TextView timerText; private Button startButton; // 'Handler()' is deprecated as of API 30: Android 11.0 (R) private final Handler handler = new Handler(Looper.getMainLooper()); private volatile boolean stopRun = false; private final SimpleDateFormat dataFormat = new SimpleDateFormat("mm:ss.SS", Locale.JAPAN); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); timerText = findViewById(R.id.timer); timerText.setText(dataFormat.format(0)); startButton = findViewById(R.id.start_button); startButton.setOnClickListener(this); Button stopButton = findViewById(R.id.stop_button); stopButton.setOnClickListener(this); } @Override public void onClick(View v) { Thread thread; if (v == startButton){ stopRun = false; thread = new Thread(this); thread.start(); startTime = System.currentTimeMillis(); } else{ stopRun = true; timerText.setText(dataFormat.format(0)); } } @Override public void run() { // 10 msec order int period = 10; while (!stopRun) { // sleep: period msec try { Thread.sleep(period); } catch (InterruptedException e) { e.printStackTrace(); stopRun = true; } handler.post(() -> { long endTime = System.currentTimeMillis(); // カウント時間 = 経過時間 - 開始時間 long diffTime = (endTime - startTime); timerText.setText(dataFormat.format(diffTime)); }); } } } |
レイアウトは
activity_main.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="20dp" android:orientation="vertical" android:gravity="center" android:background="@color/cardview_light_background" tools:context=".MainActivity"> <TextView android:id="@+id/timer" android:layout_margin="20dp" android:textSize="60sp" android:textColor="#00f" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:text="@string/start" android:id="@+id/start_button" android:layout_margin="10dp" android:layout_width="match_parent" android:layout_height="wrap_content" /> <Button android:text="@string/stop" android:id="@+id/stop_button" android:layout_margin="10dp" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout> |
リソースです
strings.xml
1 2 3 4 5 |
<resources> <string name="app_name">YourAppName</string> <string name="start">Start</string> <string name="stop">Stop</string> </resources> |
ミリsecの表示について考察
ミリ秒の1の位まで表示させたいのですが、エミュレータでは10の位で切られています
System.currentTimeMillis()の精度はPCに依存して10msec以下は測定できない場合もあるようです
java.lang.System クラス
検証のためsleepを7msecでLogを取ってみると
private int period = 7;
Log.d(“time”, String.valueOf(endTime));
Emyulator:
1 2 3 4 5 6 7 8 9 |
01-17 05:14:00.400 2202-2202/? D/time: 1453007640400 01-17 05:14:00.420 2202-2202/? D/time: 1453007640420 01-17 05:14:00.440 2202-2202/? D/time: 1453007640440 01-17 05:14:00.460 2202-2202/? D/time: 1453007640460 01-17 05:14:00.480 2202-2202/? D/time: 1453007640480 01-17 05:14:00.500 2202-2202/? D/time: 1453007640500 01-17 05:14:00.520 2202-2202/? D/time: 1453007640520 01-17 05:14:00.540 2202-2202/? D/time: 1453007640540 01-17 05:14:00.560 2202-2202/? D/time: 1453007640560 |
10msecで切られていて、かつ間隔は20msecとなっています
Nexus7:
1 2 3 4 5 6 7 |
01-17 14:18:32.590 716-716/? D/time: 1453007912590 01-17 14:18:32.608 716-716/? D/time: 1453007912608 01-17 14:18:32.609 716-716/? D/time: 1453007912608 01-17 14:18:32.610 716-716/? D/time: 1453007912609 01-17 14:18:32.624 716-716/? D/time: 1453007912623 01-17 14:18:32.625 716-716/? D/time: 1453007912624 01-17 14:18:32.640 716-716/? D/time: 1453007912640 |
ミリsecのオーダーで数値が出ていますが
間隔は10msec以上もありバラバラです
この場合は見た目はmsecまで出ているのでいいのですが
負荷が大きいと思われます
System.currentTimeMillis()の他に
System.nanoTime()があります
使い方が良ければより精度が高くできるかもしれませんが
実際は色々問題もあるのないのという話もあります
タイマー関連: