如何在Android上使用RESTAPI访问Google Drive上的应用程序数据
谷歌正在将其用于访问谷歌服务(即谷歌硬盘)的安卓API放在rest上,并将其替换为rest 虽然有“迁移指南”,但由于“重复类定义”或其他原因,它无法构建准备好安装的APK包如何在Android上使用RESTAPI访问Google Drive上的应用程序数据,android,kotlin,google-drive-api,Android,Kotlin,Google Drive Api,谷歌正在将其用于访问谷歌服务(即谷歌硬盘)的安卓API放在rest上,并将其替换为rest 虽然有“迁移指南”,但由于“重复类定义”或其他原因,它无法构建准备好安装的APK包 由于某些原因,很难找到关于如何通过Android使用REST访问谷歌服务的全面信息(最好是使用操作系统自带的方法)。经过大量搜索、困惑、挠头、偶尔咒骂以及对我不想关心的事情的大量了解,我想和大家分享一些代码,它们实际上对我有用 免责声明:我是一个新手Android程序员(他真的不知道如何选择他的战斗),所以如果这里有一些事
由于某些原因,很难找到关于如何通过Android使用REST访问谷歌服务的全面信息(最好是使用操作系统自带的方法)。经过大量搜索、困惑、挠头、偶尔咒骂以及对我不想关心的事情的大量了解,我想和大家分享一些代码,它们实际上对我有用 免责声明:我是一个新手Android程序员(他真的不知道如何选择他的战斗),所以如果这里有一些事情,让真正的Android奇才摇头,我希望你能原谅我 所有代码示例都是用Kotlin和Android Studio编写的 值得注意的是:在这个小教程中只查询“应用程序数据文件夹”,如果您想做其他事情,您需要调整请求的
范围
必要的准备
performNet("https://www.googleapis.com/drive/v3/files?spaces=appDataFolder", "GET")
如前所述,为应用程序创建项目和OAuth密钥。我为授权收集的许多信息都来自那个地方,所以希望能找到一些相似之处
项目的仪表板可在以下位置找到:
将implementation“com.google.android.gms:play services auth:16.0.1”
添加到您的应用程序渐变文件中。此依赖项将用于身份验证目的
将“internet”支持添加到应用程序清单中
<uses-permission android:name="android.permission.INTERNET"/>
RC\u REQUEST\u 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)
此调用将导致弹出授权屏幕(如有必要),允许用户选择一个帐户,然后再次关闭,并将数据传递到ActivityResult
要获取以前登录的用户(无需启动新活动),还可以使用googlesign.getLastSignedAccount(this)代码>方法在后台
失败时,这两种方法中的任何一种都返回null
,因此请做好处理这一问题的准备
现在我们有了一个经过身份验证的用户,我们该如何处理它
我们需要一个身份验证令牌。
现在,我们的account对象中只有一个idToken,这对于我们想要做的事情是绝对无用的,因为它不允许我们调用API
但谷歌再次出手相救,为我们提供了GoogleAuthUtil.getToken(这个,account.account,“oauth2:https://www.googleapis.com/auth/drive.appdata)
呼叫
如果一切顺利,这个调用将转发帐户信息并返回一个字符串:我们需要的身份验证令牌
需要注意的是:这个方法执行一个网络请求,这意味着如果您试图在UI线程中执行它,它将在您面前抛出
我创建了一个helper类,它模仿Google'Task'对象的行为(和API),负责在线程上调用方法并通知调用线程它已经完成的细节
将auth令牌保存在可以再次找到它的地方,授权(最终)完成
查询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
}
spaces
参数用于告诉Google,我们不希望看到根文件夹,而是应用程序数据文件夹。如果没有此参数,请求将失败,因为我们只请求访问appDataFolder
响应应该在files
键下包含一个JSONArray
,然后您可以解析并绘制所需的任何信息
线程任务类
performNet("https://www.googleapis.com/drive/v3/files?spaces=appDataFolder", "GET")
这个helper类封装了在不同上下文上执行操作以及在完成时在实例化线程上执行回调所需的步骤
我并不是说这是实现这一目标的方法,只是我的“根本不知道更好的方法”
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
}
}
导入android.os.Handler
导入android.os.Looper
导入android.os.Message
导入java.lang.Exception
导入java.util.concurrent.Executor
类线程任务{
private val onSuccess=mutableListOf Unit>()
private val onFailure=mutableListOf Unit>()
private val onComplete=mutableListOf Unit>()
fun addOnSuccess(handler:(T)->Unit):ThreadedTask{onSuccess.add(handler);返回此;}
fun addOnFailure(handler:(String)->Unit):ThreadedTask{onFailure.add(handler);返回此;}
fun addOnComplete(handler:()->Unit):ThreadedTask{onComplete.add(handler);返回此;}
/**
*在线程上下文中执行传递的代码,并在调用线程上分别执行Success/Failure/Complete处理程序。
*如果触发任何(未捕获的)异常,则该任务被视为“失败”。
*在添加处理程序时,最后调用此方法以避免竞争条件。
*
*/
乐趣执行(执行者:执行者,代码:()->T)
{
val handler=object:handler(Looper.getMainLooper()){
覆盖有趣的handleMessage(消息:Message){
超级手机短信(msg)
publishResult(msg.what,msg.obj)