TrustedTime API | 让应用时间更可靠

作者 / 软件工程师 Kanyinsola Fapohunda 和技术主管 Geoffrey Boullanger

准确的时间对于各种应用功能而言至关重要,无论是日程安排、事件管理、事务日志记录,还是安全协议。但因为用户可以更改设备的时间设置,所以开发者可能需要一种比设备本地系统时间更准确的时间源。鉴于此,我们推出了 TrustedTime API,它利用 Google 的基础设施提供可信的时间戳,独立于设备上可能被篡改的本地时间设置。

TrustedTime 的工作原理是什么?

新的 API 利用 Google 的安全基础设施,为您的应用提供可信的时间源。TrustedTime 会定期将其时钟与 Google 的服务器进行同步,这些服务器可以访问高度准确的时间源,因此您无需在每次想要了解当前网络时间时都发出服务器请求。此外,我们还集成了一个独特的模型来计算设备的时钟漂移,这将在网络同步之间的时间可能不准确时通知您。

为什么准确的时间源很重要?

许多应用依赖设备的时钟来实现各种功能。然而,用户可能会有意或无意地更改其设备的时间设置,从而影响应用获取到的时间。这可能导致以下问题:

  • 数据不一致性:如果应用依赖于事件的时间顺序,则用户篡改设备时间可能导致数据损坏。TrustedTime 通过提供一个可信的时间源来降低这种风险。
  • 安全漏洞:基于时间的安全措施,如一次性密码或定时访问控制,需要未经篡改的时间源才能生效。
  • 时间安排失准:依赖于准确时间安排的应用,如日历或提醒事项应用,如果设备时钟 (例如 Unix 时间戳) 不准确,则可能会出现功能异常。
  • 不准确的时间:设备的内部时钟受各种因素 (如温度、低电耗模式、电池电量等) 的影响,可能出现漂移,导致需要更高时间精度的应用出现问题。TrustedTime API 还提供针对时间戳的估计误差,以确保能够正确执行应用中具有严格时间要求的操作。
  • 设备之间缺乏一致性:在多设备场景下,如游戏或协作应用中,设备间的时间差异可能会导致问题。TrustedTime API 有助于确保所有设备的时间保持一致,从而提升用户体验。
  • 不必要的电量和数据消耗:TrustedTime 的设计目的在于,当应用需要获取当前时间时,提供比直接调用 NTP 服务器更加高效的方法。该 API 通过定期将其时钟与时间服务器同步,来避免重复请求网络的消耗。同步后的时间将作为参考,由 TrustedTime API 依据设备的内部时钟计算当前时间。这种方式降低了网络使用量,同时提高了需要频繁检查时间的应用的性能。

TrustedTime 用例

TrustedTime API 为提升应用的可靠性和安全性开辟了更多可能,其用例包括但不限于以下领域:

  • 金融应用:即使设备处于离线状态,也能确保交易时间戳的准确性,防止欺诈和纠纷。
  • 游戏:防止玩家篡改游戏时钟来获得不公平优势,实现公平竞技。
  • 限时优惠:确保促销和优惠活动在正确的时间结束,不受用户设备设置的影响。
  • 电子商务:准确跟踪订单处理和配送时间。
  • 内容许可:对数字内容 (如租赁或订阅) 实施基于时间的限制。
  • IoT 设备:在多个设备之间同步时钟,以实现数据记录和控制的一致性。
  • 工作效率应用:准确记录在离线状态下对云文档进行任何更改的时间。

开始使用 TrustedTime API

TrustedTime API 基于 Google Play 服务而构建,大多数 Android 开发者可以无缝集成。

最简单的集成方式是在应用生命周期的早期初始化 TrustedTimeClient,例如在 Application 类的 onCreate() 方法中进行初始化。以下示例使用 Hilt 进行依赖项注入,使时间客户端在整个应用的组件中可用。

[可选] 设置依赖项注入

// TrustedTimeClientAccessor.kt
import com.google.android.gms.tasks.Task
import com.google.android.gms.time.TrustedTimeClient

interface TrustedTimeClientAccessor {
  fun createClient(): Task<TrustedTimeClient>
}

// TrustedTimeModule.kt
@Module
@InstallIn(SingletonComponent::class)
class TrustedTimeModule {

  @Provides
  fun provideTrustedTimeClientAccessor(
    @ApplicationContext context: Context
  ): TrustedTimeClientAccessor {
    return object : TrustedTimeClientAccessor {
      override fun createClient(): Task<TrustedTimeClient> {
        return TrustedTime.createClient(context)
      }
    }
  }
}

在应用生命周期的早期进行初始化

// TrustedTimeDemoApplication.kt
@HiltAndroidApp
class TrustedTimeDemoApplication : Application() {
  @Inject
  lateinit var trustedTimeClientAccessor: TrustedTimeClientAccessor
  var trustedTimeClient: TrustedTimeClient? = null
    private set
  override fun onCreate() {
    super.onCreate()
    trustedTimeClientAccessor.createClient().addOnCompleteListener { task ->
      if (task.isSuccessful) {
        // Stash the client
        trustedTimeClient = task.result
      } else {
        // Handle error, maybe retry later
        val exception = task.exception
      }
    }
    // To use Kotlin Coroutine, you can use the await() method, 
    // see https://developers.google.com/android/guides/tasks#kotlin_coroutine for more info.
  }
}
NOTE: If you don't use dependency injection in your app. You can simply call
`TrustedTime.createClient(context)` instead of using a TrustedTimeClientAccessor.

在应用的任何位置使用 TrustedTimeClient

// Retrieve the TrustedTimeClient from your application class
  val myApp = applicationContext as TrustedTimeDemoApplication
  
  // In this example, System.currentTimeMillis() is used as a fallback if the
  // client is null (i.e. client creation task failed) or when there is no time
  // signal available. You may not want to do this if using the system clock is
  // not suitable for your use case.
  val currentTimeMillis =
    myApp.trustedTimeClient?.computeCurrentUnixEpochMillis()
        ?: System.currentTimeMillis()
  // trustedTimeClient.computeCurrentInstant() can be used if Instant is
  // preferred to long for Unix epoch times and you are able to use the APIs.

在诸如 Activity 之类的短生命周期组件中使用

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
  @Inject
  lateinit var trustedTimeAccessor: TrustedTimeAccessor
  
   private var trustedTimeClient: TrustedTimeClient? = null
   
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    ...
    trustedTimeAccessor.createClient().addOnCompleteListener { task ->
      if (task.isSuccessful) {
          // Stash the client
          trustedTimeClient = task.result
        } else {
         // Handle error, maybe retry later or use another time source.
          val exception = task.exception
        }
    }
  }
  private fun getCurrentTimeInMillis() : Long? {
    return trustedTimeClient?.computeCurrentUnixEpochMillis()
  }
}

TrustedTime API 的可用性和局限性

TrustedTime API 在所有运行 Android 5 (Lollipop) 及以上版本且搭载了 Google Play 服务的设备上均可使用。您需要添加依赖项 com.google.android.gms:play-services-time:16.0.1 (或更高版本) 以访问新的 API。使用此 API 不需要额外的权限。但是,设备启动后 TrustedTime 需要连接互联网才能提供时间戳。如果设备启动后未连接到互联网,TrustedTime API 将无法返回时间戳。

需要注意的是,由于温度、低电耗模式和电池电量等因素的影响,设备的内部时钟可能会出现漂移。TrustedTime 并不能防止这种漂移,但其 API 为每个时间戳提供了一个误差估计。您可以使用这个估计值来判断时间戳的准确性是否符合应用的要求。虽然 TrustedTime 提高了用户篡改应用访问时间的难度,但它并不能完全保证安全性。用户仍有可能通过高级技术来篡改设备的时间。

后续步骤

您可以访问 Android 开发者官方网站了解有关 TrustedTime API 的更多信息。