Android:如何在同一用户的不同设备之间同步存储在Room数据库中的应用程序数据?

9
我正在开发一个应用程序,将用户数据存储在本地。我已经使用 Room 数据库将用户创建的数据存储在 Android 设备中。如果用户有多个设备,则应该在所有设备上(Android 设备)都可以访问数据,而不需要任何中央数据库或 Web 服务。
注意:对于 iOS 方面,我使用了 CloudKit 来在 iOS 设备之间同步数据。是否有办法在 Android 设备上做到同样的事情?请注意,我不想将数据存储在任何中央数据库或 Web 服务器或任何集中式云存储中。它应该仅归用户所有。(即通过用户的 Google 帐户同步)
更新:到目前为止,我发现了一些选项,如 Firestore实时数据库 和一些 AWS 服务也提供同步功能,但是所有这些选项都会在中央云位置存储用户数据。
因此,我的问题仍然是相同的——是否有一种方法可以使用 Google Drive 或类似的服务在 Android 设备之间同步数据,从而利用用户自己的云存储而不是将用户数据存储在中央数据库中。

如果需要通过Google帐户同步,为什么不在Google Drive中创建备份呢? - Gautam
备份是另一种将数据库(作为文件)放置在驱动器上的方式,每次进行备份时都会将其下载,并在需要恢复时使用。我需要一个设备之间实时同步的解决方案。 - Bharat
2个回答

11

编辑

Android上,没有本地/默认/开箱即用的机制来实时同步数据。

不过有替代方案。

在Android环境下,出现了两种主要选择:

  • 使用带有某些持久存储的套接字 - 您可以实现支持套接字的后端服务器,并将此支持添加到您的android(ios / web / desktop)应用程序中。这是实现设备之间实时同步的最常见和相当低级的机制。优点:跨平台通用、行业标准、在线信息丰富。 缺点:因为它是相对较低级别的东西,需要大量代码,如果有许多同步事件,则可能相当复杂和复杂,需要后端,需要单独的持久性。主要实现:Socket.io

  • Firebase 实时数据库/Cloud Firestore - 基本上是套接字的包装器,结合一些数据库。“优点”:既不需要后端也不需要数据库,在原型/低负载应用程序中免费,非常直观的实现,跨平台,运作得很好。 缺点:如果您想走“干净的方式”,需要大量样板文件,您无法控制内部进程,高负载时不太明显的付款计算,您将链接到具有特定API的第三方后端 - 如果您想从中迁移到其他地方而不需要完全重做应用程序,则需要定义良好的架构。 比较:firebase云Firestore与实时数据库

我不会详细介绍实现细节,因为官方文档在这种情况下相当简单(不像谷歌驱动器)

Cloud Firestore 文档

Realtime Database 文档

编辑结束

是的。您可以使用Google Drive私下存储数据。它只能由您的应用程序访问。此外,它不会占用用户Google Drive中的任何空间,这非常方便。在这种情况下,跨平台是可能的,但需要分别为iOs和Android实现(SDK不支持开箱即用)。

因此,解决方案基于Google Drive API,更具体地说是其存储应用程序特定数据部分。

用户唯一可见的操作就是谷歌登录提示。 这里有一个详细的Java快速入门指南。
因此,为了完成此操作,您需要:
  • 添加依赖项;

  • 按照这里这里所述实现谷歌认证

  • 非常重要 使用drive.appdatadrive.file范围对用户进行身份验证。此外,Google Cloud控制台中启用Google Drive API。所有这些都在这里中描述。

  • 找出您的Room SQLite文件所在位置,如下所示:String currentDBPath = context.getDatabasePath("your_database_name.db").getAbsolutePath();

  • 这里所描述的那样将文件上传到Google Drive应用程序文件夹中

基本上看起来像这样:
  • 控制台中启用Google Drive API

  • 添加依赖项(可能需要更新到相关版本)

implementation 'com.google.android.gms:play-services-auth:17.0.0'// for google sign in

// for drive integration
implementation 'com.google.android.gms:play-services-auth:16.0.1'
implementation 'com.google.http-client:google-http-client-gson:1.26.0'
implementation('com.google.api-client:google-api-client-android:1.26.0') {
exclude group: 'org.apache.httpcomponents'
}
implementation('com.google.apis:google-api-services-drive:v3-rev136-1.25.0') 
{
exclude group: 'org.apache.httpcomponents'
} 
  • android gradle标签中
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/license.txt'
exclude 'META-INF/NOTICE'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/notice.txt'
exclude 'META-INF/ASL2.0'
}
  • 清单文件
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  • 使用所需的范围进行Google身份验证
       val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestScopes(
                 Scope(Scopes.DRIVE_FILE),
                 Scope(Scopes.DRIVE_APPFOLDER)
            )
            .requestEmail()
            .build()
        googleSingInClient = GoogleSignIn.getClient(this, gso)
        val signInIntent = googleSingInClient.signInIntent
        startActivityForResult(signInIntent, RC_SIGN_IN)
        ... // handle sign in result
  • 设置 Google Drive 客户端服务(上下文位置)
        val mAccount = GoogleSignIn.getLastSignedInAccount(this)

        val credential =
            GoogleAccountCredential.usingOAuth2(
                applicationContext, Collections.singleton(Scopes.DRIVE_FILE)
            )
        credential.setSelectedAccount(mAccount.getAccount())
        val googleDriveService =
            Drive.Builder(
                AndroidHttp.newCompatibleTransport(),
                GsonFactory(),
                credential
            )
                .setApplicationName("Your app name")
                .build()
  • 创建并发送文件
        val fileMetadata = File()
        fileMetadata.setName("your_database_name.db")
        fileMetadata.setParents(Collections.singletonList("appDataFolder"))
        val databasePath = context.getDatabasePath("your_database_name.db").absolutePath
        val filePath = File(databasePath)
        val mediaContent = FileContent("application/sqlite", filePath)
        val file = driveService.files().create(fileMetadata, mediaContent)
            .setFields("id")
            .execute()

在其他设备中搜索文件。
        val files = driveService.files().list()
            .setSpaces("appDataFolder")
            .setFields("nextPageToken, files(id, name)")
            .setPageSize(10)
            .execute()
        for (file in files.getFiles()) {
            // get file here 
        }

您可以在这里获取更多相关信息。

希望能对您有所帮助。


1
感谢Paul提供这么详细的答案。看起来这是一个备份和恢复模型,我们触发文件上传,同样地,我们下载相同的文件。我们需要手动执行这些任务(通过在我们的应用程序中触发一些事件)。但是,我需要设备之间的实时数据同步,即当一个设备上发生更新(任何CRUD操作)时,它应该在其他设备上实时可用。 - Bharat
1
这意味着如果您想使用无服务器架构,只能选择Firebase。Cloud Firestore或Real-time Database。而且,如果没有后端或云上的集中式存储,您所需的功能是不可能实现的。我会在答案编辑中添加更多信息。 - Pavlo Ostasha
1
谢谢!我的观察也是一样的。我原本以为如果iOS通过iCloud账户提供了同步机制,那么安卓也应该有相应的功能。 - Bharat
在我的答案中添加文档链接和一些关于实时同步的解释。请参考文档以了解更多信息。 - Pavlo Ostasha
感谢你的努力帮我解决问题。实际上,我已经尝试使用 Firebase 的 FireStore 和 Realtime Database。所有这些解决方案的问题都在于它们会将用户数据存储在某个中央云存储中,并因此使用户失去了对其数据的控制,而这正是我(或者说我的客户)不想要的。数据应该是私有的,只属于用户自身。在 iOS 中,CoreData + iCloud 同步也是这个原理。 - Bharat
显示剩余3条评论

0

你可以使用Firebase的Cloud FireStore来实现这个功能。https://firebase.google.com/docs/firestore

这是可扩展的,Google Drive API已经被弃用。我个人在一个应用程序中使用了FireStore,并且效果非常好。 Bharat


2
感谢Pramod为帮助我做出的努力。实际上,我已经尝试过Firebase的FireStore和实时数据库。所有这些解决方案的问题都是它们通过将用户数据存储在某个中央云存储中,从用户控制中取出了用户数据,而这正是我(我的客户)不想要的。 数据应该对用户保密。在iOS中,CoreData + iCloud同步也是如此。 - Bharat
那显然是我们无法帮助的。如果您不想这样做,您可能需要实现一个自定义SDK来为应用程序执行此操作。在这种情况下,任何SDK都可以做到这一点。 - pramod_m

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接