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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 11:15:55