Dostęp do interfejsów API Google za pomocą GoogleApiClient (wycofany)

Możesz użyć obiektu GoogleApiClient („klienta interfejsu API Google”), aby uzyskać dostęp do interfejsów API Google dostępnych w bibliotece Usług Google Play (np. Logowanie przez Google, Gry czy Dysk). Klient interfejsu Google API zapewnia wspólny punkt wejścia do Usług Google Play i zarządza połączeniem sieciowym między urządzeniem użytkownika a poszczególnymi usługami Google.

Nowszy interfejs GoogleApi i jego implementacje są jednak łatwiejsze w użyciu i stanowią preferowany sposób korzystania z interfejsów API Usług Google Play. Zobacz Uzyskiwanie dostępu do interfejsów API Google.

Z tego przewodnika dowiesz się, jak:

  • Automatycznie zarządzaj połączeniem z Usługami Google Play.
  • Wykonuj synchroniczne i asynchroniczne wywołania interfejsu API w dowolnej usłudze Google Play.
  • W tych rzadkich przypadkach, gdy jest to konieczne, ręcznie zarządzaj połączeniem z Usługami Google Play. Więcej informacji znajdziesz w sekcji Połączenia zarządzane ręcznie.
Rysunek 1. Ilustracja pokazująca, jak klient interfejsu API Google zapewnia interfejs do łączenia i wykonywania wywołań do dowolnych dostępnych usług Google Play, takich jak Gry Google Play i Dysk Google.

Na początek musisz zainstalować bibliotekę Usług Google Play (w wersji 15 lub nowszej) pakietu Android SDK. W razie potrzeby wykonaj instrukcje podane w artykule Konfigurowanie pakietu SDK Usług Google Play.

Rozpoczynanie automatycznie zarządzanego połączenia

Po połączeniu projektu z biblioteką Usług Google Play utwórz instancję GoogleApiClient za pomocą interfejsów API GoogleApiClient.Builder w metodzie onCreate() Twojej aktywności. Klasa GoogleApiClient.Builder udostępnia metody umożliwiające określenie interfejsów API Google, których chcesz używać, oraz odpowiednich zakresów protokołu OAuth 2.0. Oto przykładowy kod, który tworzy instancję GoogleApiClient łączącą się z usługą Dysk Google:

GoogleApiClient mGoogleApiClient = new GoogleApiClient.Builder(this)
    .enableAutoManage(this /* FragmentActivity */,
                      this /* OnConnectionFailedListener */)
    .addApi(Drive.API)
    .addScope(Drive.SCOPE_FILE)
    .build();

Do jednego GoogleApiClient możesz dodać wiele interfejsów API i zakresów, dołączając dodatkowe wywołania do addApi() i addScope().

Ważne: jeśli dodajesz interfejs API Wearable razem z innymi interfejsami API do GoogleApiClient, na urządzeniach, które nie mają zainstalowanej aplikacji Wear OS, mogą wystąpić błędy połączenia z klientem. Aby uniknąć błędów połączenia, wywołaj metodę addApiIfAvailable() i przekaż interfejs Wearable API. Pozwoli to klientowi bezproblemowo obsługiwać brakujący interfejs API. Więcej informacji znajdziesz w artykule na temat uzyskiwania dostępu do interfejsu Wearable API.

Aby rozpocząć połączenie zarządzane automatycznie, musisz określić implementację interfejsu OnConnectionFailedListener, która będzie otrzymywać nierozstrzygnięte błędy połączenia. Gdy Twoja automatycznie zarządzana instancja GoogleApiClient spróbuje połączyć się z interfejsami API Google, automatycznie wyświetli interfejs, aby spróbować rozwiązać możliwe problemy z połączeniem (np. jeśli konieczne będzie zaktualizowanie Usług Google Play). Jeśli wystąpi błąd, którego nie można naprawić, otrzymasz wywołanie do onConnectionFailed().

Możesz też określić opcjonalną implementację interfejsu ConnectionCallbacks, jeśli aplikacja musi wiedzieć, kiedy połączenie zarządzane automatycznie zostaje nawiązane lub zawieszone. Jeśli na przykład aplikacja wykonuje wywołania zapisu danych w interfejsach API Google, należy je wywoływać dopiero po wywołaniu metody onConnected().

Oto przykładowa aktywność, która implementuje interfejsy wywołania zwrotnego i dodaje je do klienta Google API:

import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import gms.drive.*;
import android.support.v4.app.FragmentActivity;

public class MyActivity extends FragmentActivity
        implements OnConnectionFailedListener {
    private GoogleApiClient mGoogleApiClient;

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

        // Create a GoogleApiClient instance
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .enableAutoManage(this /* FragmentActivity */,
                                  this /* OnConnectionFailedListener */)
                .addApi(Drive.API)
                .addScope(Drive.SCOPE_FILE)
                .build();

        // ...
    }

    @Override
    public void onConnectionFailed(ConnectionResult result) {
        // An unresolvable error has occurred and a connection to Google APIs
        // could not be established. Display an error message, or handle
        // the failure silently

        // ...
    }
}

Instancja GoogleApiClient połączy się automatycznie po wywołaniu funkcji onStart() i rozłączy się po wywołaniu onStop(). Po skompilowaniu GoogleApiClient aplikacja może od razu rozpocząć wysyłanie żądań odczytu do interfejsów API Google, bez czekania na zakończenie połączenia.

Komunikacja z usługami Google

Po nawiązaniu połączenia klient może odczytywać i zapisywać wywołania za pomocą interfejsów API danej usługi, dla których aplikacja jest autoryzowana, zgodnie z interfejsami API i zakresami dodanymi do instancji GoogleApiClient.

Uwaga: przed nawiązaniem połączenia z określonymi usługami Google konieczne może być zarejestrowanie aplikacji w Google Developers Console. Instrukcje znajdziesz w odpowiednim przewodniku dla początkujących dotyczącym używanego przez Ciebie interfejsu API, takiego jak Dysk Google lub Logowanie przez Google.

Gdy wykonujesz żądanie odczytu lub zapisu za pomocą GoogleApiClient, klient interfejsu API zwraca obiekt PendingResult, który reprezentuje żądanie. Dzieje się tak od razu, zanim żądanie zostanie dostarczone do usługi Google wywoływanej przez Twoją aplikację.

Oto na przykład żądanie odczytu pliku z Dysku Google zawierającego obiekt PendingResult:

Query query = new Query.Builder()
        .addFilter(Filters.eq(SearchableField.TITLE, filename));
PendingResult<DriveApi.MetadataBufferResult> result = Drive.DriveApi.query(mGoogleApiClient, query);

Gdy aplikacja doda obiekt PendingResult, może określić, czy żądanie ma być obsługiwane jako wywołanie asynchroniczne czy synchroniczne.

Wskazówka: gdy aplikacja nie jest połączona z Usługami Google Play, może dodawać do kolejki żądania odczytu. Aplikacja może na przykład wywoływać metody odczytu pliku z Dysku Google niezależnie od tego, czy wystąpienie GoogleApiClient jest już połączone. Po nawiązaniu połączenia wykonywane są umieszczone w kolejce żądania odczytu. Żądania zapisu generują błąd, jeśli aplikacja wywołuje metody zapisu w Usługach Google Play, gdy klient interfejsu API Google nie jest połączony.

Używanie wywołań asynchronicznych

Aby żądanie było asynchroniczne, wywołaj metodę setResultCallback() w PendingResult i podaj implementację interfejsu ResultCallback. Na przykład żądanie zostało wykonane asynchronicznie:

private void loadFile(String filename) {
    // Create a query for a specific filename in Drive.
    Query query = new Query.Builder()
            .addFilter(Filters.eq(SearchableField.TITLE, filename))
            .build();
    // Invoke the query asynchronously with a callback method
    Drive.DriveApi.query(mGoogleApiClient, query)
            .setResultCallback(new ResultCallback<DriveApi.MetadataBufferResult>() {
        @Override
        public void onResult(DriveApi.MetadataBufferResult result) {
            // Success! Handle the query result.
            // ...
        }
    });
}

Gdy aplikacja odbiera obiekt Result w wywołaniu zwrotnym onResult(), jest on dostarczany jako instancja odpowiedniej podklasy określonej przez używany przez Ciebie interfejs API, np. DriveApi.MetadataBufferResult.

Korzystanie z wywołań synchronicznych

Jeśli chcesz, aby Twój kod był wykonywany w ściśle określonej kolejności, np. dlatego, że wynik jednego wywołania jest potrzebny jako argument do innego, możesz uprzedzić żądanie, aby było synchroniczne, wywołując metodę await() w metodzie PendingResult. Spowoduje to zablokowanie wątku i zwrócenie obiektu Result po zakończeniu żądania. Ten obiekt jest dostarczany jako instancja odpowiedniej podklasy określonej przez używany przez Ciebie interfejs API, np. DriveApi.MetadataBufferResult.

Wywołanie await() blokuje wątek do czasu pojawienia się wyniku, dlatego aplikacja nie powinna nigdy wysyłać synchronicznych żądań do interfejsów API Google w wątku interfejsu. Aplikacja może utworzyć nowy wątek za pomocą obiektu AsyncTask i wykorzystać ten wątek do wysłania żądania synchronicznego.

Poniższy przykład pokazuje, jak wysłać żądanie pliku do Dysku Google w formie wywołania synchronicznego:

private void loadFile(String filename) {
    new GetFileTask().execute(filename);
}

private class GetFileTask extends AsyncTask {
    protected void doInBackground(String filename) {
        Query query = new Query.Builder()
                .addFilter(Filters.eq(SearchableField.TITLE, filename))
                .build();
        // Invoke the query synchronously
        DriveApi.MetadataBufferResult result =
                Drive.DriveApi.query(mGoogleApiClient, query).await();

        // Continue doing other stuff synchronously
        // ...
    }
}

Dostęp do interfejsu Wearable API

Interfejs Wearable API zapewnia kanał komunikacyjny aplikacji działających na urządzeniach do noszenia. Składa się on z zestawu obiektów danych, które system może wysyłać i synchronizować, oraz z detektorów, które za pomocą warstwy danych powiadamiają aplikacje o ważnych zdarzeniach. Interfejs Wearable API jest dostępny na urządzeniach z Androidem 4.3 (poziom interfejsu API 18) lub nowszym, gdy urządzenie do noszenia jest podłączone i zainstalowana jest aplikacja towarzysząca na Wear OS.

Korzystanie z samodzielnego interfejsu Wearable API

Jeśli Twoja aplikacja używa interfejsu API do noszenia na ręku, ale nie innych interfejsów API Google, możesz dodać ten interfejs API, wywołując metodę addApi(). Z przykładu poniżej dowiesz się, jak dodać interfejs API do noszenia do instancji GoogleApiClient:

GoogleApiClient mGoogleApiClient = new GoogleApiClient.Builder(this)
    .enableAutoManage(this /* FragmentActivity */,
                      this /* OnConnectionFailedListener */)
    .addApi(Wearable.API)
    .build();

W sytuacjach, gdy interfejs API do noszenia jest niedostępny, żądania połączenia zawierające interfejs API do noszenia kończą się błędem API_UNAVAILABLE.

Z tego przykładu dowiesz się, jak sprawdzić, czy interfejs API do noszenia jest dostępny:

// Connection failed listener method for a client that only
// requests access to the Wearable API
@Override
public void onConnectionFailed(ConnectionResult result) {
    if (result.getErrorCode() == ConnectionResult.API_UNAVAILABLE) {
        // The Wearable API is unavailable
    }
    // ...
}

Używanie interfejsu Wearable API z innymi interfejsami API Google

Jeśli Twoja aplikacja korzysta z interfejsu Wearable API oraz innych interfejsów API Google, wywołaj metodę addApiIfAvailable() i prześlij Wearable API, aby sprawdzić, czy jest ona dostępna. Dzięki temu możesz ułatwić swojej aplikacji obsługę przypadków, gdy interfejs API jest niedostępny.

Poniższy przykład pokazuje, jak uzyskać dostęp do interfejsu Wearable API razem z Drive API:

// Create a GoogleApiClient instance
mGoogleApiClient = new GoogleApiClient.Builder(this)
        .enableAutoManage(this /* FragmentActivity */,
                          this /* OnConnectionFailedListener */)
        .addApi(Drive.API)
        .addApiIfAvailable(Wearable.API)
        .addScope(Drive.SCOPE_FILE)
        .build();

W powyższym przykładzie GoogleApiClient może połączyć się z Dyskiem Google bez łączenia się z Wearable API, jeśli jest on niedostępny. Po połączeniu instancji GoogleApiClient przed wykonaniem wywołań interfejsu API upewnij się, że interfejs Wearable API jest dostępny:

boolean wearAvailable = mGoogleApiClient.hasConnectedApi(Wearable.API);

Ignorowanie błędów połączeń interfejsu API

Jeśli wywołasz addApi() i GoogleApiClient nie uda się nawiązać połączenia z tym interfejsem API, cała operacja połączenia tego klienta zakończy się niepowodzeniem i wywołasz wywołanie zwrotne onConnectionFailed().

Możesz zarejestrować błąd połączenia z interfejsem API, który ma zostać zignorowany za pomocą polecenia addApiIfAvailable(). Jeśli interfejs API dodany za pomocą addApiIfAvailable() nie połączy się z powodu nieodwracalnego błędu (np. API_UNAVAILABLE w przypadku Wear), interfejs API zostanie usunięty z GoogleApiClient, a klient będzie mógł połączyć się z innymi interfejsami API. Jeśli jednak w przypadku któregoś połączenia z interfejsem API wystąpi błąd, który może zostać odzyskany (np. intencja dotycząca udzielenia zgody przez OAuth), operacja połączenia z klientem zakończy się niepowodzeniem. Jeśli używasz połączenia zarządzanego automatycznie, GoogleApiClient w miarę możliwości spróbuje naprawić takie błędy. Gdy używasz połączenia zarządzanego ręcznie, do wywołania zwrotnego onConnectionFailed() jest przesyłany ConnectionResult zawierający intencję rozwiązania problemu. Niepowodzenia połączeń z interfejsami API są ignorowane tylko wtedy, gdy nie ma rozwiązania problemu, a interfejs API został dodany za pomocą addApiIfAvailable(). Aby dowiedzieć się, jak wdrożyć ręczną obsługę błędów połączenia, przeczytaj sekcję Obsługa błędów połączenia.

Interfejsy API dodane za pomocą addApiIfAvailable() mogą nie zawsze być dostępne w połączonej instancji GoogleApiClient, dlatego należy chronić wywołania tych interfejsów API, dodając kontrolę za pomocą hasConnectedApi(). Aby dowiedzieć się, dlaczego określony interfejs API nie nawiązał połączenia, gdy cała operacja połączenia zakończyła się sukcesem klienta, wywołaj getConnectionResult() i pobierz kod błędu z obiektu ConnectionResult. Jeśli klient wywoła interfejs API, gdy nie jest z nim połączony, wywołanie zakończy się niepowodzeniem z kodem stanu API_NOT_AVAILABLE.

Jeśli interfejs API, który dodajesz za pomocą addApiIfAvailable(), wymaga co najmniej 1 zakresu, dodaj te zakresy jako parametry w wywołaniu metody addApiIfAvailable(), a nie za pomocą metody addScope(). Zakresy dodane za pomocą tej metody mogą nie być żądane, jeśli połączenie z interfejsem API nie powiedzie się przed uzyskaniem zgody OAuth, podczas gdy zakresy dodane za pomocą addScope() są zawsze żądane.

Połączenia zarządzane ręcznie

W większości tego przewodnika dowiesz się, jak używać metody enableAutoManage do inicjowania automatycznie zarządzanego połączenia z błędami naprawianymi automatycznie. Prawie we wszystkich przypadkach jest to najlepszy i najłatwiejszy sposób łączenia się z interfejsami API Google z poziomu aplikacji na Androida. Są jednak sytuacje, w których warto użyć w aplikacji zarządzanego ręcznie połączenia z interfejsami API Google:

  • Aby uzyskiwać dostęp do interfejsów API Google poza działaniami lub zachować kontrolę nad połączeniem API
  • Aby dostosować obsługę i rozwiązywanie błędów połączenia

W tej sekcji znajdziesz przykłady tego i innych zaawansowanych zastosowań.

Rozpoczynanie połączenia zarządzanego ręcznie

Aby zainicjować ręcznie zarządzane połączenie z GoogleApiClient, musisz określić implementację interfejsów wywołania zwrotnego ConnectionCallbacks i OnConnectionFailedListener. Te interfejsy otrzymują wywołania zwrotne w odpowiedzi na metodę asynchroniczną connect(), gdy połączenie z Usługami Google Play zakończy się powodzeniem, niepowodzeniem lub zawieszeniem.

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

Podczas ręcznego zarządzania połączeniem musisz wywoływać metody connect() i disconnect() w odpowiednich momentach cyklu życia aplikacji. W kontekście działań sprawdzoną metodą jest wywołanie connect() w metodzie onStart() działania i disconnect() w metodzie onStop() działania. Metody connect() i disconnect() są wywoływane automatycznie podczas korzystania z połączenia zarządzanego automatycznie.

Jeśli używasz GoogleApiClient do łączenia się z interfejsami API, które wymagają uwierzytelniania, takimi jak Dysk Google czy Gry Google Play, istnieje duże prawdopodobieństwo, że pierwsza próba połączenia się nie powiedzie i aplikacja otrzyma wywołanie onConnectionFailed() z błędem SIGN_IN_REQUIRED, ponieważ konto użytkownika nie zostało określone.

Obsługa błędów połączenia

Gdy aplikacja otrzyma wywołanie zwrotne onConnectionFailed(), użyj wywołania hasResolution() z dostarczonego obiektu ConnectionResult. Jeśli zwraca wartość „true” (prawda), aplikacja może poprosić użytkownika o natychmiastowe podjęcie działania w celu rozwiązania błędu, wywołując metodę startResolutionForResult() w obiekcie ConnectionResult. W tej sytuacji metoda startResolutionForResult() działa tak samo jak startActivityForResult() i uruchamia działanie odpowiednie do kontekstu, które pomaga użytkownikowi naprawić błąd (na przykład działanie, które pomaga użytkownikowi wybrać konto).

Jeśli hasResolution() zwraca wartość false (fałsz), aplikacja powinna wywołać metodę GoogleApiAvailability.getErrorDialog(), przekazując kod błędu do tej metody. Spowoduje to wyświetlenie odpowiedniego błędu Dialog z Usług Google Play. Może ono zawierać tylko komunikat o błędzie lub działanie uruchamiające działanie w celu usunięcia błędu (np. gdy użytkownik musi zainstalować nowszą wersję Usług Google Play).

Na przykład metoda wywołania zwrotnego onConnectionFailed() powinna teraz wyglądać tak:

public class MyActivity extends Activity
        implements ConnectionCallbacks, OnConnectionFailedListener {

    // Request code to use when launching the resolution activity
    private static final int REQUEST_RESOLVE_ERROR = 1001;
    // Unique tag for the error dialog fragment
    private static final String DIALOG_ERROR = "dialog_error";
    // Bool to track whether the app is already resolving an error
    private boolean mResolvingError = false;

    // ...

    @Override
    public void onConnectionFailed(ConnectionResult result) {
        if (mResolvingError) {
            // Already attempting to resolve an error.
            return;
        } else if (result.hasResolution()) {
            try {
                mResolvingError = true;
                result.startResolutionForResult(this, REQUEST_RESOLVE_ERROR);
            } catch (SendIntentException e) {
                // There was an error with the resolution intent. Try again.
                mGoogleApiClient.connect();
            }
        } else {
            // Show dialog using GoogleApiAvailability.getErrorDialog()
            showErrorDialog(result.getErrorCode());
            mResolvingError = true;
        }
    }

    // The rest of this code is all about building the error dialog

    /* Creates a dialog for an error message */
    private void showErrorDialog(int errorCode) {
        // Create a fragment for the error dialog
        ErrorDialogFragment dialogFragment = new ErrorDialogFragment();
        // Pass the error that should be displayed
        Bundle args = new Bundle();
        args.putInt(DIALOG_ERROR, errorCode);
        dialogFragment.setArguments(args);
        dialogFragment.show(getSupportFragmentManager(), "errordialog");
    }

    /* Called from ErrorDialogFragment when the dialog is dismissed. */
    public void onDialogDismissed() {
        mResolvingError = false;
    }

    /* A fragment to display an error dialog */
    public static class ErrorDialogFragment extends DialogFragment {
        public ErrorDialogFragment() { }

        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            // Get the error code and retrieve the appropriate dialog
            int errorCode = this.getArguments().getInt(DIALOG_ERROR);
            return GoogleApiAvailability.getInstance().getErrorDialog(
                    this.getActivity(), errorCode, REQUEST_RESOLVE_ERROR);
        }

        @Override
        public void onDismiss(DialogInterface dialog) {
            ((MyActivity) getActivity()).onDialogDismissed();
        }
    }
}

Gdy użytkownik zakończy okno wyświetlane przez funkcję startResolutionForResult() lub zamknie wiadomość przekazaną przez GoogleApiAvailability.getErrorDialog(), Twoja aktywność otrzyma wywołanie zwrotne onActivityResult() z kodem wyniku RESULT_OK. Aplikacja może wtedy ponownie wywołać metodę connect(). Na przykład:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_RESOLVE_ERROR) {
        mResolvingError = false;
        if (resultCode == RESULT_OK) {
            // Make sure the app is not already connected or attempting to connect
            if (!mGoogleApiClient.isConnecting() &&
                    !mGoogleApiClient.isConnected()) {
                mGoogleApiClient.connect();
            }
        }
    }
}

W powyższym kodzie prawdopodobnie udało Ci się zauważyć wartość logiczną mResolvingError. Informuje o stanie aplikacji, gdy użytkownik rozwiązuje problem, co pozwala uniknąć powtarzających się prób usunięcia tego samego błędu. Na przykład gdy okno selektora kont pomaga użytkownikowi w rozwiązaniu błędu SIGN_IN_REQUIRED, może on obrócić ekran. Spowoduje to odtworzenie Twojej aktywności i spowoduje to, że metoda onStart() zostanie ponownie wywołana, a następnie wywoła metodę connect() jeszcze raz. Spowoduje to utworzenie kolejnego wywołania startResolutionForResult(), które przed bieżącym selektorem konta tworzy kolejne okno wyboru konta.

Ta wartość logiczna spełnia swoje zadanie tylko wtedy, gdy występuje w instancjach aktywności. W następnej sekcji wyjaśniamy, jak utrzymać stan obsługi błędów w aplikacji pomimo innych działań i zdarzeń występujących na urządzeniu przez użytkownika.

Zachowaj stan podczas naprawiania błędu

Aby uniknąć wykonania kodu w narzędziu onConnectionFailed() podczas poprzedniej próby naprawienia błędu, musisz zachować wartość logiczna, która śledzi, czy aplikacja próbuje już naprawić błąd.

Jak pokazano w powyższym przykładzie kodu, aplikacja powinna ustawiać wartość logiczną na true za każdym razem, gdy wywołuje metodę startResolutionForResult() lub wyświetla okno z GoogleApiAvailability.getErrorDialog(). Następnie, gdy aplikacja otrzyma w wywołaniu zwrotnym onActivityResult() parametr RESULT_OK, ustaw wartość logiczną na false.

Aby śledzić wartość logiczną po ponownym uruchomieniu aktywności (np. gdy użytkownik obróci ekran), zapisz wartość logiczną w danych instancji zapisanych aktywności za pomocą metody onSaveInstanceState():

private static final String STATE_RESOLVING_ERROR = "resolving_error";

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putBoolean(STATE_RESOLVING_ERROR, mResolvingError);
}

Następnie przywróć zapisany stan podczas onCreate():

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

    // ...
    mResolvingError = savedInstanceState != null
            && savedInstanceState.getBoolean(STATE_RESOLVING_ERROR, false);
}

Możesz teraz bezpiecznie uruchomić aplikację i ręcznie połączyć się z Usługami Google Play.