Android設備不通過System.currentTimeMillis()來獲取當前時間

前言

當客戶端有一些業(yè)務邏輯需要獲取當前時間參與其中并起起到決定性作用時,System.currentTimeMillis()將不再適用,因為當用戶手動更改為錯誤的日期和時間后,該方法獲取的就是用戶修改后的日期和時間,如果這時的業(yè)務需求為預訂服務或限時秒殺時,調用System.currentTimeMillis()來獲取當前日期和時間就會出現非常嚴重的錯誤。今天我們就來通過另一種思路獲取當前日期和時間。

原理

通過任意網絡請求,獲取網絡請求頭中的時間信息,再利用Android設備的開機時長和從網絡請求頭中獲取到的時間戳來獲取手機開機時刻的時間點,之后在每次調用獲取時間的方法時只需要開機時刻的時間點加上開機時長即可。

知識點

1.SystemClock.elapsedRealtime()
返回系統啟動到現在的時間,包含設備深度休眠的時間。該時鐘被保證是單調的,即使CPU在省電模式下,該時間也會繼續(xù)計時。
2.Date頭域
http協議的通用頭域中包含的Date頭域,Date頭域表示消息發(fā)送的時間,時間的描述格式由rfc822定義。例如,Date:Mon,31Dec200104:25:57GMT。Date描述的時間表示世界標準時。
3.OkHttp攔截器
需要在項目中集成OkHttp網絡請求庫,利用OkHttp的攔截器Interceptor的攔截功能攔截網絡請求返回的Date頭域。

實現

項目地址 https://github.com/kevin-mob/timecalibration
TimeUtil工具類
class TimeUtil {
    companion object {
        /* 手機開機時刻的時間點 */
        private var bootStartUpTime: Long = -1

        /**
         * 獲取當前的服務器時間
         * 誤差范圍決定于最小的網絡響應時間
         * 當還沒有發(fā)起任何網絡請求時,返回本地時間
         */
        fun getCurrentTimeMillis(): Long {
            //開機時刻的時間+開機時長 = 當前的服務器時間
            return if (bootStartUpTime == -1L) {
                System.currentTimeMillis()
            } else {
                bootStartUpTime + SystemClock.elapsedRealtime()
            }
        }

        /**
         * 根據服務器時間更新手機開機時刻的時間
         */
        fun updateTime(lastServiceTime: Long) {
            bootStartUpTime = lastServiceTime - SystemClock.elapsedRealtime()
        }
    }
}

首先我們需要定義一個獲取時間的工具類,包含獲取時間和更新時間兩個方法。
updateTime:用于計算出手機的開機時刻,這個方法的實現很簡單,就是Date頭域獲取時間后減去開機時長,并通過bootStartUpTime來記錄這個值
getCurrentTimeMillis:在調用該方法時,會返回開機時刻的時間點加開機時長來得到當前的時間,這里需要注意一個情況,當未發(fā)生網絡請求時,還是會返回本地時間,所以在調用該方法前要保證APP有聯網操作。

ServerTimeInterceptor OkHttp攔截器
class ServerTimeInterceptor : Interceptor {
    /**
     * 記錄服務器請求最小響應時間
     */
    var minRequestResponseTime = Long.MAX_VALUE
    override fun intercept(chain: Interceptor.Chain): Response {
        val request: Request = chain.request()
        val startTime = System.nanoTime()
        val response = chain.proceed(request)
        val requestResponseTime = System.nanoTime() - startTime

        val headers: Headers? = response.headers()
        calibrationTime(requestResponseTime, headers?.get("Date"))
        return response
    }

    private fun calibrationTime(responseTime: Long, serverDateStr: String?) {
        serverDateStr?.let {
            // 本次響應時間小于最小響應時間,更新時間
            if (responseTime < minRequestResponseTime) {
                HttpDate.parse(serverDateStr)?.let {
                    TimeUtil.updateTime(it.time)
                    minRequestResponseTime = responseTime
                }
            }
        }
    }
}

自定義ServerTimeInterceptor攔截器,攔截response header中的Date頭域,并記錄一個響應時間,當之后再次攔截到網絡請求時,對比本次和之前的響應時間,只有本次響應時間小于最小響應時間才更新時間,這樣會使時間更加接近世界標準時。

使用

給OkHttp設置攔截器,并產生網絡請求后,TimeUtil.getCurrentTimeMillis()就可以獲取當前時間了。

class MainActivity : AppCompatActivity() {
    private val okHttpClient: OkHttpClient =
        OkHttpClient.Builder().addInterceptor(ServerTimeInterceptor()).build()
    private val request: Request = Request.Builder().url("https://www.xcc.cn/static/img/logo.aacc5cc.png").build()
    private val format = SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS", Locale.getDefault())
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    fun requestNetwork(view: View) {
        okHttpClient.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call?, e: IOException) {
                Log.d(TAG, "onFailure: ${e.message}")
                System.currentTimeMillis()
            }

            @Throws(IOException::class)
            override fun onResponse(call: Call?, response: Response) {
                findViewById<TextView>(R.id.tv_hello)?.let {
                    runOnUiThread {
                        it.text = format.format(Date(TimeUtil.getCurrentTimeMillis()))
                    }
                }
            }
        })
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • final關鍵字 可以用來修飾類、方法和變量(包括成員變量和局部變量) 1.修飾類 當用final修飾一個類時,表...
    快感的感知閱讀 1,387評論 0 2
  • OkHttp3中有五大攔截器,分別是重試和重定向攔截器(RetryAndFollowUpInterceptor)、...
    zzq_nene閱讀 5,456評論 0 0
  • final關鍵字 可以用來修飾類、方法和變量(包括成員變量和局部變量) 1.修飾類 當用final修飾一個類時,表...
    Mr_Fly閱讀 519評論 0 1
  • update time 2021年04月27日19:50:07,文章版本:V 1.4,閱讀時間40分鐘,建議先收藏...
    _明川閱讀 22,836評論 22 108
  • 1.NSLog(@"%@",[[UIDevice currentDevice] systemVersion]);/...
    Y像夢一樣自由閱讀 10,699評論 3 12