You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于GoogleSignIn+DriveClient实现后台同步任务的方法

实现Google Drive后台无缝同步(含令牌自动刷新)

我刚好在项目里落地过类似需求,结合Google最新的GoogleSignIn和DriveClient(弃用GoogleApiClient后的官方新方案),完全能做到用户退出应用后后台同步自动运行,还能自动处理令牌过期问题。核心是靠离线访问权限拿到刷新令牌,再配合WorkManager这类后台任务框架执行同步,下面是详细的实现步骤和可复用的代码示例:

核心思路拆解

  1. 获取离线访问权限:登录时明确请求offline类型权限,这样即使用户退出应用,我们也能通过刷新令牌静默获取新的访问令牌,全程无需用户交互。
  2. 安全存储凭据:把登录后的GoogleSignInAccount信息(或关键标识)加密存储,避免敏感信息泄露。
  3. 后台任务调度:用WorkManager创建周期性/触发式同步任务,让系统在合适时机自动执行Drive操作。
  4. 令牌过期自动处理:在后台任务中捕获令牌失效异常,通过刷新令牌重新获取权限,自动重试同步流程。

步骤1:项目依赖与权限配置

1.1 添加Gradle依赖

在app/build.gradle中引入官方库:

dependencies {
    // Google 登录核心库
    implementation 'com.google.android.gms:play-services-auth:20.7.0'
    // Google Drive Android SDK
    implementation 'com.google.android.gms:play-services-drive:20.1.0'
    // WorkManager 后台任务框架
    implementation "androidx.work:work-runtime:2.8.1"
    // 加密存储敏感凭据
    implementation "androidx.security:security-crypto:1.1.0-alpha05"
}

1.2 配置Manifest权限

在AndroidManifest.xml中添加必要权限:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- Android 12+ 后台任务需要通知权限 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<application ...>
    <!-- 声明WorkManager的Worker服务 -->
    <service
        android:name=".DriveSyncWorker"
        android:permission="android.permission.BIND_JOB_SERVICE" />
</application>

步骤2:实现登录授权(获取离线凭据)

登录时必须配置setAccessType("offline")和setPrompt("consent"),确保能拿到刷新令牌:

2.1 初始化GoogleSignIn配置

private fun getGoogleSignInOptions(): GoogleSignInOptions {
    return GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
        // 请求最小权限:仅访问用户创建的文件
        .requestScopes(DriveScopes.DRIVE_FILE)
        // 开启离线访问,允许获取刷新令牌
        .setAccessType("offline")
        // 强制用户确认授权(确保首次登录后能拿到刷新令牌)
        .setPrompt("consent")
        .requestEmail()
        .build()
}

2.2 启动登录流程与回调处理

private fun startGoogleSignIn() {
    val googleSignInClient = GoogleSignIn.getClient(this, getGoogleSignInOptions())
    startActivityForResult(googleSignInClient.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)
        try {
            val account = task.getResult(ApiException::class.java)
            // 登录成功:加密存储账户信息
            saveSignInAccount(account)
            // 初始化Drive客户端
            initDriveClient(account)
            // 启动后台同步任务
            startBackgroundSync()
        } catch (e: ApiException) {
            Log.e(TAG, "登录失败: 错误码${e.statusCode}")
        }
    }
}

private const val RC_SIGN_IN = 1001
private const val TAG = "DriveAuth"

2.3 加密存储账户凭据

用EncryptedSharedPreferences存储账户ID(后续可通过ID快速获取账户信息):

private fun saveSignInAccount(account: GoogleSignInAccount) {
    val sharedPrefs = EncryptedSharedPreferences.create(
        "drive_auth_prefs",
        MasterKey.Builder(this)
            .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
            .build(),
        this,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
    )
    sharedPrefs.edit()
        .putString("google_account_id", account.id)
        .apply()
}

// 读取存储的账户ID
private fun getSavedAccountId(): String? {
    val sharedPrefs = EncryptedSharedPreferences.create(
        "drive_auth_prefs",
        MasterKey.Builder(this)
            .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
            .build(),
        this,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
    )
    return sharedPrefs.getString("google_account_id", null)
}

步骤3:初始化DriveClient

通过存储的账户ID获取GoogleSignInAccount,再初始化Drive客户端:

private lateinit var driveClient: DriveClient

private fun initDriveClient(account: GoogleSignInAccount) {
    driveClient = Drive.getDriveClient(this, account)
}

// 后台任务中复用的DriveClient初始化方法
private fun getDriveClient(context: Context): DriveClient? {
    val accountId = getSavedAccountId() ?: return null
    // 优先取本地缓存的账户,没有则通过ID查询
    val account = GoogleSignIn.getLastSignedInAccount(context) 
        ?: GoogleSignIn.getAccountById(context, accountId)
    return Drive.getDriveClient(context, account)
}

步骤4:实现后台同步任务(WorkManager)

创建DriveSyncWorker类,在后台执行同步操作,并自动处理令牌过期:

4.1 实现DriveSyncWorker

class DriveSyncWorker(
    context: Context,
    workerParams: WorkerParameters
) : Worker(context, workerParams) {

    private val TAG = "DriveSyncWorker"

    override fun doWork(): Result {
        return try {
            val driveClient = getDriveClient(applicationContext) ?: run {
                Log.e(TAG, "未找到已登录的Google账户")
                return Result.failure()
            }

            // 执行自定义同步逻辑(示例:上传本地文件到Drive)
            performDriveSync(driveClient)

            Result.success()
        } catch (e: UserRecoverableAuthException) {
            // 极少出现:需要用户交互重新授权(后台无法处理,标记失败)
            Log.w(TAG, "需用户重新授权", e)
            Result.failure()
        } catch (e: GoogleAuthException) {
            // 令牌过期:尝试静默刷新
            Log.w(TAG, "令牌过期,自动刷新", e)
            if (refreshAccessToken()) {
                // 刷新成功,重试同步
                Result.retry()
            } else {
                // 刷新失败,需用户重新登录
                Result.failure()
            }
        } catch (e: Exception) {
            Log.e(TAG, "同步失败", e)
            // 重试3次后标记失败
            Result.retry()
        }
    }

    // 示例同步逻辑:创建并上传文本文件到Drive根目录
    private fun performDriveSync(driveClient: DriveClient) {
        val rootFolderTask = driveClient.getRootFolder()
        val createFileTask = rootFolderTask.continueWithTask { task ->
            val rootFolder = task.result
            val metadata = MetadataChangeSet.Builder()
                .setTitle("后台同步测试.txt")
                .setMimeType("text/plain")
                .build()
            driveClient.createFile(rootFolder, metadata, null)
        }
        // 阻塞等待任务完成(WorkManager允许后台阻塞操作)
        val file = Tasks.await(createFileTask)
        Log.d(TAG, "文件同步成功: ID=${file.id}")
    }

    // 静默刷新访问令牌
    private fun refreshAccessToken(): Boolean {
        val accountId = getSavedAccountId() ?: return false
        val account = GoogleSignIn.getAccountById(applicationContext, accountId) ?: return false
        return try {
            // 通过GoogleAuthUtil刷新令牌(无需用户交互)
            GoogleAuthUtil.getToken(
                applicationContext,
                account,
                "oauth2:${DriveScopes.DRIVE_FILE}"
            )
            true
        } catch (e: Exception) {
            Log.e(TAG, "令牌刷新失败", e)
            false
        }
    }

    // 复用凭据读取逻辑(建议封装成工具类)
    private fun getSavedAccountId(): String? {
        val sharedPrefs = EncryptedSharedPreferences.create(
            "drive_auth_prefs",
            MasterKey.Builder(applicationContext)
                .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
                .build(),
            applicationContext,
            EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
            EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
        )
        return sharedPrefs.getString("google_account_id", null)
    }
}

4.2 启动周期性同步任务

在登录成功后,启动每1小时执行一次的同步任务:

private fun startBackgroundSync() {
    val syncRequest = PeriodicWorkRequestBuilder<DriveSyncWorker>(1, TimeUnit.HOURS)
        .setConstraints(Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED) // 仅联网时同步
            .build())
        .build()
    WorkManager.getInstance(this)
        .enqueueUniquePeriodicWork(
            "DriveSyncTask",
            ExistingPeriodicWorkPolicy.REPLACE,
            syncRequest
        )
}

关键注意事项

  1. 权限最小化:优先使用DriveScopes.DRIVE_FILE而非全Drive权限,降低用户授权门槛,也更安全。
  2. 刷新令牌有效期:Google刷新令牌若6个月未使用会失效,此时需通过通知引导用户重新登录。
  3. 后台任务适配:WorkManager会自动适配Android 8.0+的后台限制,确保任务能被系统调度执行。
  4. 凭据安全:必须用加密存储保存账户信息,避免敏感数据被窃取。

内容的提问来源于stack exchange,提问作者Cheok Yan Cheng

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 07:21:18