Ref*_*ium 2 android kotlin google-drive-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"/>
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、执行的方法 ( GET、POST、PATCH、DELETE) 并从中构造一个 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。
响应应该包含一个JSONArray下files键,然后您可以解析并绘制您想要的任何信息。
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 做的所有事情,但它是一个开始,我希望这个小汇编能够在未来为其他人减轻一些痛苦。
| 归档时间: |
|
| 查看次数: |
1442 次 |
| 最近记录: |