28

我们已就此与 Google 联系,我们正在聊天

对于三星手机以外的设备,该问题似乎已得到解决。

我正在按照官方说明向应用添加 Google+ 登录选项。一旦用户选择了他们的帐户,我希望我的服务器检索他们的 Google+ 个人资料信息并更新他们在我们网站上的个人资料以匹配。

第一部分 - 让用户在本地选择一个 Google 帐户 - 似乎工作得很好。当我尝试为所选帐户请求令牌时,会显示带有适当参数的 Google 身份验证对话框;但是,当我使用该对话框授权应用程序并重新请求令牌时,GoogleAuthUtil.getToken(...)再次抛出UserRecoverableAuthException( NeedPermission, not GooglePlayServicesAvailabilityException) 并且我得到相同的对话框,要求我批准!

此行为存在于运行 Android 4.1.1(具有 3 个 Google 帐户)的三星 S3 和运行 4.0.3 的 Acer A100 上。它不存在于运行 2.3.4 的 HTC Glacier 上。相反,HTC Glacier 给了我一个有效的验证码。 所有设备都安装了最新版本的 Google Play 服务,并使用不同的 Google+ 帐户。

有人见过这个吗?我可以从哪里开始调试?

这是完整的代码 - 有什么明显的错误吗?

public class MyGooglePlusClient {
private static final String LOG_TAG = "GPlus";
private static final String SCOPES_LOGIN = Scopes.PLUS_LOGIN + " " + Scopes.PLUS_PROFILE;
private static final String ACTIVITIES_LOGIN = "http://schemas.google.com/AddActivity";
private static MyGooglePlusClient myGPlus = null;
private BaseActivity mRequestingActivity = null;
private String mSelectedAccount = null;
    
/**
 * Get the GPlus singleton
 * @return GPlus
 */
public synchronized static MyGooglePlusClient getInstance() {
    if (myGPlus == null)
        myGPlus = new MyGooglePlusClient();
    return myGPlus;
}

public boolean login(BaseActivity requester) {
    Log.w(LOG_TAG, "Starting login...");
    if (mRequestingActivity != null) {
        Log.w(LOG_TAG, "Login attempt already in progress.");
        return false; // Cannot launch a new request; already in progress
    }
    
    mRequestingActivity = requester;
    if (mSelectedAccount == null) {
        Intent intent = AccountPicker.newChooseAccountIntent(null, null, new String[]{GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE}, false,
                null, GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE, null, null);
        mRequestingActivity.startActivityForResult(intent, BaseActivity.REQUEST_GPLUS_SELECT);
    }
    return true;
}

public void loginCallback(String accountName) {
    mSelectedAccount = accountName;
    authorizeCallback();
}
    
public void logout() {
    Log.w(LOG_TAG, "Logging out...");
    mSelectedAccount = null;
}

public void authorizeCallback() {
    Log.w(LOG_TAG, "User authorized");

    AsyncTask<Void, Void, String> task = new AsyncTask<Void, Void, String>() {
        @Override
        protected String doInBackground(Void... params) {
            String token = null;
            try {
                Bundle b = new Bundle();
                b.putString(GoogleAuthUtil.KEY_REQUEST_VISIBLE_ACTIVITIES, ACTIVITIES_LOGIN);
                token = GoogleAuthUtil.getToken(mRequestingActivity,
                        mSelectedAccount,
                        "oauth2:server:client_id:"+Constants.GOOGLE_PLUS_SERVER_OAUTH_CLIENT
                        +":api_scope:" + SCOPES_LOGIN,
                        b);
            } catch (IOException transientEx) {
                // Network or server error, try later
                Log.w(LOG_TAG, transientEx.toString());
                onCompletedLoginAttempt(false);
            } catch (GooglePlayServicesAvailabilityException e) {
                Log.w(LOG_TAG, "Google Play services not available.");
                Intent recover = e.getIntent();
                mRequestingActivity.startActivityForResult(recover, BaseActivity.REQUEST_GPLUS_AUTHORIZE);
            } catch (UserRecoverableAuthException e) {
                // Recover (with e.getIntent())
                Log.w(LOG_TAG, "User must approve "+e.toString());
                Intent recover = e.getIntent();
                mRequestingActivity.startActivityForResult(recover, BaseActivity.REQUEST_GPLUS_AUTHORIZE);
            } catch (GoogleAuthException authEx) {
                // The call is not ever expected to succeed
                Log.w(LOG_TAG, authEx.toString());
                onCompletedLoginAttempt(false);
            }

            Log.w(LOG_TAG, "Finished with task; token is "+token);
            if (token != null) {
                authorizeCallback(token);
            }
            
            return token;
        }

    };
    task.execute();
}

public void authorizeCallback(String token) {
    Log.w(LOG_TAG, "Token obtained: "+token);
    // <snipped - do some more stuff involving connecting to the server and resetting the state locally>
}

public void onCompletedLoginAttempt(boolean success) {
    Log.w(LOG_TAG, "Login attempt "+(success ? "succeeded" : "failed"));
    mRequestingActivity.hideProgressDialog();
    mRequestingActivity = null;
}
}
4

8 回答 8

13

我有这个问题有一段时间了,并想出了一个合适的解决方案。

String token = GoogleAuthUtil.getToken(this, accountName, scopeString, appActivities);

此行将返回一次性令牌或触发 UserRecoverableAuthException。在 Google Plus 登录指南中,它说要打开正确的恢复活动。

startActivityForResult(e.getIntent(), RECOVERABLE_REQUEST_CODE);

当活动返回结果时,它会返回意图中的一些额外内容,这就是新令牌所在的位置:

@Override
protected void onActivityResult(int requestCode, int responseCode, Intent intent) {
    if (requestCode == RECOVERABLE_REQUEST_CODE && responseCode == RESULT_OK) {
        Bundle extra = intent.getExtras();
        String oneTimeToken = extra.getString("authtoken");
    }
}

使用 extra 提供的新 oneTimeToken,您可以提交到服务器以正确连接。

我希望这有帮助!

于 2014-10-02T04:07:54.060 回答
5

现在回复为时已晚,但它可能对将来有同样担忧的人有所帮助。

他们在教程中提到,当您第一次调用 GoogleAuthUtil.getToken() 时,它总是会抛出 UserRecoverableAuthException 。第二次就会成功。

catch (UserRecoverableAuthException e) {
  // Requesting an authorization code will always throw
  // UserRecoverableAuthException on the first call to GoogleAuthUtil.getToken
  // because the user must consent to offline access to their data.  After
  // consent is granted control is returned to your activity in onActivityResult
  // and the second call to GoogleAuthUtil.getToken will succeed.
  startActivityForResult(e.getIntent(), AUTH_CODE_REQUEST_CODE);
  return;
}

我使用下面的代码从谷歌获取访问代码。

执行new GetAuthTokenFromGoogle().execute();一次public void onConnected(Bundle connectionHint) 和一次protected void onActivityResult(int requestCode, int responseCode, Intent intent)

private class GetAuthTokenFromGoogle extends AsyncTask<Void, Integer, Void>{
        @Override  
        protected void onPreExecute()  
        {  

        }
        @Override
        protected Void doInBackground(Void... params) {
            // TODO Auto-generated method stub

            try {
                accessCode = GoogleAuthUtil.getToken(mContext, Plus.AccountApi.getAccountName(mGoogleApiClient), SCOPE);
                new ValidateTokenWithPhoneOmega().execute();
                Log.d("Token  -- ", accessCode);
            } catch (IOException transientEx) {
                // network or server error, the call is expected to succeed if you try again later.
                // Don't attempt to call again immediately - the request is likely to
                // fail, you'll hit quotas or back-off.

                return null;
            } catch (UserRecoverableAuthException e) {
                // Recover
                startActivityForResult(e.getIntent(), RC_ACCESS_CODE);
                e.printStackTrace();
            } catch (GoogleAuthException authEx) {
                // Failure. The call is not expected to ever succeed so it should not be
                // retried.
                authEx.printStackTrace();
                return null;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            return null;  
        }

        @Override  
        protected void onPostExecute(Void result)  
        { 
        }
    }
于 2014-10-07T02:21:54.387 回答
2

编辑(2013 年 8 月 6 日):这似乎已为我修复,无需对我的代码进行任何更改。

我可以看到的第一个潜在问题是您GoogleAuthUtil.getToken()在收到onConnected()回调后打电话。这是一个问题,因为请求您的服务器使用的授权代码GoogleAuthUtil.getToken()始终向您的用户显示同意屏幕。因此,您应该只为新用户获取授权代码,并且为了避免向新用户显示两个同意屏幕,您必须获取授权代码并在您的服务器上交换它,然后才能解决来自PlusClient.

其次,确保您确实需要 PlusClient 和服务器的授权代码。如果您打算同时从 Android 客户端服务器调用 Google API,则只需要获取 PlusClient 和授权代码。如本答案所述。

这些问题只会导致显示两个同意对话框(这显然不是无限循环) - 您是否看到两个以上的同意对话框?

于 2013-07-18T14:02:53.030 回答
2

我通过使用基于 Web 的登录解决了这个问题。我打开一个这样的网址

String url = "https://accounts.google.com/o/oauth2/auth?scope=" + Scopes.PLUS_LOGIN + "&client_id=" + webLoginClientId + "&response_type=code&access_type=offline&approval_prompt=force&redirect_uri=" + redirect;

然后重定向 url 处理响应并返回到我的应用程序。

就我使用 Google Play 服务的发现而言,我发现:

HTC One 是 3.1.59 (736673-30) - 不工作 Galaxy Note 是 3.1.59 (736673-36) - 不工作 Nexus S 是 3.1.59 (736673-34) - 工作

而且我想参与正在进行的聊天,但是我没有足够高的声誉来这样做。

于 2013-07-23T23:36:11.790 回答
2

我最近遇到了同样的问题 - 它似乎是特定于设备的(我每次在一个 S3 上都会发生这种情况,但在另一个运行相同操作系统的 S3 上它没有发生,即使使用相同的帐户也是如此)。我的预感是这是客户端应用程序中的错误,无论是 G+ 应用程序还是 Google Play 服务应用程序。我设法通过工厂重置它(摩托罗拉 Defy),然后重新安装 Google Play 服务应用程序来解决我的一个设备上的问题,但这是一个完全无用的解决方案告诉用户。

于 2013-08-01T06:51:17.930 回答
1

我有一个类似的问题,一个明显的身份验证循环不断创建 {read: spamming} 这些“登录... ”和权限请求对话框,同时还反复发出讨论的异常。

问题出现在我(以及我怀疑的其他类似我)从AndroidHive “货物崇拜”的一些稍微修改的示例代码中。对我有用的解决方案是确保在任何给定时间只有一个后台令牌检索任务在后台运行。

为了使我的代码更易于理解,这是我的应用程序中的身份验证流程(几乎与 AndoidHive 上的示例代码相同):Activity-> onConnected(...)-> getProfileInformation()-> getOneTimeToken()

getOneTimeToken()是调用的地方:

private void getProfileInformation() {
    try {
        if (Plus.PeopleApi.getCurrentPerson(mGoogleApiClient) != null) {
            Person currentPerson = Plus.PeopleApi
                    .getCurrentPerson(mGoogleApiClient);
            String personName = currentPerson.getDisplayName();
            String personPhotoUrl = currentPerson.getImage().getUrl();
            String personGooglePlusProfile = currentPerson.getUrl();
            String email = Plus.AccountApi.getAccountName(mGoogleApiClient);
            getOneTimeToken(); // <-------
            ...

这是我的getOneTimeToken()

private void getOneTimeToken(){
    if (task==null){
    task = new AsyncTask<Void, Void, String>() {
        @Override
        protected String doInBackground(Void... params) {
            LogHelper.log('d',LOGTAG, "Executing background task....");
            Bundle appActivities = new Bundle();
            appActivities.putString(
                         GoogleAuthUtil.KEY_REQUEST_VISIBLE_ACTIVITIES,
                         ACTIVITIES_LOGIN);
            String scopes = "oauth2:server" + 
                            ":client_id:" + SERVER_CLIENT_ID + 
                            ":api_scope:" + SCOPES_LOGIN;
            String token = null;
            try {
                token = GoogleAuthUtil.getToken(
                        ActivityPlus.this,
                        Plus.AccountApi.getAccountName(mGoogleApiClient),
                        scopes,
                        appActivities
                );
            } catch (IOException transientEx) {
                /* Original comment removed*/
                LogHelper.log('e',LOGTAG, transientEx.toString());
            } catch (UserRecoverableAuthException e) {
                /* Original comment removed*/
                LogHelper.log('e',LOGTAG, e.toString());
                startActivityForResult(e.getIntent(), AUTH_CODE_REQUEST);
            } catch (GoogleAuthException authEx) {
                /* Original comment removed*/
                LogHelper.log('e',LOGTAG, authEx.toString());
            } catch (IllegalStateException stateEx){
                LogHelper.log('e',LOGTAG, stateEx.toString());
            }
            LogHelper.log('d',LOGTAG, "Background task finishing....");
            return token;
        }

        @Override
        protected void onPostExecute(String token) {
            LogHelper.log('i',LOGTAG, "Access token retrieved: " + token);
        }

    };
    }
    LogHelper.log('d',LOGTAG, "Task setup successful.");
    if(task.getStatus() != AsyncTask.Status.RUNNING){
        task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); //double safety!
    } else
        LogHelper.log('d',LOGTAG, 
                       "Attempted to restart task while it is running!");
}

请注意,对于多次执行的任务,我有一个{可能是多余的}双重安全:

  1. if(task .getStatus() != AsyncTask.Status.RUNNING){...}- 确保任务在尝试执行之前没有运行。
  2. task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);- 确保该任务的副本是“同步的”(即有一个队列以便在给定时间只能执行一个此类任务)。

PS Minor clarification:LogHelper.log('e',...)等同于Log.e(...)etc.

于 2014-10-04T09:52:29.670 回答
0

你应该在 UI 线程中启动活动

try {
    ....
} catch (IOException transientEx) {
    ....
} catch (final UserRecoverableAuthException e) {
    ....
    runOnUiThread(new Runnable() {
        public void run() {         
            startActivityForResult(e1.getIntent(), AUTH_CODE_REQUEST);
        }
    });
}
于 2014-11-27T01:28:58.797 回答
0

与权限请求的无限循环有相同的错误。对我来说,这是因为我手机上的时间发生了变化。当我自动检查检测时间时,此错误消失了。希望这可以帮助!

于 2015-06-13T10:04:45.923 回答