2014-11-05 94 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