Android 端實現(xiàn)谷歌備份與恢復

Google 云端控制臺

  1. 訪問 Google Cloud Console寒瓦。

  2. 如果還沒有項目爪飘,請創(chuàng)建一個懒熙。如果已有項目碍讯,請選擇該項目。

  3. 在導航菜單中選擇 APIs & Services > Dashboard载迄。

  4. 點擊 ENABLE APIS AND SERVICES 按鈕。搜索 "Google Drive API"奠涌,然后點擊 Google Drive API 進入詳情頁面宪巨。點擊 Enable 按鈕啟用此 API。

    1732003634450.jpg

  5. 返回到導航欄溜畅,選擇 APIs & Services > Credentials捏卓。

  6. 點擊 CREATE CREDENTIALS 按鈕,并從下拉列表中選擇 OAuth client ID慈格。

  7. 在 "Application type" 選項中怠晴,選擇 "Android":

    • 輸入 Name
    • Signing-certificate fingerprint 中輸入您的應用程序簽名證書的 SHA-1 指紋浴捆。如果您不知道如何獲取 SHA-1 指紋蒜田,請參考這篇文章
    • Package name 中輸入您的 Android 應用程序的包名选泻。包名必須與您實際應用的包名一致冲粤,否則授權(quán)將無法成功。
  8. 點擊 Create∫趁校現(xiàn)在梯捕,您將看到您的 Client ID。請務必安全地保存這些信息窝撵,稍后將在您的應用程序中使用它們傀顾。

Android端依賴

    implementation 'com.google.android.gms:play-services-auth:19.2.0'
    implementation 'com.google.apis:google-api-services-drive:v3-rev197-1.25.0'
    implementation 'com.google.api-client:google-api-client-android:1.23.0'

Android端代碼

1.初始化服務,

    private lateinit var googleSignInClient: GoogleSignInClient
    private var isRecover: Boolean=false
    private val RC_SIGN_IN = 9001
    private fun initDrive() {
        val signInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestEmail()
            .requestScopes(Scope(DriveScopes.DRIVE_FILE))
            .build()
        googleSignInClient = GoogleSignIn.getClient(this, signInOptions)
    }

切記 使用 requestScopes(Scope(DriveScopes.DRIVE_FILE)) 而不是 .requestIdToken(ApiAction.SERVER_CLIENT_ID)
.requestIdToken(ApiAction.SERVER_CLIENT_ID)是登錄注冊使用的

2.具體業(yè)務邏輯

點擊事件

  views.backupGoogle.debouncedClicks {
            if (isGooglePlayServicesAvailable(this)) {
                isRecover = false
                getDriveAuth()
            } else {
                T.showShort(this, "設備不支持谷歌登錄")
            }
        }
        views.recoverGoogle.debouncedClicks {
            if (isGooglePlayServicesAvailable(this)) {
                isRecover = true
                getDriveAuth()
            } else {
                T.showShort(this, "設備不支持谷歌登錄")
            }
        }

詳細方法

  fun isGooglePlayServicesAvailable(context: Context): Boolean {
        val googleApiAvailability = GoogleApiAvailability.getInstance()
        val resultCode = googleApiAvailability.isGooglePlayServicesAvailable(context)
        return resultCode == ConnectionResult.SUCCESS
    }

    private fun getDriveAuth() {
        val signInIntent = googleSignInClient.signInIntent
        startActivityForResult(signInIntent, RC_SIGN_IN)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == RC_SIGN_IN) {
            val task = GoogleSignIn.getSignedInAccountFromIntent(data)
            handleSignInResult(task)
        }
    }

    private fun handleSignInResult(completedTask: Task<GoogleSignInAccount>) {
        try {
            val account = completedTask.getResult(ApiException::class.java)
            account?.let {
                // Get a Drive service instance using the signed-in account.
                getDriveService(this, it)?.let {drive->
                    lifecycleScope.launch(Dispatchers.IO) {
                        if(isRecover){
                            BackupUtil.restoreData(drive, "backup.txt").let { content->
                                L.v("===content==${content}")
                            }
                        }else{
                            BackupUtil.backupData(drive, privateHex, "backup.txt")
                        }
                    }
                }
            }
        } catch (e: ApiException) {
            L.v("=====signInResult:failed code=" + e.statusCode)
        }
    }

BackupUtil代碼碌奉,里面有覆蓋上傳以及更新上傳短曾,自我斟酌

import android.content.Context
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.api.client.extensions.android.http.AndroidHttp
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential
import com.google.api.client.http.InputStreamContent
import com.google.api.client.json.jackson2.JacksonFactory
import com.google.api.services.drive.Drive
import com.google.api.services.drive.DriveScopes
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.nio.charset.StandardCharsets

/**
 * Author : lisir
 * Time: 2024/11/18 14:17
 * Describe :
 */
object BackupUtil {
    /**
     *實例化Google Drive
     */
    fun getDriveService(context: Context, account: GoogleSignInAccount): Drive? {
        return try {
            val credential = GoogleAccountCredential.usingOAuth2(
                context, listOf(DriveScopes.DRIVE_FILE)
            )
            credential.selectedAccount = account.account
            Drive.Builder(
                AndroidHttp.newCompatibleTransport(),
                JacksonFactory.getDefaultInstance(),
                credential
            ).setApplicationName(context.getString(R.string.app_name))
                .build()
        } catch (e: Exception) {
            L.v("====" + e.message)
            null
        }
    }


    /**
     * @param drive :Google Drive
     * @param data :寫入數(shù)據(jù) eg:真是美好的一天
     * @param fileName :文件夾名稱 eg: back.txt
     * 這個方法可以實現(xiàn)云盤上只有一份文件
     */
    fun backupData(drive: Drive, data: String, fileName: String):String? {
        try {
            val query = "mimeType='text/plain' and trashed=false and name='$fileName'"
            val existingFiles = drive.files().list()
                .setQ(query)
                .setSpaces("drive")
                .setFields("nextPageToken, files(id, name)")
                .setPageSize(1)
                .execute()

            val metadata = com.google.api.services.drive.model.File()
                .setName(fileName)
                .setMimeType("text/plain")

            val contentStream = ByteArrayInputStream(data.toByteArray(StandardCharsets.UTF_8))
            val content = InputStreamContent("text/plain", contentStream)

            if (existingFiles.files.isEmpty()) {
                val newFile = drive.files().create(metadata, content).setFields("id").execute()
                L.v("File ID=====:"+newFile.id)
                return  newFile.id
            } else {
                val fileId = existingFiles.files[0].id
                val updatedFile = drive.files().update(fileId, metadata, content).setFields("id").execute()
                L.v("Updated File ID:===="+updatedFile.id)
                return  updatedFile.id
            }
        }catch (e: Exception) {
            L.v("====" + e.message)
            return  null
        }

    }


    /**
     * 當云盤只有一個名字唯一的文件時候,
     * @param drive :Google Drive
     * @param fileName :文件夾名稱 eg: back.txt
     */
    fun restoreData(drive: Drive, fileName: String): String? {
        try {
            val query = "name='$fileName'"
            val fileList = drive.files().list()
                .setQ(query)
                .execute()
            if (fileList.files.isEmpty()) {
                return null
            }
            val backupFile = fileList.files.first()
            val outputStream = ByteArrayOutputStream()
            drive.files().get(backupFile.id).executeMediaAndDownloadTo(outputStream)
            return String(outputStream.toByteArray(), StandardCharsets.UTF_8)
        } catch (e: Exception) {
            L.v("====" + e.message)
            return  null
        }
    }

    
    /**
     * 當云盤有多個名字相同的文件時候赐劣,
     * @param drive :Google Drive
     * @param fileName :文件夾名稱 eg: back.txt
     */
    fun restoreDataMore(drive: Drive, fileName: String): String? {
        val query = "mimeType='text/plain' and trashed=false and name='$fileName'"
        val existingFiles = drive.files().list()
            .setQ(query)
            .setSpaces("drive")
            .setFields("nextPageToken, files(id, name)")
            .setPageSize(1)
            .execute()
        if (existingFiles.files.isEmpty()) {
            L.v("File not found:=="+fileName)
            return null
        }
        // Download the file's content
        val fileId = existingFiles.files[0].id
        L.v("fileId:=="+fileId)
        ByteArrayOutputStream().use { outputStream ->
            drive.files().get(fileId)
                .executeMediaAndDownloadTo(outputStream)

            // Convert the downloaded content to a string
            return outputStream.toString(StandardCharsets.UTF_8.name())
        }
    }

    /**
     * @param drive :Google Drive
     * @param data :寫入數(shù)據(jù) eg:真是美好的一天
     * @param fileName :文件夾名稱 eg: back.txt
     * 這個方法可以實現(xiàn)云盤上有多份相同文件
     */
    fun backupDataMore(drive: Drive, data: String, fileName: String) {
        val metadata = com.google.api.services.drive.model.File()
            .setName(fileName)
            .setMimeType("text/plain")

        val contentStream = ByteArrayInputStream(data.toByteArray(StandardCharsets.UTF_8))
        val content = InputStreamContent("text/plain", contentStream)
        val file = drive.files().create(metadata, content).setFields("id").execute()
        L.v("File ID: " + file.id)
    }
    
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嫉拐,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子魁兼,更是在濱河造成了極大的恐慌椭岩,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異判哥,居然都是意外死亡献雅,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門塌计,熙熙樓的掌柜王于貴愁眉苦臉地迎上來挺身,“玉大人,你說我怎么就攤上這事锌仅≌录兀” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵热芹,是天一觀的道長贱傀。 經(jīng)常有香客問我,道長伊脓,這世上最難降的妖魔是什么府寒? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮报腔,結(jié)果婚禮上株搔,老公的妹妹穿的比我還像新娘。我一直安慰自己纯蛾,他們只是感情好纤房,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著翻诉,像睡著了一般炮姨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上碰煌,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天舒岸,我揣著相機與錄音,去河邊找鬼拄查。 笑死吁津,一個胖子當著我的面吹牛棚蓄,可吹牛的內(nèi)容都是我干的堕扶。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼梭依,長吁一口氣:“原來是場噩夢啊……” “哼稍算!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起役拴,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤糊探,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體科平,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡褥紫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了瞪慧。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片髓考。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖弃酌,靈堂內(nèi)的尸體忽然破棺而出氨菇,到底是詐尸還是另有隱情,我是刑警寧澤妓湘,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布查蓉,位于F島的核電站,受9級特大地震影響榜贴,放射性物質(zhì)發(fā)生泄漏豌研。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一竣灌、第九天 我趴在偏房一處隱蔽的房頂上張望聂沙。 院中可真熱鬧,春花似錦初嘹、人聲如沸及汉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坷随。三九已至,卻和暖如春驻龟,著一層夾襖步出監(jiān)牢的瞬間温眉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工翁狐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留类溢,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓露懒,卻偏偏與公主長得像闯冷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子懈词,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容