簡體   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