Как получить доступ к данным приложения на Google Drive на Android с помощью REST API - PullRequest
0 голосов
/ 05 января 2019

Google устанавливает свой API Android для доступа к службам Google (например, Google Drive) и заменяет его на REST.

И хотя есть «руководства по миграции», он не может создать пакет APK, готовый к установке, из-за «Дублирующегося определения класса» или чего-то подобного.

По какой-то причине невероятно трудно найти исчерпывающую информацию о том, как получить доступ к Сервису Google с помощью REST через Android (предпочтительно с использованием методов, изначально доступных для ОС).

1 Ответ

0 голосов
/ 05 января 2019

После долгих поисков, недоразумений, царапин на голове, случайных ругательств и большого количества знаний о вещах, которые мне действительно не нужны, я хотел бы поделиться несколькими фрагментами кода, которые на самом деле работают для меня.

Отказ от ответственности: я новичок-программист на 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 Framework.

Создайте мероприятие (или используйте ваше основное занятие по вашему выбору) и переопределите там метод 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 - произвольно выбранное значение идентификатора, определенное в сопутствующем объекте как константа.

Как только вы захотите выполнить аутентификацию (т. Е. Нажатием кнопки), вам потребуется запустить действие, для которого мы только что объявили обратный вызов.

Для этого вам нужно сначала подготовить запрос аутентификации.

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").

Этот вызов перенаправит информацию об учетной записи и вернет строку, если все пойдет хорошо: токен аутентификации нам нужен.

Следует отметить: этот метод выполняет сетевой запрос, что означает, что он бросит вам в лицо, если вы попытаетесь выполнить его в потоке пользовательского интерфейса.

Я создал вспомогательный класс, который имитирует поведение (и API) объекта Googles 'Task', который заботится о тщательности вызова метода в потоке и уведомляет вызывающий поток о том, что это сделано.

Сохраните токен аутентификации где-нибудь, где вы сможете найти его снова, авторизация (наконец-то) завершена.

Запрос API

Эта часть намного проще, чем предыдущая, и идет рука об руку с API-интерфейсом Google Drive REST

Все сетевые запросы должны выполняться в потоке «не-пользовательского интерфейса», поэтому я поместил их в свой класс помощника, чтобы уведомить меня, когда появятся данные для отображения.

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-адрес для запроса, метод выполнения (GET, POST, PATCH, DELETE) и создает из него HTTP-запрос.

Токен авторизации, который мы получили ранее во время авторизации, передается в качестве заголовка запроса на аутентификацию и идентифицирует себя как «пользователя» в Google.

Google, если все в порядке, будет вызывать ответ с HTTP_OK (200) и onSuccess, который переведет ответ JSON в JSONObject, который затем будет передан функции оценки, которую мы зарегистрировали ранее.

Получение списка файлов

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

Параметр 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
    }
}

В этом случае важен порядок исполнения. Сначала вам нужно добавить обратные вызовы к объекту класса, и в конце вам нужно вызвать execute и предоставить его исполнителю, с которым вы хотите запустить поток, и, конечно, коду, который вы хотите выполнить.

Это не все, что вы можете сделать с Google Drive, но это начало, и я надеюсь, что этот небольшой сборник спасет кого-то еще в будущем.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...