基于GoogleSignIn+DriveClient实现后台同步任务的方法
实现Google Drive后台无缝同步(含令牌自动刷新)
我刚好在项目里落地过类似需求,结合Google最新的GoogleSignIn和DriveClient(弃用GoogleApiClient后的官方新方案),完全能做到用户退出应用后后台同步自动运行,还能自动处理令牌过期问题。核心是靠离线访问权限拿到刷新令牌,再配合WorkManager这类后台任务框架执行同步,下面是详细的实现步骤和可复用的代码示例:
核心思路拆解
- 获取离线访问权限:登录时明确请求
offline类型权限,这样即使用户退出应用,我们也能通过刷新令牌静默获取新的访问令牌,全程无需用户交互。 - 安全存储凭据:把登录后的
GoogleSignInAccount信息(或关键标识)加密存储,避免敏感信息泄露。 - 后台任务调度:用WorkManager创建周期性/触发式同步任务,让系统在合适时机自动执行Drive操作。
- 令牌过期自动处理:在后台任务中捕获令牌失效异常,通过刷新令牌重新获取权限,自动重试同步流程。
步骤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 ) }
关键注意事项
- 权限最小化:优先使用
DriveScopes.DRIVE_FILE而非全Drive权限,降低用户授权门槛,也更安全。 - 刷新令牌有效期:Google刷新令牌若6个月未使用会失效,此时需通过通知引导用户重新登录。
- 后台任务适配:WorkManager会自动适配Android 8.0+的后台限制,确保任务能被系统调度执行。
- 凭据安全:必须用加密存储保存账户信息,避免敏感数据被窃取。
内容的提问来源于stack exchange,提问作者Cheok Yan Cheng
相关产品推荐
相关产品推荐

