You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android TV端使用CredentialManager与AuthorizationClient的Google登录异常问题

Android TV上CredentialManager+AuthorizationClient实现Google登录自动取消问题排查与解决

问题现象

  • Android手机端:Google登录功能正常,用户确认登录后返回Activity.RESULT_OK及有效授权数据
  • Android TV端:登录意图被自动取消,返回Activity.RESULT_CANCELED且数据为空

复现代码

class MainActivity : AppCompatActivity() {
 private val credentialManager = CredentialManager.create(this)
 private val credential = mutableStateOf<Credential?>(null)
 private val googleIdTokenCredential = mutableStateOf<GoogleIdTokenCredential?>(null)
 override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  val authorizationRequest = AuthorizationRequest.Builder()
   .requestOfflineAccess(getString(R.string.server_client_id),true)
   .setRequestedScopes(listOf(Scope("https://www.googleapis.com/auth/userinfo.email"),
    Scope("https://www.googleapis.com/auth/userinfo.profile"),
    Scope("https://www.googleapis.com/auth/photoslibrary.readonly"),
    Scope("openid")))
   .build()
  val authorizationLauncher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()){
   activityResult ->
    when(activityResult.resultCode) {
     Activity.RESULT_OK -> {
      val auth = Identity.getAuthorizationClient(this)
       .getAuthorizationResultFromIntent(activityResult.data)
      setContent{
       SignInSuccess()
     }}
     Activity.RESULT_CANCELED -> {
      Log.d("GoogleId", "User cancelled sign-in: $activityResult")
     }
     else -> Log.d("GoogleId","resultcode: ${activityResult.resultCode}")
  }}
  setContent {
   MyApplicationTheme {
    Homepage(onClick = {signIn(authorizationLauncher, authorizationRequest)})
 }}}

 private fun signIn(authorizationLauncher: ActivityResultLauncher<IntentSenderRequest>,authorizationRequest: AuthorizationRequest) {
  if(isNetworkAvailable(this)) {
   val rawNonce = UUID.randomUUID().toString()
   val bytes = rawNonce.toByteArray()
   val md = MessageDigest.getInstance("SHA-256")
   val digest = md.digest(bytes)
   val hashedNonce = digest.fold(""){str, it -> str + "%02x".format(it)}
   val signInWithGoogleOption: GetSignInWithGoogleOption = GetSignInWithGoogleOption.Builder(getString(R.string.server_client_id))
    .setNonce(hashedNonce)
    .build()
   val request: GetCredentialRequest = GetCredentialRequest.Builder()
    .addCredentialOption(signInWithGoogleOption)
    .build()
   lifecycleScope.launch(Dispatchers.IO){
    try{
     val result = credentialManager.getCredential(this@MainActivity,request)
     credential.value = result.credential
     when(credential.value){
      is CustomCredential -> {
       if(credential.value!!.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL){
        googleIdTokenCredential.value = GoogleIdTokenCredential.createFrom(credential.value!!.data)
        Identity.getAuthorizationClient(this@MainActivity)
         .authorize(authorizationRequest)
         .addOnSuccessListener { authorizationResult ->
          if (authorizationResult.hasResolution()) {
           authorizationResult.pendingIntent?.intentSender?.let { intentSender ->
            val intentSenderRequest = IntentSenderRequest.Builder(intentSender).build()
            authorizationLauncher.launch(intentSenderRequest)
          }}}
         .addOnFailureListener { exception ->
          Log.e("GoogleID","failure: ${exception.message}")
       }}}
      else -> Log.e("GoogleID","Unexpected type of credential")
    }} catch (e: GetCredentialException) {
     Log.e("GoogleID","CredentialException: ${e.message}")
    } catch (e: GoogleIdTokenParsingException) {
     Log.e("GoogleID","GoogleIdTokenParsingException: ${e.message}")
  }}}}}

问题排查

  1. 交互适配缺失:Android TV依赖遥控器操作,手机端的授权弹窗未适配TV的导航逻辑,系统因无法完成交互自动取消意图
  2. 离线授权不支持:部分TV设备不支持requestOfflineAccess请求,触发授权流程异常
  3. CredentialManager流程冗余:当前代码同时使用CredentialManager获取ID Token和AuthorizationClient发起授权,TV平台下该组合流程存在兼容性问题
  4. Google Play服务版本过低:旧版本的Google Play服务在TV上对CredentialManager的支持不完善

解决方法

方案1:切换为TV专用Google Sign-In流程

Android TV官方推荐使用GoogleSignInClient实现登录,适配遥控器交互:

// 初始化GoogleSignInClient
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
    .requestIdToken(getString(R.string.server_client_id))
    .requestScopes(
        Scope("https://www.googleapis.com/auth/userinfo.email"),
        Scope("https://www.googleapis.com/auth/userinfo.profile"),
        Scope("https://www.googleapis.com/auth/photoslibrary.readonly")
    )
    .requestOfflineAccess()
    .build()
val googleSignInClient = GoogleSignIn.getClient(this, gso)

// 启动登录流程
private fun signIn(launcher: ActivityResultLauncher<Intent>) {
    val signInIntent = googleSignInClient.signInIntent
    // TV上需确保任务栈正确
    signInIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    launcher.launch(signInIntent)
}

// 注册ActivityResultLauncher
val signInLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
    if (result.resultCode == Activity.RESULT_OK) {
        val task = GoogleSignIn.getSignedInAccountFromIntent(result.data)
        try {
            val account = task.getResult(ApiException::class.java)
            // 处理登录成功逻辑,获取ID Token、Scopes等
            setContent { SignInSuccess() }
        } catch (e: ApiException) {
            Log.e("GoogleID", "Sign-in failed: ${e.statusCode}")
        }
    } else {
        Log.d("GoogleId", "Sign-in cancelled: ${result.resultCode}")
    }
}

方案2:调整CredentialManager适配TV平台

若坚持使用CredentialManager,需做以下修改:

  • 移除requestOfflineAccess强制设置,或增加TV设备判断:
    val authorizationRequestBuilder = AuthorizationRequest.Builder()
    if (!isTvDevice()) {
        authorizationRequestBuilder.requestOfflineAccess(getString(R.string.server_client_id), true)
    }
    val authorizationRequest = authorizationRequestBuilder
        .setRequestedScopes(...)
        .build()
    
  • 确保在主线程调用credentialManager.getCredential:
    lifecycleScope.launch(Dispatchers.Main) {
        try {
            val result = credentialManager.getCredential(this@MainActivity, request)
            // 后续逻辑
        } catch (e: Exception) {
            Log.e("GoogleID", "Error: ${e.message}", e)
        }
    }
    
  • 在AndroidManifest.xml添加TV适配配置:
    <uses-feature android:name="android.software.leanback" android:required="true" />
    <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
    

方案3:增加异常排查日志

在OnFailureListener中打印完整异常栈,定位具体错误:

.addOnFailureListener { exception ->
    Log.e("GoogleID", "Authorization failed", exception)
}

Android TV上CredentialManager的注意事项

  • 支持范围:CredentialManager支持Android TV,但仅适合简单的凭证获取场景,复杂OAuth授权建议使用TV专用Google Sign-In
  • 交互限制:所有弹窗必须适配遥控器导航,避免使用依赖触摸的UI组件
  • 权限限制:部分Scopes(如离线访问)在TV上可能不被支持,需提前测试
  • 依赖要求:必须安装最新版本的Google Play服务,旧版本存在兼容性问题

内容的提问来源于stack exchange,提问作者Sreelakshmi P P

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 14:08:10