如何使用 REST API 访问 Android 上 Google Drive 上的应用程序数据

Ref*_*ium 2 android kotlin google-drive-api

Google 正在停止使用其用于访问 Google 服务(即 Google Drive)的 Android API,并用 REST 取代它。

虽然有“迁移指南”,但由于“重复类定义”或其他原因,它无法构建可供安装的 APK 包。

由于某些原因,很难找到一些关于如何通过 Android 使用 REST 访问 Google 服务的全面信息(最好使用操作系统本机可用的方法)。

Ref*_*ium 5

经过大量的搜索、困惑、挠头、偶尔的咒骂以及大量了解我真正不想关心的事情之后,我想分享一些实际上对我有用的代码。

免责声明:我是一个菜鸟 Android 程序员(他真的不知道如何选择自己的战斗),所以如果这里有一些东西让真正的 Android 巫师摇头,我希望你能原谅我。

所有代码示例都是用 Kotlin 和 Android Studio 编写的。

值得注意的是:这个小教程仅查询“应用程序数据文件夹”,scopes如果您想做其他事情,您将需要调整请求。

必要的准备

按照此处所述为您的应用程序创建一个项目和一个 OAuth 密钥。我为授权收集的许多信息都来自该地方,因此希望能找到一些相似之处。

您的项目的仪表板可以在https://console.developers.google.com/apis/dashboard找到

添加implementation "com.google.android.gms:play-services-auth:16.0.1"到您的应用程序 gradle 文件。此依赖性将用于身份验证目的。

将“互联网”支持添加到您的应用程序清单中

<uses-permission android:name="android.permission.INTERNET"/>
Run Code Online (Sandbox Code Playgroud)

正在验证

我们旅程的开始是身份验证。为此,我使用了GoogleSignIn框架。

创建一个活动(或使用您的主要活动,您的选择)并在那里重写onActivityResult方法。

添加一个像这样的块:

if (requestCode == RC_SIGN_IN) {
    GoogleSignIn.getSignedInAccountFromIntent(data)
        .addOnSuccessListener(::evaluateResponse)
        .addOnFailureListener { e ->
            Log.w(RecipeList.TAG, "signInResult:failed =" + e.toString())
            evaluateResponse(null)
        }
}
Run Code Online (Sandbox Code Playgroud)

RC_REQUEST_CODE是在伴生对象中定义为常量的任意选择的 ID 值。

一旦您想要执行身份验证(即通过单击按钮),您将需要启动我们刚刚声明回调的活动。

为此,您需要先准备身份验证请求。

GoogleSignIn.getClient(this, GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
        .requestIdToken("YourClientIDGoesHere.apps.googleusercontent.com")
        .requestScopes(Scope(Scopes.DRIVE_APPFOLDER))
        .build())
Run Code Online (Sandbox Code Playgroud)

此请求为您提供一个客户端对象,您可以通过调用立即开始使用。

startActivityForResult(client.signInIntent, RC_SIGN_IN)
Run Code Online (Sandbox Code Playgroud)

此调用将导致弹出授权屏幕(如果需要),允许用户选择一个帐户,然后再次关闭自身,将数据传递给onActivityResult

要获取之前登录的用户(无需启动新活动),您还可以GoogleSignIn.getLastSignedInAccount(this);在后台使用该方法。

如果这些方法中的任何一个失败,都会返回null,因此请准备好处理该问题。

现在我们有了一个经过身份验证的用户,我们该如何处理它呢?

我们要求一个身份验证令牌。现在,我们的账户对象中只有一个 idToken,这对于我们想要做的事情来说绝对没有用,因为它不允许我们调用 API。

但谷歌再次出手相救,为我们提供了电话GoogleAuthUtil.getToken(this, account.account, "oauth2:https://www.googleapis.com/auth/drive.appdata")

如果一切顺利,此调用将转发帐户信息并返回一个字符串:我们需要的身份验证令牌。

需要注意的是:此方法执行网络请求,这意味着如果您尝试在 UI 线程中执行它,它将出现在您面前。

我创建了一个帮助器类,它模仿 Google 'Task' 对象的行为(和 API),它负责在线程上调用方法并通知调用线程它已完成的细节。

将身份验证令牌保存在您可以再次找到的地方,授权(最终)完成。

查询API

这部分比前一部分要简单得多,并且与Google Drive REST API密切相关

所有网络请求都需要在“非 UI”线程上执行,这就是为什么我将它们包装在辅助类中,以便在有数据要显示时通知我。

private fun performNet(url: String, method: String, onSuccess: (JSONObject) -> Unit)
{
    ThreadedTask<String>()
        .addOnSuccess { onSuccess(JSONObject(it))               }
        .addOnFailure { Log.w("DriveSync", "Sync failure $it")  }
        .execute(executor) {
            val url = URL(url)
            with (url.openConnection() as HttpURLConnection)
            {
                requestMethod = method
                useCaches   = false
                doInput     = true
                doOutput    = false
                setRequestProperty("Authorization", "Bearer $authToken")

                processNetResponse(responseCode, this)
            }
        }
}

private fun processNetResponse(responseCode: Int, connection: HttpURLConnection) : String
{
    var responseData = "No Data"
    val requestOK    = (responseCode == HttpURLConnection.HTTP_OK)

    BufferedReader(InputStreamReader(if (requestOK) connection.inputStream else connection.errorStream))
        .use {
            val response = StringBuffer()

            var inputLine = it.readLine()
            while (inputLine != null) {
                response.append(inputLine)
                inputLine = it.readLine()
            }
            responseData = response.toString()
        }

    if (!requestOK)
        throw Exception("Bad request: $responseCode ($responseData)")

    return responseData
}
Run Code Online (Sandbox Code Playgroud)

这段代码是一个相当通用的辅助函数,我从各种来源组合在一起,本质上只是获取要查询的 URL、执行的方法 ( GETPOSTPATCHDELETE) 并从中构造一个 HTTP 请求。

我们之前在授权过程中获得的身份验证令牌将作为标头传递给请求,以向 Google 进行身份验证并将我们自己标识为“用户”。

如果一切正常,Google 将回复 HTTP_OK (200) 并将onSuccess被调用,这会将 JSON 回复转换为 JSONObject,然后将其传递给我们之前注册的评估函数。

获取文件列表

performNet("https://www.googleapis.com/drive/v3/files?spaces=appDataFolder", "GET")
Run Code Online (Sandbox Code Playgroud)

spaces参数用于告诉 Google,我们不想看到根文件夹,而是看到应用程序数据文件夹。如果没有此参数,请求将失败,因为我们只请求访问 appDataFolder。

响应应该包含一个JSONArrayfiles键,然后您可以解析并绘制您想要的任何信息。

ThreadTask 类

该帮助器类封装了在不同上下文上执行操作所需的步骤,并在完成后在实例化线程上执行回调。

我并不是说这就是解决问题的方法,这只是我的“只是不知道更好”的方法。

import android.os.Handler
import android.os.Looper
import android.os.Message
import java.lang.Exception
import java.util.concurrent.Executor

class ThreadedTask<T> {
    private val onSuccess = mutableListOf<(T) -> Unit>()
    private val onFailure = mutableListOf<(String) -> Unit>()
    private val onComplete = mutableListOf<() -> Unit>()

    fun addOnSuccess(handler: (T) -> Unit)      : ThreadedTask<T> { onSuccess.add(handler); return this; }
    fun addOnFailure(handler: (String) -> Unit) : ThreadedTask<T> { onFailure.add(handler); return this; }
    fun addOnComplete(handler: () -> Unit)      : ThreadedTask<T> { onComplete.add(handler);return this; }

    /**
     * Performs the passed code in a threaded context and executes Success/Failure/Complete handler respectively on the calling thread.
     * If any (uncaught) exception is triggered, the task is considered 'failed'.
     * Call this method last in the chain to avoid race conditions while adding the handlers.
     *
     */
    fun execute(executor: Executor, code: () -> T)
    {
        val handler = object : Handler(Looper.getMainLooper()) {
            override fun handleMessage(msg: Message) {
                super.handleMessage(msg)
                publishResult(msg.what, msg.obj)
            }
        }

        executor.execute {
            try {
                handler.obtainMessage(TASK_SUCCESS, code()).sendToTarget()
            } catch (exception: Exception) {
                handler.obtainMessage(TASK_FAILED, exception.toString()).sendToTarget()
            }
        }
    }

    private fun publishResult(returnCode: Int, returnValue: Any)
    {
        if (returnCode == TASK_FAILED)
            onFailure.forEach { it(returnValue as String) }
        else
            onSuccess.forEach { it(returnValue as T) }
        onComplete.forEach { it() }

        // Removes all handlers, cleaning up potential retain cycles.
        onFailure.clear()
        onSuccess.clear()
        onComplete.clear()
    }

    companion object {
        private const val TASK_SUCCESS = 0
        private const val TASK_FAILED  = 1
    }
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,执行顺序很重要。首先需要将回调添加到类对象,最后需要调用execute它并为其提供要运行线程的执行器,当然还有要执行的代码。

这并不是您可以使用 Google Drive 做的所有事情,但它是一个开始,我希望这个小汇编能够在未来为其他人减轻一些痛苦。