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}") }}}}}
问题排查
- 交互适配缺失:Android TV依赖遥控器操作,手机端的授权弹窗未适配TV的导航逻辑,系统因无法完成交互自动取消意图
- 离线授权不支持:部分TV设备不支持
requestOfflineAccess请求,触发授权流程异常 - CredentialManager流程冗余:当前代码同时使用CredentialManager获取ID Token和AuthorizationClient发起授权,TV平台下该组合流程存在兼容性问题
- 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
相关产品推荐
相关产品推荐

