繁体   English   中英

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

[英]How to access the application data on Google Drive on Android with its REST API

Google 将其用于访问 Google 服务(即 Google Drive)的 Android API 搁置,并用 REST 取代它。

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

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

经过大量的搜索、困惑、挠头、偶尔的咒骂和大量的学习,我真的不想关心的事情,我想分享一些代码,这些代码实际上对我有用。

免责声明:我是一个菜鸟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"/>

认证

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

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

添加一个这样的块:

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

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

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

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

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

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

startActivityForResult(client.signInIntent, RC_SIGN_IN)

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

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

在失败时,这些方法中的任何一个都返回null ,所以准备好处理它。

现在我们有了一个经过身份验证的用户,我们该怎么做呢?

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

但是 Google 再次出手相救,并为我们提供了GoogleAuthUtil.getToken(this, account.account, "oauth2:https://www.googleapis.com/auth/drive.appdata")调用。

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

需要注意的是:这个方法执行一个网络请求,这意味着如果你试图在你的 UI 线程中执行它,它会在你面前抛出。

我创建了一个助手类,它模仿 Google 的“任务”对象的行为(和 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
}

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

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

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

获取文件列表

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

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

响应应该在files键下包含一个JSONArray ,然后您可以解析和绘制您想要的任何信息。

线程任务类

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

我并不是说这是通向这一点的方式,这只是我的“简直不知道有什么更好的”方式。

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

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

这不是您可以使用 Google Drive 做的所有事情,但这是一个开始,我希望这个小小的汇编将来可以为其他人省去一些悲伤。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM