2014-11-05 6 views
5

내 프로젝트에 com.google.android.gms.com.api.GoogleApiClient를 구현하려고합니다.GoogleApiClient에서 연결에 실패했습니다.

문제는 연결을 시도 할 때마다 실행중인 보류중인 인 텐트와 함께 onConnectionFailed 수신기에 대한 호출이 다시 발생한다는 것입니다. 새로 설치하는 경우 가장 먼저 대기중인 인 텐트가 계정 선택 화면을 시작합니다. 이것은 예상된다. 응용 프로그램 관리자에서 응용 프로그램의 데이터가 지워지지 않는 한, 이후의 모든 응용 프로그램 재시작은 계정 선택을 우회합니다.

계정 선택 화면이 끝나면 로그인 화면이 간결하게 표시됩니다. 그것은 결코 서명하지 않습니다. 로그인 화면이 깜박이고 클라이언트 연결을 시도하면 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

답변

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

BTW, 나는 '이메일'을 철자하는 방법을 알고, '에밀'내 삼촌의 이름이 우연히 내가 :-) 저항 할 수 없었다

UPDATE (2015 4 월 11 일) :

최근에 Google 드라이브 인증 및 계정 전환을 처리하는 코드를 다시 방문했습니다. 결과는 here이며 REST 및 GDAA API를 모두 지원합니다.

+0

** boolean mIsInAuth **이 트릭을 만들었습니다. Google 개발자 문서에없는 이유가 궁금합니다 ... – m02ph3u5

+0

@ m02ph3u5 - 문서에있는 내용은 __mIntentInProgress__ (https : // developers 참조)입니다. .google.com/+/mobile/android/sign-in) –

+0

@ Dev-iL은 * mIntentInProgess *를 찾을 수 없으며 G + 문서에 있습니다. 왜 GoogleApiClient 문서에 없습니까? – m02ph3u5

3

이것은 첫 번째 로그인/승인 후에 Android가 동일한 기본 계정 매개 변수를 계속 사용하기 때문에 발생합니다. 루프를 피하고 피커가 다시 표시되는지 확인하려면 다시 연결하기 전에 Plus.AccountApi.clearDefaultAccount (mGoogleApiClient)를 호출하여 기본 계정을 완전히 지워야합니다.

이렇게하려면 Plus를 추가해야합니다.GoogleApiClient 빌더 API 범위 :

 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())에 대해 다음과 같은 오류가 발생했습니다. androidgoogleplusloginexample.AndroidGooglePlusExample} 결과 : 결과 ResultInfo {who = null, request = 0, result = 0, data = null} : java.lang.IllegalStateException : GoogleApiClient가 연결되어야합니다. ..... Thanks – gnB

+0

연결 예제를보다 자세하고 최신으로 만들기 위해 내 대답을 편집했습니다. 보시다시피 api 클라이언트가 이미 계정에 연결되어있는 경우에만 기본 계정 만 지 웁니다. 그렇지 않은 경우 설명 된 오류가 발생합니다. 첫 번째 연결 전에 기본 계정을 지우려고하는 것 같아 오류가 발생하는 이유입니다. 다른 계정에 다시 연결하는 경우에만 수행하십시오. – jmart

관련 문제