問題は、System.currentTimeMillis()
が誤ったミリ秒を返すことです。これは、主に将来6か月以内のさまざまな時間範囲で時々異なりますが、数秒から数か月の間で変化します。
これが発生しているデバイスはタブレットモデルですHuawei M2-A201W on Android 5.1.1カーネルバージョンは次のとおりです:_**3.10.74-gdbd9055**
_
私の最初の仮定は[〜#〜] ntp [〜#〜]がどういうわけか時間を台無しにしていたが、私はそれらの何千ものタブレットを持っており、それらのいくつかはネットワーク接続もSIMカードもないGSM/3G/4Gはありません。
System.currentTimeMillis()
を使用して、ローカルのsqliteデータベースで作成された行がいつだったかをテーブルの列に保存します。
これは、私が使用するタブレットで非常に頻繁に発生します(各System.currentTimeMillis()
呼び出しの30%)。
System.currentTimeMillis()
を使用するための回避策として、sqliteにタイムスタンプの作成を処理させ、それが問題を解決するかどうかを確認できますか?
次のように、ミリ秒も必要な場合は、_timestamp default current_timestamp
_またはdefault(strftime('%Y-%m-%d %H:%M:%f', 'now'))
を使用して「作成」列を定義/変更します。
sqlite> create table my_table(id integer primary key autoincrement not null, name text, created timestamp default(strftime('%Y-%m-%d %H:%M:%f', 'now')) not null);
sqlite> insert into my_table(name) values ('MyTestRow1');
sqlite> insert into my_table(name) values ('MyTestRow2');
_sqlite> select * from my_table;
_
_1|MyTestRow1|2017-08-07 10:08:50.898
_
_2|MyTestRow2|2017-08-07 10:08:54.701
_
Java API呼び出しSystem.currentTimeMillis()
in Androidプラットフォームは、POSIX API gettimeofday
を使用して、ミリ秒単位で時間を取得します。 こちら を参照してください。
static jlong System_currentTimeMillis(JNIEnv*, jclass) {
timeval now;
gettimeofday(&now, NULL);
jlong when = now.tv_sec * 1000LL + now.tv_usec / 1000;
return when;
}
gettimeofday
へのすべての呼び出しが成功することを前提としています。あなたの問題がここで起こるかもしれないと思います。
すべてのAPI呼び出しの戻り値を確認し、エラーが発生した場合の対処方法を決定することをお勧めします。
したがって、以下のような独自の実装を使用して、JNIでより信頼性の高い方法を提案します。これらのPOSIX APIを順番に呼び出します。gettimeofday
が失敗した場合はclock_gettime
を呼び出し、再度失敗した場合はtime
を呼び出します。
struct timeval now;
if (gettimeofday(&now, NULL) != 0) {
struct timespec ts;
if (clock_gettime(CLOCK_REALTIME, &ts) == 0) {
now.tv_sec = ts.tv_sec;
now.tv_usec = ts.tv_nsec / 1000LL;
} else {
now.tv_sec = time(NULL);
now.tv_usec = 0;
}
}
jlong when = now.tv_sec * 1000LL + now.tv_usec / 1000LL;
__Android_log_print(Android_LOG_INFO, "TAG", "%lld", when);
SIMカードがないためGSM/3G/4Gがない場合、携帯電話はネットワークが提供する時間/ゾーンに基づいて正しい時間を更新できません。
したがって、ネットワークを備えたデバイスは正しい時刻を表示しますが、ネットワークを備えていない他のデバイスは誤った時刻を表示する可能性があります。手動で正しい時刻を設定する必要があります。 System.currentTimeMilis()はシステムから時刻を読み取ります。 gしかし、電源を入れると、時計は動作します。
NTP(UDPポート123)が、SocketまたはDatagramSocketを使用するアプリによってブロックされているかどうかを確認します。注:NTPは、すべてのホストまたはルーターのクロックがネットワークが同じである必要があります。デバイスが2つ(またはそれ以上)の異なるネットワークに切り替わり、異なるソースから時刻が更新されると、時刻が変動する可能性があります。
最終的に、システム時刻が変更されているため、変動しています。自動日付と時刻を手動で無効にした後で手動でSystem.currentTimeMilis()を実行した場合、変動はありません(異常なし)。この場合は、Huewaiタブレットにバグはありません。
Linuxカーネルで"date +%s"コマンドを実行してネイティブにタイムスタンプを取得するのはどうですか?
ここで、"+%s"は1970-01-01 00:00:00 UTCからの秒数です。 (GNU Coreutils 8.24日付マニュアル)
try {
// Run the command
Process process = Runtime.getRuntime().exec("date +%s");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
// Grab the results
StringBuilder log = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
log.append(line);
}
} catch (IOException e) {
e.printStackTrace();
}
これを印刷すると
Log.e("unix_time: ", "" + log.toString());
Unixタイムスタンプが取得されます。 1502187111
Javaはミリ秒を想定しているため、これを日付オブジェクトに変換するには、1000を掛けます。
Date time = new Date(Long.parseLong(log.toString()) * 1000);
Log.e("date_time: ", "" + time.toString());
これにより、プレーンな日付形式が得られます。例えば8月8日火曜日16:15:58 GMT + 06:00 2017
ほとんどの呼び出しが適切な時間を取得しており、それが発生するのは30%だけであるとのことですが、ACTION_TIME_CHANGED
およびACTION_TIMEZONE_CHANGED
インテントブロードキャストのレシーバーを作成して、時間の変化を確認します。多分これはあなたに時間を変えているものについての手がかりを与えるでしょう。
ConnectivityManager と一緒に使用すると、デバイスが接続されているかどうか、および接続のタイプを検出できます。おそらく、一部の接続が時間の変化を引き起こしています。
// init the register and register the intents, in onStart, using:
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
getNetworkInfo();
if (Intent.ACTION_TIME_CHANGED.equals(intent.getAction()))
Log.d(this.getClass().getName(), "Detected a time change. isWifiConn: " +
isWifiConn + " isMobileConn: " + isMobileConn);
if (Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction()))
Log.d(this.getClass().getName(), "Detected a timezone change. isWifiConn: " +
isWifiConn + " isMobileConn: " + isMobileConn);
}
};
IntentFilter filters = new IntentFilter();
filters.addAction(Intent.ACTION_TIME_CHANGED);
filters.addAction(Intent.ACTION_TIMEZONE_CHANGED);
registerReceiver(receiver, filters);
Log.d(DEBUG_TAG, "Receiver registered");
// do not forget to unregister the receiver, eg. onStop, using:
unregisterReceiver(receiver);
//...
private void getNetworkInfo() {
ConnectivityManager connMgr = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
isWifiConn = networkInfo.isConnected();
networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
isMobileConn = networkInfo.isConnected();
Log.d(DEBUG_TAG, "Wifi connected: " + isWifiConn);
Log.d(DEBUG_TAG, "Mobile connected: " + isMobileConn);
}
このデバイスには実際のハードウェアRTCがありますか?仕様書をググったり読んだりしても、確実な答えは見つかりませんでした。一連の:
$ dmesg -s 65535 | grep -i rtc
シェルからはあなたに答えを与える必要があります。次のようなものが表示されます(チップセットとカーネルのバージョンによって異なります)。
[ 3.816058] rtc_cmos 00:02: RTC can wake from S4
[ 3.816429] rtc_cmos 00:02: rtc core: registered rtc_cmos as rtc0
[ 3.816510] rtc_cmos 00:02: alarms up to one month, y3k, 242 bytes nvram, hpet irqs
Grepからメッセージが返されない場合(そしてカーネルメッセージバッファー全体に対してgrepを実行した場合)は、答えがあります。これらのデバイスで時間を維持する時計がありません。クロックチップなしでこのようなデバイスをワールドタイムと同期させるには、NTP)とインターネットへのネットワーク接続が常に必要です。