通过 GoogleApiClient 访问 Google API(已弃用)

您可以使用 GoogleApiClient(“Google API 客户端”)对象访问 Google Play 服务库中提供的 Google API(例如 Google 登录、游戏和云端硬盘)。Google API 客户端为 Google Play 服务提供了一个公共入口点,并管理用户设备与各项 Google 服务之间的网络连接。

不过,较新的 GoogleApi 接口及其实现更易于使用,是访问 Play 服务 API 的首选方式。请参阅访问 Google API

本指南介绍了如何完成以下操作:

  • 自动管理您与 Google Play 服务之间的关联。
  • 对任何 Google Play 服务执行同步和异步 API 调用。
  • 在极少数情况下,必要时手动管理与 Google Play 服务的连接。如需了解详情,请参阅手动管理的连接
图 1:该图显示了 Google API 客户端如何通过提供接口来连接和调用任何可用的 Google Play 服务(例如 Google Play 游戏和 Google 云端硬盘)。

首先,您必须先安装适用于您的 Android SDK 的 Google Play 服务库(修订版 15 或更高版本)。如果您尚未按照设置 Google Play 服务 SDK 中的说明操作,请按照说明操作。

启动自动管理的连接

将项目关联到 Google Play 服务库后,请在 activity 的 onCreate() 方法中使用 GoogleApiClient.Builder API 创建一个 GoogleApiClient 实例。借助 GoogleApiClient.Builder 类提供的方法,您可以指定要使用的 Google API 和所需的 OAuth 2.0 范围。以下代码示例创建了一个与 Google 云端硬盘服务连接的 GoogleApiClient 实例:

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

您可以通过向 addApi()addScope() 附加额外的调用,向同一 GoogleApiClient 添加多个 API 和多个范围。

重要提示:如果您要将 Wearable API 与其他 API 一起添加到 GoogleApiClient,可能会在未安装 Wear OS 应用的设备上遇到客户端连接错误。为避免连接错误,请调用 addApiIfAvailable() 方法并传入 Wearable API,以允许您的客户端妥善处理缺失的 API。如需了解详情,请参阅访问 Wearable API

如需启动自动管理的连接,您必须为 OnConnectionFailedListener 接口指定一个实现,以接收无法解决的连接错误。当您的自动管理的 GoogleApiClient 实例尝试连接到 Google API 时,它会自动显示界面以尝试修复任何可解决的连接失败问题(例如,如果需要更新 Google Play 服务)。如果发生无法解析的错误,您将收到对 onConnectionFailed() 的调用。

如果您的应用需要知道自动管理的连接何时建立或暂停,您还可以为 ConnectionCallbacks 接口指定可选实现。例如,如果您的应用发出调用以将数据写入 Google API,则只能在调用 onConnected() 方法后调用这些调用。

以下是一个实现回调接口并将其添加到 Google API 客户端的示例 activity:

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

        // ...
    }
}

您的 GoogleApiClient 实例会在 activity 调用 onStart() 后自动连接,并在调用 onStop() 后断开连接。构建 GoogleApiClient 后,您的应用可以立即开始向 Google API 发出读取请求,无需等待连接完成。

与 Google 服务通信

连接后,您的客户端可以使用应用获得授权的特定于服务的 API(根据您添加到 GoogleApiClient 实例的 API 和范围指定)进行读取和写入调用。

注意:在调用特定的 Google 服务之前,您可能需要先在 Google Developer Console 中注册您的应用。如需了解相关说明,请参阅与您所用 API 对应的入门指南,例如 Google 云端硬盘Google 登录

当您使用 GoogleApiClient 执行读取或写入请求时,API 客户端会返回一个代表请求的 PendingResult 对象。此过程会立即发生,然后请求才会传送给您的应用所调用的 Google 服务。

例如,下面是一个从 Google 云端硬盘读取文件的请求,该文件提供了 PendingResult 对象:

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

在应用获取 PendingResult 对象后,应用便可以指定是将请求作为异步调用处理还是作为同步调用处理。

提示:您的应用可以在未连接到 Google Play 服务的情况下将读取请求加入队列。例如,应用可以调用方法来从 Google 云端硬盘读取文件,无论您的 GoogleApiClient 实例是否已连接。建立连接后,执行已加入队列的读取请求。如果您的应用在未连接 Google API 客户端的情况下调用 Google Play 服务写入方法,写入请求会生成错误。

使用异步调用

如需将请求设为异步请求,请对 PendingResult 调用 setResultCallback() 并提供 ResultCallback 接口的实现。例如,以下是异步执行的请求:

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

当应用在 onResult() 回调中收到 Result 对象时,它会作为您使用的 API 指定的相应子类的实例(例如 DriveApi.MetadataBufferResult)进行传递。

使用同步调用

如果您希望代码以严格定义的顺序执行,或许是因为一次调用的结果需要作为另一调用的结果作为参数,可以通过对 PendingResult 调用 await() 来将您的请求设为同步。这会阻塞线程,并在请求完成时返回 Result 对象。此对象将作为您使用的 API 指定的相应子类的实例(例如 DriveApi.MetadataBufferResult)传送。

由于调用 await() 会在结果到达之前阻塞线程,因此您的应用绝不应在界面线程上向 Google API 发出同步请求。应用可以使用 AsyncTask 对象创建新线程,并使用该线程发出同步请求。

以下示例展示了如何以同步调用的形式向 Google 云端硬盘发出文件请求:

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
        // ...
    }
}

访问 Wearable API

Wearable API 为在手持设备和穿戴式设备上运行的应用提供通信渠道。该 API 由一组数据对象(系统可以发送和同步)以及监听器组成,这些监听器使用数据层向您的应用通知重要事件。如果已连接穿戴式设备且设备上安装了 Wear OS 配套应用,则可在搭载 Android 4.3(API 级别 18)或更高版本的设备上使用 Wearable API

单独使用 Wearable API

如果您的应用使用 Wearable API,但未使用其他 Google API,您可以通过调用 addApi() 方法来添加此 API。以下示例展示了如何将 Wearable API 添加到 GoogleApiClient 实例:

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

Wearable API 不可用的情况下,包含 Wearable API 的连接请求会失败,并返回 API_UNAVAILABLE 错误代码。

以下示例展示了如何确定 Wearable API 是否可用:

// 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
    }
    // ...
}

将 Wearable API 与其他 Google API 结合使用

如果您的应用除了使用其他 Google API 之外,还使用 Wearable API,请调用 addApiIfAvailable() 方法并传入 Wearable API,以检查它是否可用。您可以使用此检查来帮助您的应用妥善处理 API 不可用的情况。

以下示例展示了如何访问 Wearable API 以及 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();

在上面的示例中,如果 Google 云端硬盘不可用,GoogleApiClient 无需连接到 Wearable API 即可成功连接。连接 GoogleApiClient 实例后,请先确保 Wearable API 可用,然后再进行 API 调用:

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

忽略 API 连接失败

如果您调用 addApi()GoogleApiClient 无法成功连接到该 API,则该客户端的整个连接操作将失败并触发 onConnectionFailed() 回调。

您可以使用 addApiIfAvailable() 注册要忽略的 API 连接失败项。如果使用 addApiIfAvailable() 添加的 API 因不可恢复的错误(例如 Wear 的 API_UNAVAILABLE)而无法连接,该 API 会从 GoogleApiClient 中移除,客户端会继续连接到其他 API。但是,如果任何 API 连接因可恢复的错误(如 OAuth 同意解析 intent)而失败,则客户端连接操作将失败。使用自动管理连接时,GoogleApiClient 将尝试解决此类错误。使用手动管理的连接时,包含解析 intent 的 ConnectionResult 会传递给 onConnectionFailed() 回调。仅当 API 连接失败没有解决方案且使用 addApiIfAvailable() 添加 API 时,系统才会忽略该 API 连接失败问题。如需了解如何实现手动连接失败处理,请参阅处理连接失败

由于使用 addApiIfAvailable() 添加的 API 可能并不总是存在于已连接的 GoogleApiClient 实例中,因此您应使用 hasConnectedApi() 添加一项检查来保护对这些 API 的调用。如需了解在客户端的整个连接操作成功时为什么特定 API 无法连接,请调用 getConnectionResult() 并从 ConnectionResult 对象获取错误代码。如果您的客户端在未连接到客户端时调用 API,则调用会失败并显示 API_NOT_AVAILABLE 状态代码。

如果您要通过 addApiIfAvailable() 添加的 API 需要一个或多个范围,请在 addApiIfAvailable() 方法调用中将这些范围添加为参数,而不要使用 addScope() 方法。如果在获得 OAuth 同意之前 API 连接失败,则可能无法请求使用此方法添加的范围,而始终请求使用 addScope() 添加的范围。

手动管理的连接

本指南的大部分内容介绍了如何使用 enableAutoManage 方法启动自动管理的连接并自动解决错误。在几乎所有情况下,这都是从 Android 应用连接到 Google API 的最佳且最简单的方法。不过,在某些情况下,您可能需要在应用中手动管理与 Google API 的连接:

  • 在 activity 之外访问 Google API 或保留对 API 连接的控制权
  • 自定义连接错误处理和解决方案

本部分提供了这些示例及其他高级用例。

启动手动管理的连接

如需启动与 GoogleApiClient 的手动管理连接,您必须为回调接口 ConnectionCallbacksOnConnectionFailedListener 指定实现。当与 Google Play 服务的连接成功、失败或被暂停时,这些接口会收到回调以响应异步 connect() 方法。

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

手动管理连接时,您需要在应用生命周期的适当时间点调用 connect()disconnect() 方法。在 activity 上下文中,最佳实践是在 activity 的 onStart() 方法中调用 connect(),并在 activity 的 onStop() 方法中调用 disconnect()。 使用自动管理连接时,系统会自动调用 connect()disconnect() 方法。

如果您使用 GoogleApiClient 连接到需要身份验证的 API(如 Google 云端硬盘或 Google Play 游戏),则您的第一次连接尝试很有可能会失败,并且您的应用会收到对 onConnectionFailed() 的调用,其中包含 SIGN_IN_REQUIRED 错误,因为未指定用户帐号。

处理连接失败

当应用收到对 onConnectionFailed() 回调的调用时,您应对提供的 ConnectionResult 对象调用 hasResolution()。如果它返回 true,您的应用可以通过对 ConnectionResult 对象调用 startResolutionForResult() 来请求用户立即采取措施来解决错误。在这种情况下,startResolutionForResult() 方法的行为与 startActivityForResult() 相同,它会启动适合上下文的 activity 以帮助用户解决错误的(例如,帮助用户选择帐号的 activity)。

如果 hasResolution() 返回 false,您的应用应调用 GoogleApiAvailability.getErrorDialog(),并将错误代码传递给此方法。这将返回 Google Play 服务提供的与错误对应的 Dialog。该对话框可能只是提供一条说明错误的消息,也可能提供一项操作来启动可解决该错误的 activity(例如,当用户需要安装较新版本的 Google Play 服务时)。

例如,您的 onConnectionFailed() 回调方法现在应如下所示:

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

用户完成 startResolutionForResult() 提供的对话框或关闭 GoogleApiAvailability.getErrorDialog() 提供的消息后,您的 activity 会收到包含 RESULT_OK 结果代码的 onActivityResult() 回调。然后,您的应用可以再次调用 connect()。例如:

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

在上面的代码中,您可能已经注意到了布尔值 mResolvingError。这样会在用户解析错误时跟踪应用状态,以避免重复尝试解决同一错误。例如,虽然系统会显示帐号选择器对话框以帮助用户解决 SIGN_IN_REQUIRED 错误,但用户可能会旋转屏幕。这会重新创建您的 activity,并导致系统再次调用 onStart() 方法,后者随后会再次调用 connect()。因此,系统会再次调用 startResolutionForResult(),从而在现有帐号选择器对话框的前面创建另一个帐号选择器对话框。

仅当此布尔值在 activity 实例之间持续存在时,此布尔值才有其预期用途。下一部分将介绍如何即使设备上发生了其他用户操作或事件,也保持应用的错误处理状态。

解决错误时维护状态

为避免在上一次尝试解决错误的过程中执行 onConnectionFailed() 中的代码,您需要保留一个布尔值,以跟踪您的应用是否已尝试解决某个错误。

如上面的代码示例所示,每当您的应用调用 startResolutionForResult() 或通过 GoogleApiAvailability.getErrorDialog() 显示对话框时,都应将布尔值设置为 true。然后,当您的应用在 onActivityResult() 回调中收到 RESULT_OK 时,将该布尔值设置为 false

如需在 activity 重启后(例如当用户旋转屏幕时)跟踪该布尔值,请使用 onSaveInstanceState() 将该布尔值保存在 activity 保存的实例数据中:

private static final String STATE_RESOLVING_ERROR = "resolving_error";

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

然后在 onCreate() 期间恢复保存的状态:

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

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

现在,您可以安全地运行应用并手动连接到 Google Play 服务了。