TrustedTime API | 让应用时间更可靠

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 的更多信息。

版权声明

禁止一切形式的转载-禁止商用-禁止衍生 申请授权

脉脉不得语
脉脉不得语
Zhengzhou Website
Android Developer | https://androiddevtools.cn and https://androidweekly.io Funder | GDG Zhengzhou Funder & Ex Organizer | http://Toast.show(∞) Podcast Host

你已经成功订阅到 Android 开发技术周报
太棒了!接下来,完成检验以获得全部访问权限 Android 开发技术周报
欢迎回来!你已经成功登录了。
Unable to sign you in. Please try again.
成功!您的帐户已完全激活,您现在可以访问所有内容。
Error! Stripe checkout failed.
Success! Your billing info is updated.
Error! Billing info update failed.
🍗