我已使用 CredentialManager 成功登录 Google 帐户,并使用授权 API 获取了 ServerAuthCode。
但是,有一个问题。尽管我每次都使用相同的帐户并且仅请求相同的范围,但每次都会显示用户权限的弹出窗口。
换句话说,hasResolution() 始终返回 true。
我使用的授权部分的代码如下。
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());
}
}
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);
}
}
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;
}
}
}
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"
如何防止第二次后出现弹出窗口?
已解决。
new AuthorizationRequest.Builder()
.requestOfflineAccess(clientId, refreshToken) // here
我发现每次当“forceCodeForRefreshToken”作为requestOfflineAccess函数的参数为true时,都会显示同意屏幕。
根据上面的解释
forceCodeForRefreshToken:
If true, the granted code can be exchanged for an access token and a refresh token.
The first time you retrieve a code, a refresh token will be granted automatically.
Subsequent requests will require additional user consent.
Use false by default;
only use true if your server has suffered some failure and lost the user's refresh token.