2014-11-05 66 views
5

我想在我的项目中实现com.google.android.gms.common.api.GoogleApiClient。卡住连接失败循环与GoogleApiClient

问题是,我每次尝试连接时,都会回调onConnectionFailed侦听器,并执行一个挂起的intent。在干净的安装中,第一个未决意图将启动一个账户选择屏幕。这是预料之中的。除非在应用程序管理器中清除应用程序的数据,否则后续每次重新启动应用程序都将绕过帐户选择。

在帐户选择屏幕之后,登录屏幕将显示不足。它从来没有登录。 onActivityResult将在登录屏幕闪烁后尝试连接客户端。它不会连接,并再次调用onConnectionFailed监听器。

如果我一直试图执行这些意图,我会陷入循环,出现很短的登录屏幕,然后消失,但从未连接或登录。ConnectionResult.toString指示“Sign_In_Required”,并返回一个错误代码为4(与Sign_In_Required常量相同)

在API控制台上,我实现了一个Ouath 2.0客户端ID和一个用于android应用程序的公共API访问键值得注意的是,我的应用程序使用旧版com。 google.api.services.drive.Drive客户

至于我的代码:

我试过使用两种不同的实现herehere。我试图实现第二个例子,尽可能少地做出改变。它抄录如下:

public class MainActivity extends Activity implements ConnectionCallbacks, 
    OnConnectionFailedListener { 

private static final String TAG = "android-drive-quickstart"; 
private static final int REQUEST_CODE_CAPTURE_IMAGE = 1; 
private static final int REQUEST_CODE_CREATOR = 2; 
private static final int REQUEST_CODE_RESOLUTION = 3; 

private GoogleApiClient mGoogleApiClient; 
private Bitmap mBitmapToSave; 

/** 
* Create a new file and save it to Drive. 
*/ 
private void saveFileToDrive() { 
    // Start by creating a new contents, and setting a callback. 
    Log.i(TAG, "Creating new contents."); 
    final Bitmap image = mBitmapToSave; 

    Drive.DriveApi.newContents(mGoogleApiClient).setResultCallback(new ResultCallback<DriveApi.ContentsResult>() { 

     @Override 
     public void onResult(DriveApi.ContentsResult result) { 

      // If the operation was not successful, we cannot do anything 
      // and must 
      // fail. 
      if (!result.getStatus().isSuccess()) { 
       Log.i(TAG, "Failed to create new contents."); 
       return; 
      } 
      // Otherwise, we can write our data to the new contents. 
      Log.i(TAG, "New contents created."); 
      // Get an output stream for the contents. 
      OutputStream outputStream = result.getContents().getOutputStream(); 
      // Write the bitmap data from it. 
      ByteArrayOutputStream bitmapStream = new ByteArrayOutputStream(); 
      image.compress(Bitmap.CompressFormat.PNG, 100, bitmapStream); 
      try { 
       outputStream.write(bitmapStream.toByteArray()); 
      } catch (IOException e1) { 
       Log.i(TAG, "Unable to write file contents."); 
      } 
      // Create the initial metadata - MIME type and title. 
      // Note that the user will be able to change the title later. 
      MetadataChangeSet metadataChangeSet = new MetadataChangeSet.Builder() 
        .setMimeType("image/jpeg").setTitle("Android Photo.png").build(); 
      // Create an intent for the file chooser, and start it. 
      IntentSender intentSender = Drive.DriveApi 
        .newCreateFileActivityBuilder() 
        .setInitialMetadata(metadataChangeSet) 
        .setInitialContents(result.getContents()) 
        .build(mGoogleApiClient); 
      try { 
       startIntentSenderForResult(
         intentSender, REQUEST_CODE_CREATOR, null, 0, 0, 0); 
      } catch (SendIntentException e) { 
       Log.i(TAG, "Failed to launch file chooser."); 
      } 
     } 
    }); 
} 

@Override 
protected void onResume() { 
    super.onResume(); 
    if (mGoogleApiClient == null) { 
     // Create the API client and bind it to an instance variable. 
     // We use this instance as the callback for connection and connection 
     // failures. 
     // Since no account name is passed, the user is prompted to choose. 
     mGoogleApiClient = new GoogleApiClient.Builder(this) 
       .addApi(Drive.API) 
       .addScope(Drive.SCOPE_FILE) 
       .addConnectionCallbacks(this) 
       .addOnConnectionFailedListener(this) 
       .build(); 
    } 
    // Connect the client. Once connected, the camera is launched. 
    mGoogleApiClient.connect(); 
} 

@Override 
protected void onPause() { 
    if (mGoogleApiClient != null) { 
     mGoogleApiClient.disconnect(); 
    } 
    super.onPause(); 
} 

@Override 
protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) { 
    switch (requestCode) { 
     case REQUEST_CODE_CAPTURE_IMAGE: 
      // Called after a photo has been taken. 
      if (resultCode == Activity.RESULT_OK) { 
       // Store the image data as a bitmap for writing later. 
       mBitmapToSave = (Bitmap) data.getExtras().get("data"); 
      } 
      break; 
     case REQUEST_CODE_CREATOR: 
      // Called after a file is saved to Drive. 
      if (resultCode == RESULT_OK) { 
       Log.i(TAG, "Image successfully saved."); 
       mBitmapToSave = null; 
       // Just start the camera again for another photo. 
       startActivityForResult(new Intent(MediaStore.ACTION_IMAGE_CAPTURE), 
         REQUEST_CODE_CAPTURE_IMAGE); 
      } 
      break; 
    } 
} 

@Override 
public void onConnectionFailed(ConnectionResult result) { 
    // Called whenever the API client fails to connect. 
    Log.i(TAG, "GoogleApiClient connection failed: " + result.toString()); 
    if (!result.hasResolution()) { 
     // show the localized error dialog. 
     GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(), this, 0).show(); 
     return; 
    } 
    // The failure has a resolution. Resolve it. 
    // Called typically when the app is not yet authorized, and an 
    // authorization 
    // dialog is displayed to the user. 
    try { 
     result.startResolutionForResult(this, REQUEST_CODE_RESOLUTION); 
    } catch (SendIntentException e) { 
     Log.e(TAG, "Exception while starting resolution activity", e); 
    } 
} 

@Override 
public void onConnected(Bundle connectionHint) { 
    Log.i(TAG, "API client connected."); 
    if (mBitmapToSave == null) { 
     // This activity has no UI of its own. Just start the camera. 
     startActivityForResult(new Intent(MediaStore.ACTION_IMAGE_CAPTURE), 
       REQUEST_CODE_CAPTURE_IMAGE); 
     return; 
    } 
    saveFileToDrive(); 
} 

@Override 
public void onConnectionSuspended(int cause) { 
    Log.i(TAG, "GoogleApiClient connection suspended"); 
} 

}

+0

我在这里有完全相同的问题。奇怪的是,它已经习惯了。我在4.1.2(API 16)设备上运行我的应用程序。 – m02ph3u5 2014-12-04 14:27:36

回答

3

这是一个艰难的一个,因为我没有时间来完全重新运行和分析代码。没有运行它,我没有看到任何明显的。

但是,因为我已经在我的应用程序中运行了这个东西,所以我想帮忙。不幸的是,Google Play服务连接和授权码遍布我的应用程序的片段和活动。所以,我试图创建一个虚拟活动,并将其中的所有东西都拉进去。 '所有的东西'我的意思是账户经理包装(GA)和相关账户选择代码。

结果是大约300行可能有效的乱码,但我没有做出任何声明。看一看,祝你好运。

package com.......; 

import android.accounts.Account; 
import android.accounts.AccountManager; 
import android.app.Activity; 
import android.app.Dialog; 
import android.app.DialogFragment; 
import android.content.Context; 
import android.content.DialogInterface; 
import android.content.Intent; 
import android.content.IntentSender; 
import android.content.SharedPreferences; 
import android.os.Bundle; 
import android.preference.PreferenceManager; 
import android.util.Log; 
import android.widget.Toast; 

import com.google.android.gms.auth.GoogleAuthUtil; 
import com.google.android.gms.common.AccountPicker; 
import com.google.android.gms.common.ConnectionResult; 
import com.google.android.gms.common.GooglePlayServicesUtil; 
import com.google.android.gms.common.api.GoogleApiClient; 

public class GooApiClient extends Activity implements 
       GoogleApiClient.OnConnectionFailedListener, GoogleApiClient.ConnectionCallbacks { 

    private static final String DIALOG_ERROR = "dialog_error"; 
    private static final String REQUEST_CODE = "request_code"; 

    private static final int REQ_ACCPICK = 1; 
    private static final int REQ_AUTH = 2; 
    private static final int REQ_RECOVER = 3; 

    private GoogleApiClient mGooApiClient; 
    private boolean mIsInAuth; //block re-entrancy 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 

    if (checkPlayServices() && checkUserAccount()) { 
     gooInit(); 
     gooConnect(true); 
    } 
    } 

    @Override 
    public void onConnected(Bundle bundle) { 
    Log.d("_", "connected"); 
    } 
    @Override 
    public void onConnectionSuspended(int i) { } 
    @Override 
    public void onConnectionFailed(ConnectionResult result) { 
    Log.d("_", "failed " + result.hasResolution()); 
    if (!mIsInAuth) { 
     if (result.hasResolution()) { 
     try { 
      mIsInAuth = true; 
      result.startResolutionForResult(this, REQ_AUTH); 
     } catch (IntentSender.SendIntentException e) { 
      suicide("authorization fail"); 
     } 
     } else { 
     suicide("authorization fail"); 
     } 
    } 
    } 

    @Override 
    protected void onActivityResult(int requestCode, int resultCode, Intent it) { 
    Log.d("_", "activity result " + requestCode + " " + resultCode); 

    switch (requestCode) { 
     case REQ_AUTH: case REQ_RECOVER: { 
     mIsInAuth = false; 
     if (resultCode == Activity.RESULT_OK) { 
      gooConnect(true); 
     } else if (resultCode == RESULT_CANCELED) { 
      suicide("authorization fail"); 
     } 
     return; 
     } 

     case REQ_ACCPICK: { // return from account picker 
     if (resultCode == Activity.RESULT_OK && it != null) { 
      String emil = it.getStringExtra(AccountManager.KEY_ACCOUNT_NAME); 
      if (GA.setEmil(this, emil) == GA.CHANGED) { 
      gooInit(); 
      gooConnect(true); 
      } 
     } else if (GA.getActiveEmil(this) == null) { 
      suicide("selection failed"); 
     } 
     return; 
     } 
    } 
    super.onActivityResult(requestCode, resultCode, it); // DO NOT REMOVE 
    } 

    private boolean checkPlayServices() { 
    Log.d("_", "check PS"); 
    int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this); 
    if (status != ConnectionResult.SUCCESS) { 
     if (GooglePlayServicesUtil.isUserRecoverableError(status)) { 
     mIsInAuth = true; 
     errorDialog(status, LstActivity.REQ_RECOVER); 
     } else { 
     suicide("play services failed"); 
     } 
     return false; 
    } 
    return true; 
    } 
    private boolean checkUserAccount() { 
    String emil = GA.getActiveEmil(this); 
    Account accnt = GA.getPrimaryAccnt(this, true); 
    Log.d("_", "check user account " + emil + " " + accnt); 

    if (emil == null) { // no emil (after install) 
     if (accnt == null) { // multiple or no accounts available, go pick one 
     accnt = GA.getPrimaryAccnt(this, false); 
     Intent it = AccountPicker.newChooseAccountIntent(accnt, null, 
     new String[]{GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE}, true, null, null, null, null 
     ); 
     this.startActivityForResult(it, LstActivity.REQ_ACCPICK); 
     return false; //--------------------->>> 

     } else { // there's only one goo account registered with the device, skip the picker 
     GA.setEmil(this, accnt.name); 
     } 

    // UNLIKELY BUT POSSIBLE, emil's OK, but the account have been removed since (through settings) 
    } else { 
     accnt = GA.getActiveAccnt(this); 
     if (accnt == null) { 
     accnt = GA.getPrimaryAccnt(this, false); 
     Intent it = AccountPicker.newChooseAccountIntent(accnt, null, 
     new String[]{GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE}, true, null, null, null, null 
     ); 
     this.startActivityForResult(it, LstActivity.REQ_ACCPICK); 
     return false; //------------------>>> 
     } 
    } 
    return true; 
    } 

    private void gooInit(){ 
    String emil = GA.getActiveEmil(this); 
    Log.d("_", "goo init " + emil); 
    if (emil != null){ 
     mGooApiClient = new GoogleApiClient.Builder(this) 
     .setAccountName(emil).addApi(com.google.android.gms.drive.Drive.API) 
     .addScope(com.google.android.gms.drive.Drive.SCOPE_FILE) 
     .addConnectionCallbacks(this).addOnConnectionFailedListener(this) 
     .build(); 
    } 
    } 

    private void gooConnect(boolean bConnect) { 
    Log.d("_", "goo connect " + bConnect); 
    if (mGooApiClient != null) { 
     if (!bConnect) { 
     mGooApiClient.disconnect(); 
     } else if (! (mGooApiClient.isConnecting() || mGooApiClient.isConnected())){ 
     mGooApiClient.connect(); 
     } 
    } 
    } 

    private void suicide(String msg) { 
    GA.removeActiveAccnt(this); 
    Toast.makeText(this, msg, Toast.LENGTH_LONG).show(); 
    finish(); 
    } 

    private void errorDialog(int errorCode, int requestCode) { 
    Bundle args = new Bundle(); 
    args.putInt(DIALOG_ERROR, errorCode); 
    args.putInt(REQUEST_CODE, requestCode); 
    ErrorDialogFragment dialogFragment = new ErrorDialogFragment(); 
    dialogFragment.setArguments(args); 
    dialogFragment.show(getFragmentManager(), "errordialog"); 
    } 
    public static class ErrorDialogFragment extends DialogFragment { 
    public ErrorDialogFragment() { } 
    @Override 
    public Dialog onCreateDialog(Bundle savedInstanceState) { 
     int errorCode = getArguments().getInt(DIALOG_ERROR); 
     int requestCode = getArguments().getInt(DIALOG_ERROR); 
     return GooglePlayServicesUtil.getErrorDialog(errorCode, getActivity(), requestCode); 
    } 
    @Override 
    public void onDismiss(DialogInterface dialog) { 
     getActivity().finish(); 
    } 
    } 

    private static class GA { 
    private static final String ACC_NAME = "account_name"; 
    public static final int FAIL = -1; 
    public static final int UNCHANGED = 0; 
    public static final int CHANGED = +1; 

    private static String mCurrEmil = null; // cache locally 
    private static String mPrevEmil = null; // cache locally 

    public static Account[] getAllAccnts(Context ctx) { 
     return AccountManager.get(acx(ctx)).getAccountsByType(GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE); 
    } 

    public static Account getPrimaryAccnt(Context ctx, boolean bOneOnly) { 
     Account[] accts = getAllAccnts(ctx); 
     if (bOneOnly) 
     return accts == null || accts.length != 1 ? null : accts[0]; 
     return accts == null || accts.length == 0 ? null : accts[0]; 
    } 

    public static Account getActiveAccnt(Context ctx) { 
     return emil2Accnt(ctx, getActiveEmil(ctx)); 
    } 

    public static String getActiveEmil(Context ctx) { 
     if (mCurrEmil != null) { 
     return mCurrEmil; 
     } 
     mCurrEmil = ctx == null ? null : pfs(ctx).getString(ACC_NAME, null); 
     return mCurrEmil; 
    } 

    public static Account getPrevEmil(Context ctx) { 
     return emil2Accnt(ctx, mPrevEmil); 
    } 

    public static Account emil2Accnt(Context ctx, String emil) { 
     if (emil != null) { 
     Account[] accounts = 
     AccountManager.get(acx(ctx)).getAccountsByType(GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE); 
     for (Account account : accounts) { 
      if (emil.equalsIgnoreCase(account.name)) { 
      return account; 
      } 
     } 
     } 
     return null; 
    } 

    /** 
    * Stores a new email in persistent app storage, reporting result 
    * @param newEmil new email, optionally null 
    * @param ctx activity context 
    * @return FAIL, CHANGED or UNCHANGED (based on the following table) 
    * OLD NEW SAVED RESULT 
    * ERROR    FAIL 
    * null null null FAIL 
    * null new new  CHANGED 
    * old null old  UNCHANGED 
    * old != new new  CHANGED 
    * old == new new  UNCHANGED 
    */ 
    public static int setEmil(Context ctx, String newEmil) { 
     int result = FAIL; // 0 0 

     mPrevEmil = getActiveEmil(ctx); 
     if  ((mPrevEmil == null) && (newEmil != null)) { 
     result = CHANGED; 
     } else if ((mPrevEmil != null) && (newEmil == null)) { 
     result = UNCHANGED; 
     } else if ((mPrevEmil != null) && (newEmil != null)) { 
     result = mPrevEmil.equalsIgnoreCase(newEmil) ? UNCHANGED : CHANGED; 
     } 
     if (result == CHANGED) { 
     mCurrEmil = newEmil; 
     pfs(ctx).edit().putString(ACC_NAME, newEmil).apply(); 
     } 
     return result; 
    } 
    public static void removeActiveAccnt(Context ctx) { 
     mCurrEmil = null; 
     pfs(ctx).edit().remove(ACC_NAME).apply(); 
    } 

    private static Context acx(Context ctx) { 
     return ctx == null ? null : ctx.getApplicationContext(); 
    } 
    private static SharedPreferences pfs(Context ctx) { 
     return ctx == null ? null : PreferenceManager.getDefaultSharedPreferences(acx(ctx)); 
    } 
    } 
} 

顺便说一句,我知道如何拼写 '电子邮件', '埃米尔' 正好是我叔叔的名字,我无法抗拒:-)

UPDATE(2015-APR-11) :

我最近重新访问了处理Google云端硬盘授权和帐户切换的代码。结果可以找到here,它支持REST和GDAA apis。

+0

** boolean mIsInAuth **为我做了诡计 - 想知道为什么这不在Google开发文档中... – m02ph3u5 2014-12-16 15:06:16

+0

@ m02ph3u5 - 它*是*在他们的文档中,它被称为__mIntentInProgress__(请参阅:https://开发人员.google.com/+/mobile/android /登录) – 2015-01-23 21:16:00

+0

@ Dev-iL无法找到* mIntentInProgess *加上它在G +文档中 - 为什么它不在GoogleApiClient文档中? – m02ph3u5 2015-01-25 15:06:47

3

发生这种情况是因为第一次登录/授权android继续使用相同的默认帐户参数。如果要避免循环并确保选择器再次显示,则必须在重新连接之前调用Plus.AccountApi.clearDefaultAccount(mGoogleApiClient)来完全清除默认帐户。

要实现这一点,您必须添加加号。API范围的GoogleApiClient建设者:

 mGoogleApiClient = new GoogleApiClient.Builder(this) 
      .addApi(Drive.API) 
      .addApi(Plus.API) 
      .addScope(Drive.SCOPE_FILE) 
      .addConnectionCallbacks(this) 
      .addOnConnectionFailedListener(this) 
      .build(); 

然后你就可以重建的API客户端并连接到不同的帐户,然后清除默认帐户(重建API客户不断变化的帐户时避免问题):

// if the api client existed, we terminate it 
    if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) { 
     Plus.AccountApi.clearDefaultAccount(mGoogleApiClient); 
     mGoogleApiClient.disconnect(); 
    } 
    // build new api client to avoid problems reusing it 
    mGoogleApiClient = new GoogleApiClient.Builder(this) 
      .addApi(Drive.API) 
      .addApi(Plus.API) 
      .addScope(Drive.SCOPE_FILE) 
      .setAccountName(account) 
      .addConnectionCallbacks(this) 
      .addOnConnectionFailedListener(this) 
      .build(); 
    mGoogleApiClient.connect(); 

以这种方式使用Plus.API范围不需要额外的权限或API激活。我希望这可以帮助你解决问题。

+0

以我的例子来说,它没有用。你能提出一个用Google登录的好例子吗? ........我试过这个地方,我有这条线addApi:.addApi(Plus.API,Plus.PlusOptions.builder()。build())..并得到以下错误:失败将结果ResultInfo {who = null,request = 0,result = 0,data = null}传递给activity {com ... androidgoogleplusloginexample.AndroidGooglePlusExample}:java.lang.IllegalStateException:必须连接GoogleApiClient。 .....谢谢 – gnB 2015-04-10 23:17:56

+0

我编辑了我的答案,使连接示例更加详细和最新。正如你所看到的,如果api客户端已经连接到一个帐户,我只清除默认帐户。如果没有,你会得到你描述的错误。我想你想在第一次连接之前清除默认帐户,这就是为什么你会得到错误。只是在重新连接到另一个帐户时才这样做。 – jmart 2015-04-11 08:59:22