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

Za pomocą obiektu GoogleApiClient („Klient interfejsu API Google”) możesz korzystać z interfejsów API Google udostępnianych w bibliotece Usług Google Play (takich jak Logowanie przez Google, Gry czy Dysk). Klient interfejsów API Google zapewnia wspólny punkt wejścia do usług Google Play i zarządza połączeniem sieciowym między urządzeniem użytkownika a każdą usługą Google.

Jednak nowszy interfejs GoogleApi i jego implementacje są łatwiejsze w użyciu i stanowią preferowany sposób dostępu do interfejsów API Usług Google Play. Zapoznaj się z artykułem Uzyskiwanie dostępu do interfejsów API Google.

Z tego przewodnika dowiesz się, jak:

  • Automatycznie zarządzaj połączeniem z Usługami Google Play.
  • Wykonywanie synchronicznych i asynchronicznych wywołań interfejsu API do dowolnych Usług Google Play.
  • W rzadkich przypadkach, gdy jest to konieczne, możesz ręcznie zarządzać połączeniem z Usługami Google Play. Więcej informacji znajdziesz w artykule Połączenia zarządzane ręcznie.
Rys. 1. Ilustracja pokazująca, jak klient interfejsu API Google zapewnia interfejs do łączenia i wywoływania dowolnych dostępnych usług Google Play, takich jak Gry Google Play czy Dysk Google.

Najpierw zainstaluj bibliotekę Usług Google Play (w wersji 15 lub nowszej) dla pakietu SDK do Androida. Wykonaj instrukcje podane w artykule Konfigurowanie pakietu SDK Usług Google Play.

Uruchamianie połączenia zarządzanego automatycznie

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 pozwalające określić interfejsy API Google, których chcesz używać, oraz żądane zakresy 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 tego samego obiektu GoogleApiClient możesz dodać wiele interfejsów API i wiele 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 na 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, aby klient mógł bezproblemowo obsłużyć brakujący interfejs API. Więcej informacji znajdziesz w artykule na temat uzyskiwania dostępu do interfejsu Wearable API.

Aby rozpocząć automatycznie zarządzane połączenie, musisz określić implementację interfejsu OnConnectionFailedListener, aby otrzymywać nierozwiązane błędy połączenia. Gdy automatycznie zarządzana instancja GoogleApiClient spróbuje połączyć się z interfejsami API Google, automatycznie wyświetli się interfejs użytkownika w celu podjęcia próby naprawienia możliwych problemów z połączeniem (na przykład gdy konieczna jest aktualizacja Usług Google Play). Jeśli wystąpi błąd, którego nie można naprawić, otrzymasz wywołanie 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 do interfejsów API Google, powinny one być wywoływane dopiero po wywołaniu metody onConnected().

Oto przykładowe działanie, które implementuje interfejsy wywołań zwrotnych i dodaje je do klienta interfejsu API Google:

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, gdy Twoja aktywność wywoła onStart(), i rozłączy się po wywołaniu onStop(). Po skompilowaniu GoogleApiClient aplikacja może od razu zacząć wysyłać żądania odczytu do interfejsów API Google bez oczekiwania na zakończenie połączenia.

Komunikacja z usługami Google

Po nawiązaniu połączenia klient może wykonywać wywołania odczytu i zapisu za pomocą interfejsów API związanych z daną usługą, do których autoryzowana jest Twoja aplikacja, zgodnie z interfejsami API i zakresami dodanymi przez Ciebie do instancji GoogleApiClient.

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

Gdy wykonujesz żądanie odczytu lub zapisu przy użyciu GoogleApiClient, klient interfejsu API zwraca obiekt PendingResult, który reprezentuje żądanie. Dzieje się to natychmiast przed dostarczeniem żądania do usługi Google, z której wywołuje Twoja aplikacja.

Oto przykładowe żądanie odczytu z Dysku Google pliku 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 będzie miała obiekt PendingResult, może określić, czy żądanie ma być obsługiwane jako wywołanie asynchroniczne czy synchroniczne.

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

Używanie wywołań asynchronicznych

Aby ustawić żądanie jako asynchroniczne, wywołaj metodę setResultCallback() w PendingResult i podaj implementację interfejsu ResultCallback. Oto przykład żądania 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 dostarczana jako instancja odpowiedniej podklasy określonej przez używany interfejs API, na przykład DriveApi.MetadataBufferResult.

Korzystanie z wywołań synchronicznych

Jeśli chcesz, aby Twój kod był wykonywany w ściśle zdefiniowanej kolejności, np. dlatego, że wynik jednego wywołania jest potrzebny jako argument dla drugiego, możesz synchronizować swoje żądanie, wywołując metodę await() na 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, na przykład DriveApi.MetadataBufferResult.

Wywołanie await() blokuje wątek do momentu uzyskania 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 przy użyciu obiektu AsyncTask i użyć tego wątku do wykonania żądania synchronicznego.

Poniższy przykład pokazuje, jak wysłać żądanie pliku do Dysku Google w ramach 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
        // ...
    }
}

Uzyskaj dostęp do interfejsu Wearable API

Interfejs Wearable API zapewnia kanał komunikacji dla aplikacji działających na urządzeniach mobilnych i urządzeniach do noszenia. Interfejs API składa się ze zbioru obiektów danych, które system może wysyłać i synchronizować, oraz ze 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 połączone jest urządzenie do noszenia i zainstalowana aplikacja towarzysząca dla Wear OS.

Korzystanie z samodzielnego interfejsu Wearable API

Jeśli Twoja aplikacja używa interfejsu Wearable API, ale nie innych interfejsów API Google, możesz go dodać, wywołując metodę addApi(). Poniższy przykład pokazuje, jak dodać interfejs API do noszenia do instancji GoogleApiClient:

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

Jeśli interfejs Wearable API jest niedostępny, żądania połączenia zawierające interfejs API do noszenia kończą się niepowodzeniem z kodem błędu API_UNAVAILABLE.

Ten przykład pokazuje, 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 używa interfejsu API do noszenia oraz innych interfejsów API Google, wywołaj metodę addApiIfAvailable() i przekaż interfejs API do noszenia, by sprawdzić, czy jest dostępna. Możesz skorzystać z tej opcji, aby ułatwić aplikacji bezproblemową obsługę przypadków, w których interfejs API jest niedostępny.

Ten przykład pokazuje, jak uzyskać dostęp do interfejsu Wearable API przy użyciu interfejsu 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 interfejsem API do noszenia, jeśli jest on niedostępny. Po połączeniu instancji GoogleApiClient przed wykonaniem wywołań interfejsu API sprawdź, czy interfejs Wearable API jest dostępny:

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

Ignorowanie błędów połączenia z interfejsem API

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

Za pomocą narzędzia addApiIfAvailable() możesz zarejestrować niepowodzenie połączenia interfejsu API, które ma być ignorowane. Jeśli interfejs API dodany za pomocą addApiIfAvailable() nie może się połączyć z powodu nieodwracalnego błędu (np. API_UNAVAILABLE w przypadku Wear), interfejs ten jest usuwany z GoogleApiClient i klient łączy się z innymi interfejsami API. Jeśli jednak któreś z połączeń interfejsu API zakończy się niepowodzeniem i wystąpi błąd, który można naprawić (np. intencja zgody OAuth), operacja łączenia się z klientem zakończy się niepowodzeniem. Podczas korzystania z połączenia zarządzanego automatycznie GoogleApiClient w miarę możliwości spróbuje naprawić takie błędy. W przypadku połączenia zarządzanego ręcznie do wywołania zwrotnego onConnectionFailed() dostarczany jest obiekt ConnectionResult z intencją rozwiązania. Niepowodzenia połączenia z interfejsem API są ignorowane tylko wtedy, gdy nie można znaleźć 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, zapoznaj się z sekcją Postępowanie z błędami połączenia.

Interfejsy API dodane za pomocą addApiIfAvailable() mogą nie być dostępne w połączonej instancji GoogleApiClient, dlatego wywołania tych interfejsów API warto zabezpieczyć przez dodanie kontroli za pomocą hasConnectedApi(). Aby dowiedzieć się, dlaczego dany interfejs API nie nawiązał połączenia, mimo że cała operacja połączenia zakończyła się sukcesem dla klienta, wywołaj metodę getConnectionResult() i pobierz kod błędu z obiektu ConnectionResult. Jeśli klient wywoła interfejs API, gdy nie jest on 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 je 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 nie uda się nawiązać połączenia API przed uzyskaniem zgody OAuth, podczas gdy zakresy dodane za pomocą addScope() są zawsze żądane.

Połączenia zarządzane ręcznie

Z większości tego przewodnika dowiesz się, jak za pomocą metody enableAutoManage inicjować automatycznie zarządzane połączenie z automatycznie usuwanymi błędami. Niemal we wszystkich przypadkach jest to najlepszy i najłatwiejszy sposób łączenia się z interfejsami API Google z aplikacji na Androida. W pewnych sytuacjach warto jednak użyć w aplikacji połączenia zarządzanego ręcznie z interfejsami API Google:

  • Aby uzyskać dostęp do interfejsów API Google poza aktywnością 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 takich 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łań zwrotnych ConnectionCallbacks i OnConnectionFailedListener. Te interfejsy otrzymują wywołania zwrotne w odpowiedzi na asynchroniczną metodę connect(), gdy połączenie z Usługami Google Play jest udane, nie działa lub zostanie zawieszone.

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

Przy ręcznym zarządzaniu połączeniem musisz wywoływać metody connect() i disconnect() w odpowiednich momentach cyklu życia aplikacji. W kontekście aktywności sprawdzoną metodą jest wywoływanie connect() w metodzie onStart() i disconnect() w metodzie aktywności onStop(). Podczas korzystania z połączenia zarządzanego automatycznie metody connect() i disconnect() są wywoływane automatycznie.

Jeśli używasz interfejsu GoogleApiClient do łączenia się z interfejsami API, które wymagają uwierzytelnienia, takimi jak Dysk Google czy Gry Google Play, istnieje duże prawdopodobieństwo, że pierwsza próba połączenia się nie powiedzie. Aplikacja otrzyma wywołanie onConnectionFailed() z komunikatem o błędzie SIGN_IN_REQUIRED, ponieważ nie określono konta użytkownika.

Obsługa błędów połączeń

Gdy aplikacja otrzyma wywołanie zwrotne onConnectionFailed(), wywołaj hasResolution() w podanym obiekcie ConnectionResult. Jeśli wartość to „prawda”, aplikacja może poprosić użytkownika o natychmiastowe działanie w celu naprawienia 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 dopasowane do kontekstu, który pomaga użytkownikowi usunąć błąd (np. działanie, które pomaga użytkownikowi wybrać konto).

Jeśli hasResolution() zwraca wartość fałsz, aplikacja powinna wywołać metodę GoogleApiAvailability.getErrorDialog(), przekazując do tej metody kod błędu. Spowoduje to zwrócenie wartości Dialog dostarczonej przez Usługi Google Play, która jest odpowiednia w przypadku błędu. Okno może zawierać po prostu komunikat z wyjaśnieniem błędu lub może też zawierać działanie uruchamiające działanie, które rozwiąże błąd (na przykład gdy użytkownik musi zainstalować nowszą wersję Usług Google Play).

Na przykład Twoja 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 ukończy okno dialogowe wyświetlane przez startResolutionForResult() lub odrzuci komunikat podany przez GoogleApiAvailability.getErrorDialog(), 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 zauważyłeś(-aś) wartość logiczną mResolvingError. Śledzi stan aplikacji, gdy użytkownik próbuje usunąć błąd, aby uniknąć powtarzających się prób rozwiązania tego samego błędu. Na przykład okno wyboru konta, które ma pomóc użytkownikowi w rozwiązaniu błędu SIGN_IN_REQUIRED, może obrócić ekran. Spowoduje to odtworzenie Twojej aktywności i ponowne wywołanie metody onStart(), która następnie wywoła connect(). Powoduje to kolejne wywołanie metody startResolutionForResult(), co powoduje utworzenie przed dotychczasowym oknem kolejnego okna wyboru konta.

Ta wartość logiczna spełnia swoje zamierzone działanie tylko wtedy, gdy pozostaje w wielu instancjach aktywności. W następnej sekcji wyjaśniamy, jak zachować stan obsługi błędów w aplikacji pomimo innych działań lub zdarzeń użytkownika, które wystąpią na urządzeniu.

Zachowywanie stanu podczas rozwiązywania błędu

Aby uniknąć wykonywania kodu w zadaniu onConnectionFailed(), gdy poprzednia próba rozwiązania błędu jest w toku, musisz zachować wartość logiczną śledzącą, czy aplikacja nie próbuje już naprawić błędu.

Jak widać w powyższym przykładzie kodu, aplikacja powinna ustawić wartość logiczną true przy każdym wywołaniu funkcji startResolutionForResult() lub wyświetleniu okna z GoogleApiAvailability.getErrorDialog(). Następnie, gdy aplikacja otrzyma wartość RESULT_OK w wywołaniu zwrotnym onActivityResult(), ustaw wartość logiczną na false.

Aby śledzić wartości logiczne po ponownym uruchomieniu aktywności (np. gdy użytkownik obraca ekran), zapisz wartość logiczną w zapisanych danych instancji za pomocą narzędzia 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 z onCreate():

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

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

Teraz możesz bezpiecznie uruchamiać aplikację i ręcznie łączyć się z Usługami Google Play.