下载外部 .db 文件以在 Kotlin Android 应用程序中以只读方式使用?

Download external .db file for read-only use in Kotlin Android application?

我已经有一个 SQLite 数据库 (.db) 文件可以在特定的 URL(比方说 https://mywebsite.com/mydb.db)下载。我想下载这个文件并在我的 Kotlin Android 应用程序中使用它。我可以将文件下载到我的设备模拟器中的 "Downloads" 文件夹,但我有几个关于从这里去哪里的问题。

  1. 我是否需要将此文件复制到内部存储,或者我是否可以直接从下载文件夹中的文件创建一个数据库?我希望数据库数据保留在本地,所以我不想将数据库文件留在可能被删除的地方(下载文件夹)。

    • 基本上,我希望该应用能够从持久的内部 .db 文件中读取
  2. 有时,我需要向 .db 文件中添加更多数据。将来,当我重新下载 .db 文件并从中创建数据库时,这是否会覆盖应用程序内部存储中的现有数据库?我需要手动删除数据库的旧迭代吗?

  3. 我想在应用程序第一次运行时自动下载 .db 文件,并且每次我将 .db 文件的新版本上传到 https://mywebsite.com/mydb.db.

总的来说,我对 Android 开发还很陌生,所以我不确定最佳做法是什么。单击按钮,我提供的代码成功地将我的 .db 文件下载到 "Downloads" 文件夹。在从 .db 文件创建数据库以及在我将新的 .db 文件上传到我的主机 URL 时自动替换数据库方面,我不确定从哪里开始。

private const val PERMISSION_CODE = 1000

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        downloadButton.setOnClickListener {
            // Checks if version of Android is >= Marshmallow
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED){
                    // request permission if denied
                    requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), PERMISSION_CODE)
                }
                else {
                    startDownload()
                }
            }
            else {
                // OS is out of date, no permissions are needed
                startDownload()
            }
        }

private fun startDownload() {
        val request = DownloadManager.Request(Uri.parse(getString(R.string.db_URL)))
        request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE)
        request.setTitle("my_database.db")
        request.setDescription("My database is downloading.")
        request.allowScanningByMediaScanner()
        request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
        request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "mydb_" + "${System.currentTimeMillis()}")

        val manager =  getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
        manager.enqueue(request)
}

Do I need to copy this file to internal storage, or can I create a database directly from the file in the Downloads folder?

该文件是数据库,因此可以从下载文件夹中打开。

I want the database data to persist locally, so I don't want to leave the database file in a place where it may get deleted (the Downloads folder).

您可以将下载文件夹中的文件复制到App的默认位置,根据ContextgetDatabasePath(the_database_name)方法获取位置该文件将被复制到。您将(至少对于第一个 运行)检查数据库文件是否存在于它的最终位置,然后启动副本,之后可以打开和使用数据库。

或者,您可以直接将其下载到最终目的地,而无需中间副本。

Sometimes, I'll need to add more data to the .db file. In the future, when I re-download the .db file and create a database from it, will this overwrite the existing database in the app's internal storage? Do I need to manually delete old iterations of the database?

有多种方法,但基本上如果引入新文件,那么您可能会在旧位置覆盖现有数据库文件(存储在应用程序的数据中)(尽管这不是唯一的选择)。虽然,如果引入新数据而不是新模式(后者可能需要新代码,因此您可以利用数据库版本强制重新复制)。

问题是如何知道是否引入了新文件,因为数据库文件名至少在最终目的地可能是相同的。

  1. 检查文件大小可能并不表示发生了变化,因此这不是一个故障安全选项。

    • 即数据库文件保存为页面块,其中一些页面可能包含更新数据库时可能使用的免费 space,因此数据库文件大小保持不变。
  2. 您可以查看文件的最后修改日期。

  3. 您可以使用不同的文件名,例如mydbv1.db 然后 mydbv2.db 等等
  4. 您可以使用 user_version,如果使用 SQLiteOpenHelper 的子类(最常见的情况),则需要小心,因为 SQLiteOpenHelper 使用它(数据库版本作为第 4 个参数传递)。
  5. 您可以使用 application_id(这类似于 user_version 但 SQLiteOpenHelper 不使用)。

1-3 需要一种持久记录当前使用值的方法,可能在数据库本身的 table 中,可能是同一目录中的空文件。

4 和 5 实际上存储在数据库中 header 您通过 pragma 访问这些值或以编程方式提取它们(后者不需要将文件作为数据库打开,并且通常占用资源较少因为您只需要读取前 100 个字节即可获得完整的 header).

一旦您检测到新版本,只需 re-doing 下载和复制即可。

以上假设该应用程序有多个用户,因此主机服务器(您的网站)上始终必须存在下载文件,并且您已经将应用程序与数据库一起运送打折(这可以简化问题,因为什么时候有新版本可用你引入新版本的应用程序)。

以上还假定数据库仅由最终用户读取。如果不是,并且用户可以更新数据,则需要一种策略来满足保留特定用户数据的需求。