28

この件について Google に連絡済みで、チャット中です

この問題は、 Samsung の携帯電話以外のデバイスでは修正されているようです。

公式の指示に従って、Google+ ログイン オプションをアプリに追加しています。ユーザーが自分のアカウントを選択したら、サーバーで Google+ プロフィール情報を取得し、サイトのプロフィールを一致するように更新します。

最初の部分 - ユーザーにローカルで Google アカウントを選択させる - は問題なく動作するようです。選択したアカウントのトークンをリクエストしようとすると、Google 認証ダイアログに適切なパラメーターが表示されます。ただし、そのダイアログを使用してアプリを承認し、トークンを再要求すると、再び( , not )GoogleAuthUtil.getToken(...)がスローされ、承認を求める同じダイアログが表示されます!UserRecoverableAuthExceptionNeedPermissionGooglePlayServicesAvailabilityException

この動作は、Android 4.1.1 を実行している Samsung S3 (3 つの Google アカウントを使用) および 4.0.3 を実行している Acer A100 に見られます。2.3.4 を実行している HTC Glacier には存在しません。代わりに、HTC Glacier が有効な認証コードを提供してくれます。 すべてのデバイスに最新版の Google Play Services がインストールされており、別の 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");
    }
}

エクストラから渡された新しい oneTimeToken を使用して、サーバーに送信して正しく接続できます。

これが役立つことを願っています!

于 2014-10-02T04:07:54.060 に答える
5

返信するには遅すぎますが、将来同じ懸念を持つ人々に役立つかもしれません.

GoogleAuthUtil.getToken() を初めて呼び出すと、常に UserRecoverableAuthException がスローされることがチュートリアルで言及されています。2回目で成功します。

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;
}

以下のコードを使用して、Google からアクセス コードを取得しました。

これを1回new GetAuthTokenFromGoogle().execute();から1回実行する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

最近同じ問題が発生しました-デバイス固有のようです(あるS3で毎回発生しましたが、同じOSを実行している別のS3では、同じアカウントでも発生しませんでした)。私の推測では、これはクライアント アプリ (G+ アプリまたは Google Play Services アプリ) のバグです。デバイスの 1 つ (Motorola Defy) を工場出荷時の状態にリセットしてから、Google Play Services アプリを再インストールすることで問題を解決できましたが、それはユーザーに伝えるのにまったく役に立たない解決策です。

于 2013-08-01T06:51:17.930 に答える
2

Web ベースのログインを使用して、この問題を回避しました。このようなURLを開きます

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 Services の使用に関する調査結果に関しては、次のことがわかりました。

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

編集 (2013 年 8 月 6 日): これは、コードを変更することなく修正されたようです。

私が見ることができる最初の潜在的な問題は、コールバックGoogleAuthUtil.getToken()を取得した後に電話をかけていることです。を使用してサーバーの認証コードを要求すると、常にユーザーに同意画面が表示されるonConnected()ため、これは問題です。そのため、新しいユーザーの認証コードのみを取得する必要があります。新しいユーザーに 2 つの同意画面を表示しないようにするには、認証コードを取得してサーバー上で交換してから、 からの接続エラーを解決する必要があります。GoogleAuthUtil.getToken()PlusClient

次に、サーバーに PlusClient と認証コードの両方が実際に必要であることを確認します。Android クライアントとサーバーの両方から Google API を呼び出す場合は、PlusClient と認証コードを取得するだけで済みます。この回答で説明されているように。

これらの問題により、2 つの同意ダイアログが表示されるだけです (これは明らかに無限ループではありません)。3 つ以上の同意ダイアログが表示されていますか?

于 2013-07-18T14:02:53.030 に答える
1

明らかな認証ループが{read: spamming}これらの「サインイン...」および許可要求ダイアログを作成し続け、議論された例外を繰り返し与えるという同様の問題がありました。

この問題は、私 (および私と同じように思われる他の人)がAndroidHiveから「カーゴカルト」した、わずかに変更されたサンプル コードに現れます。私にとってうまくいった解決策は、常に 1 つのバックグラウンド トークン取得タスクだけがバックグラウンドで実行されるようにすることでした。

コードを理解しやすくするために、アプリの認証フローを次に示します (これは、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);- このタスクのコピーが「同期」されていることを確認します (つまり、特定の時間にこのタイプのタスクを 1 つだけ実行できるようにキューが配置されます)。

PS マイナーな説明:はetcLogHelper.log('e',...)と同等です。Log.e(...)

于 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 に答える