使用Android Authorization API如何避免重复弹出权限确认弹窗?
问题描述
我已经通过CredentialManager完成Google账号登录,并且借助Authorization API获取到了ServerAuthCode,但遇到一个问题:每次使用同一账号且仅请求相同Scopes时,仍然会弹出用户权限确认弹窗,也就是hasResolution()始终返回true。
相关代码
AuthorizationClient类
public class AuthorizationClient { static final int ACTIVITY_RESULT_IS_NOT_OK = 1; static final int EMPTY_SERVER_AUTH_CODE = 2; public static void authorize (AuthorizationListener listener, String clientId, boolean refreshToken, String[] scopes) { if (IntentRunnerActivity.isInitialized()) authorizeInternal(listener, clientId, refreshToken, scopes); else IntentRunnerActivity.initialize(() -> authorizeInternal(listener, clientId, refreshToken, scopes)); } static void authorizeInternal (AuthorizationListener listener, String clientId, boolean refreshToken, String[] scopes) { try { List<Scope> requestedScopes = new ArrayList<>(); requestedScopes.add(new Scope("email")); if (scopes != null) for (String scope : scopes) requestedScopes.add(new Scope(scope)); AuthorizationRequest authorizationRequest = new AuthorizationRequest.Builder() .requestOfflineAccess(clientId, refreshToken) .setRequestedScopes(requestedScopes) .build(); Identity.getAuthorizationClient(UnityActivity.get()) .authorize(authorizationRequest) .addOnSuccessListener(r -> { if (r.hasResolution()) { IntentRunnerActivity.run(r.getPendingIntent(), activityResult -> { if (activityResult.getResultCode() == Activity.RESULT_OK) { try { handleAuthorizationResult(listener, Identity.getAuthorizationClient(UnityActivity.get()) .getAuthorizationResultFromIntent(activityResult.getData())); } catch (ApiException e) { onException(listener, e); } } else listener.OnFail(ACTIVITY_RESULT_IS_NOT_OK); }); } else handleAuthorizationResult(listener, r); }) .addOnFailureListener(e -> onException(listener, e)); } catch (Exception e) { onException(listener, e); } } static void handleAuthorizationResult(AuthorizationListener listener, AuthorizationResult result) { String serverAuthCode = result.getServerAuthCode(); if (serverAuthCode != null) { listener.OnSuccess(serverAuthCode); } else listener.OnFail(EMPTY_SERVER_AUTH_CODE); } static void onException(AuthorizationListener listener, Exception e) { listener.OnException(e.toString()); } }
IntentRunnerActivity类
public class IntentRunnerActivity extends AppCompatActivity { static final AtomicReference<IntentRunnerInitializationListener> listener = new AtomicReference<>(null); static final AtomicReference<OnAuthorizationIntentResult> onResult = new AtomicReference<>(null); static IntentRunnerActivity instance; ActivityResultRegistry registry; ActivityResultLauncher<IntentSenderRequest> launcher; public static boolean isInitialized() { return listener.get() != null; } public static void initialize(IntentRunnerInitializationListener initializationListener) { listener.set(initializationListener); Context context = UnityContext.get(); Intent intent = new Intent(context, IntentRunnerActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.registry = getActivityResultRegistry(); this.launcher = registry.register(generateKey(), this, new ActivityResultContracts.StartIntentSenderForResult(), result -> { Intent intent = new Intent(this, UnityActivity.get().getClass()); intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); startActivity(intent); onResult.get().OnResult(result); }); instance = this; listener.get().OnCompleted(); } public static void run(PendingIntent pendingIntent, OnAuthorizationIntentResult onResult) { IntentRunnerActivity.onResult.set(onResult); instance.launcher.launch(new IntentSenderRequest.Builder(pendingIntent).build()); } private static String generateKey() { return UUID.randomUUID().toString(); } public interface OnAuthorizationIntentResult { void OnResult(ActivityResult result); } }
UnityActivity类
public class UnityActivity { static UnityActivity instance; static final Class<?> unityPlayerClass; final Field field; static { try { unityPlayerClass = Class.forName("com.unity3d.player.UnityPlayer"); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } public UnityActivity() throws NoSuchFieldException { this.field = unityPlayerClass.getDeclaredField("currentActivity"); this.field.setAccessible(true); } static UnityActivity getInstance() throws NoSuchFieldException, ClassNotFoundException { if (instance != null) return instance; instance = new UnityActivity(); return instance; } Activity getActivity() throws IllegalAccessException { return (Activity) field.get(null); } @NonNull public static Activity get() { try { return getInstance().getActivity(); } catch (IllegalAccessException | NoSuchFieldException | ClassNotFoundException e) { //noinspection DataFlowIssue return null; } } }
UnityContext类
public class UnityContext { private static final Context applicationContext = UnityActivity.get().getApplicationContext(); public static Context get() { return applicationContext; } }
依赖配置
implementation 'com.google.android.gms:play-services-auth:21.2.0' implementation "androidx.credentials:credentials: 1.3.0-beta02" implementation "androidx.credentials:credentials-play-services-auth:1.3.0-beta02" implementation "com.google.android.libraries.identity.googleid:googleid:1.1.1" def appcompat_version = "1.6.1" implementation "androidx.appcompat:appcompat:$appcompat_version" implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
解决方案
要避免重复弹出权限确认弹窗,需要从凭证查询、Scope规范、授权配置三个方面调整代码:
1. 先查询已保存的凭证,避免重复请求
在发起新的授权请求前,先通过CredentialManager查询设备上已保存的、已授权的Google账号凭证。如果存在匹配的凭证,直接复用或指定账号发起授权,跳过弹窗步骤。
2. 使用完整的Google OAuth2 Scope格式
Google的OAuth2 Scope必须使用完整的URL格式,不能仅写email,需要替换为https://www.googleapis.com/auth/userinfo.email(获取用户邮箱权限)。不完整的Scope会导致系统无法识别已授权的权限,从而重复触发弹窗。
3. 保存授权后的凭证,并指定账号发起授权
第一次授权成功后,将凭证保存到CredentialManager;后续发起授权时,指定已授权的账号ID,系统会直接使用已有的授权信息,无需再次弹窗确认。
修改后的核心代码示例
调整AuthorizationClient的authorizeInternal方法,增加凭证查询、保存和指定账号授权的逻辑:
static void authorizeInternal(AuthorizationListener listener, String clientId, boolean refreshToken, String[] scopes) { try { List<Scope> requestedScopes = new ArrayList<>(); // 使用完整的Scope格式 requestedScopes.add(new Scope("https://www.googleapis.com/auth/userinfo.email")); if (scopes != null) { for (String scope : scopes) { // 确保传入的Scope也是完整格式 requestedScopes.add(new Scope(scope)); } } // 先查询已保存的凭证 CredentialManager credentialManager = CredentialManager.create(UnityActivity.get()); QueryRequest queryRequest = new QueryRequest.Builder() .addCredentialOption(new GoogleIdTokenCredentialOption.Builder() .setFilterByAuthorizedAccounts(true) .setRequestedScopes(requestedScopes) .build()) .build(); credentialManager.query(UnityActivity.get(), queryRequest) .addOnSuccessListener(queryResult -> { if (!queryResult.getCredentials().isEmpty()) { // 使用已有的账号发起授权,避免弹窗 Credential credential = queryResult.getCredentials().get(0); authorizeWithExistingAccount(listener, clientId, refreshToken, requestedScopes, credential.getId()); } else { // 无已保存凭证,发起新授权 startAuthorizationRequest(listener, clientId, refreshToken, requestedScopes); } }) .addOnFailureListener(e -> { // 查询失败,发起新授权 startAuthorizationRequest(listener, clientId, refreshToken, requestedScopes); }); } catch (Exception e) { onException(listener, e); } } // 封装授权请求逻辑 private static void startAuthorizationRequest(AuthorizationListener listener, String clientId, boolean refreshToken, List<Scope> requestedScopes) { AuthorizationRequest authorizationRequest = new AuthorizationRequest.Builder() .requestOfflineAccess(clientId, refreshToken) .setRequestedScopes(requestedScopes) .build(); Identity.getAuthorizationClient(UnityActivity.get()) .authorize(authorizationRequest) .addOnSuccessListener(r -> { if (r.hasResolution()) { IntentRunnerActivity.run(r.getPendingIntent(), activityResult -> { if (activityResult.getResultCode() == Activity.RESULT_OK) { try { handleAuthorizationResult(listener, Identity.getAuthorizationClient(UnityActivity.get()) .getAuthorizationResultFromIntent(activityResult.getData())); // 保存授权后的凭证 saveCredential(activityResult.getData()); } catch (ApiException e) { onException(listener, e); } } else listener.OnFail(ACTIVITY_RESULT_IS_NOT_OK); }); } else handleAuthorizationResult(listener, r); }) .addOnFailureListener(e -> onException(listener, e)); } // 保存凭证到CredentialManager private static void saveCredential(Intent data) { try { Credential credential = Identity.getAuthorizationClient(UnityActivity.get()) .getCredentialFromIntent(data); if (credential != null) { CredentialManager credentialManager = CredentialManager.create(UnityActivity.get()); SaveRequest saveRequest = new SaveRequest.Builder() .setCredential(credential) .build(); credentialManager.save(UnityActivity.get(), saveRequest); } } catch (Exception e) { e.printStackTrace(); } } // 指定已有账号发起授权 private static void authorizeWithExistingAccount(AuthorizationListener listener, String clientId, boolean refreshToken, List<Scope> requestedScopes, String accountId) { AuthorizationRequest authorizationRequest = new AuthorizationRequest.Builder() .requestOfflineAccess(clientId, refreshToken) .setRequestedScopes(requestedScopes) .setAccountId(accountId) // 指定已授权账号ID .build(); Identity.getAuthorizationClient(UnityActivity.get()) .authorize(authorizationRequest) .addOnSuccessListener(r -> { if (r.hasResolution()) { // 理论上不会触发弹窗,除非权限有变更 IntentRunnerActivity.run(r.getPendingIntent(), activityResult -> { if (activityResult.getResultCode() == Activity.RESULT_OK) { try { handleAuthorizationResult(listener, Identity.getAuthorizationClient(UnityActivity.get()) .getAuthorizationResultFromIntent(activityResult.getData())); } catch (ApiException e) { onException(listener, e); } } else listener.OnFail(ACTIVITY_RESULT_IS_NOT_OK); }); } else handleAuthorizationResult(listener, r); }) .addOnFailureListener(e -> onException(listener, e)); }
内容的提问来源于stack exchange,提问作者MohuMohu.Corp
相关产品推荐
相关产品推荐

