kotlin 如何将Room数据库导出为.db文件到下载文件,以便稍后使用?

wwwo4jvm  于 2023-08-07  发布在  Kotlin
关注(0)|答案(3)|浏览(302)

如何将Room数据库导出为.db文件以便以后使用?我想将其导出到Android设备存储中的下载文件夹。我已经设置导出到.CSV,但现在我需要. db。
这是这样我就可以重新导入它,如果用户正在切换设备,并需要他的数据在其他设备上。
有没有一种方法可以只使用.CSV数据,或者我必须使用.db?

jgovgodb

jgovgodb1#

备份数据库文件要简单得多,而且可能比尝试操作CSV数据更快。
还有一个优点是,只需复制文件,就可以在SQLite工具中使用数据库。
P.S.扩展(例如. csv.db)只是文件用途的指示。在示例中使用了.whatever

  • 一个问题是,恢复数据库后,访问恢复的数据可能会出现问题。
  • 规避此类问题的一种方法是在恢复后自动重新启动应用程序。
  • 另一个问题是,默认情况下,Room使用Write-Ahead Logging(WAL),并且检查点数据库似乎并不那么容易。
  • 作为替代备份单个文件的方法,可以备份和还原3个文件(wal文件和shm文件)请参阅https://www.sqlite.org/wal.html

这里有一个示例/演示应用程序,它使用一个Room数据库,其中只有一个表,只有2列。当应用程序运行时,有一个具有4个UI组件的单个Activity:

  • 允许备份数据库的按钮。
  • 允许还原数据库的按钮。
  • 允许添加某些数据的按钮(3行)。
  • 当前数据的列表。
    • 注意,为了简单和简洁,所有工作都在主线程上进行 *
    • 同样为了简单和简洁,所有数据库代码都在一个文件中RoomEntities*
    • 此外,为简单起见,备份到同一目录data/data/the_package_name/databases *
      RoomEntities(所有数据库代码,包括备份和恢复代码):-
  1. const val THETABLE_TABLENAME = "theTable"
  2. const val THETABLE_ID_COLUMN = THETABLE_TABLENAME + "_id"
  3. const val TheTABLE_OTHER_COLUMN = THETABLE_TABLENAME + "_other"
  4. const val THEDATABASE_DATABASE_NAME = "thedatabase.whatever"
  5. const val THEDATABASE_DATABASE_BACKUP_SUFFIX = "-bkp"
  6. const val SQLITE_WALFILE_SUFFIX = "-wal"
  7. const val SQLITE_SHMFILE_SUFFIX = "-shm"
  8. @Entity(tableName = THETABLE_TABLENAME)
  9. data class TheTable(
  10. @PrimaryKey @ColumnInfo(name = THETABLE_ID_COLUMN) var id: Long?=null,
  11. @ColumnInfo(name = TheTABLE_OTHER_COLUMN) var other: String
  12. )
  13. @Dao
  14. interface AllDAO {
  15. @Insert(onConflict = OnConflictStrategy.IGNORE)
  16. fun insert(theTable: TheTable): Long
  17. @Query("SELECT * FROM $THETABLE_TABLENAME")
  18. fun getAllFromTheTable(): List<TheTable>
  19. }
  20. @Database(entities = [TheTable::class], exportSchema = false, version = 1)
  21. abstract class TheDatabase: RoomDatabase() {
  22. abstract fun getAllDao(): AllDAO
  23. companion object {
  24. private var instance: TheDatabase? = null
  25. fun getInstance(context: Context): TheDatabase {
  26. if (instance==null) {
  27. instance = Room.databaseBuilder(context,TheDatabase::class.java,
  28. THEDATABASE_DATABASE_NAME)
  29. .allowMainThreadQueries()
  30. .build()
  31. }
  32. return instance as TheDatabase
  33. }
  34. }
  35. /**
  36. * Backup the database
  37. */
  38. fun backupDatabase(context: Context): Int {
  39. var result = -99
  40. if (instance==null) return result
  41. val dbFile = context.getDatabasePath(THEDATABASE_DATABASE_NAME)
  42. val dbWalFile = File(dbFile.path + SQLITE_WALFILE_SUFFIX)
  43. val dbShmFile = File(dbFile.path + SQLITE_SHMFILE_SUFFIX)
  44. val bkpFile = File(dbFile.path + THEDATABASE_DATABASE_BACKUP_SUFFIX)
  45. val bkpWalFile = File(bkpFile.path + SQLITE_WALFILE_SUFFIX)
  46. val bkpShmFile = File(bkpFile.path + SQLITE_SHMFILE_SUFFIX)
  47. if (bkpFile.exists()) bkpFile.delete()
  48. if (bkpWalFile.exists()) bkpWalFile.delete()
  49. if (bkpShmFile.exists()) bkpShmFile.delete()
  50. checkpoint()
  51. try {
  52. dbFile.copyTo(bkpFile,true)
  53. if (dbWalFile.exists()) dbWalFile.copyTo(bkpWalFile,true)
  54. if (dbShmFile.exists()) dbShmFile.copyTo(bkpShmFile, true)
  55. result = 0
  56. } catch (e: IOException) {
  57. e.printStackTrace()
  58. }
  59. return result
  60. }
  61. /**
  62. * Restore the database and then restart the App
  63. */
  64. fun restoreDatabase(context: Context,restart: Boolean = true) {
  65. if(!File(context.getDatabasePath(THEDATABASE_DATABASE_NAME).path + THEDATABASE_DATABASE_BACKUP_SUFFIX).exists()) {
  66. return
  67. }
  68. if (instance == null) return
  69. val dbpath = instance!!.getOpenHelper().readableDatabase.path
  70. val dbFile = File(dbpath)
  71. val dbWalFile = File(dbFile.path + SQLITE_WALFILE_SUFFIX)
  72. val dbShmFile = File(dbFile.path + SQLITE_SHMFILE_SUFFIX)
  73. val bkpFile = File(dbFile.path + THEDATABASE_DATABASE_BACKUP_SUFFIX)
  74. val bkpWalFile = File(bkpFile.path + SQLITE_WALFILE_SUFFIX)
  75. val bkpShmFile = File(bkpFile.path + SQLITE_SHMFILE_SUFFIX)
  76. try {
  77. bkpFile.copyTo(dbFile, true)
  78. if (bkpWalFile.exists()) bkpWalFile.copyTo(dbWalFile, true)
  79. if (bkpShmFile.exists()) bkpShmFile.copyTo(dbShmFile,true)
  80. checkpoint()
  81. } catch (e: IOException) {
  82. e.printStackTrace()
  83. }
  84. if (restart) {
  85. val i = context.packageManager.getLaunchIntentForPackage(context.packageName)
  86. i!!.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
  87. context.startActivity(i)
  88. System.exit(0)
  89. }
  90. }
  91. private fun checkpoint() {
  92. var db = this.getOpenHelper().writableDatabase
  93. db.query("PRAGMA wal_checkpoint(FULL);",null)
  94. db.query("PRAGMA wal_checkpoint(TRUNCATE);",null)
  95. }
  96. }

字符串

  • 请注意,检查点函数似乎并没有实际检查点(关闭数据库,这将检查数据库,导致Room框架/ Package 器周围的问题)

从代码中可以看出,备份和恢复基本上只是复制文件。
活动的布局是:-

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. android:orientation="vertical"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent"
  7. tools:context=".MainActivity">
  8. <TextView
  9. android:layout_width="wrap_content"
  10. android:layout_height="wrap_content"
  11. android:text="Hello World!"
  12. />
  13. <Button
  14. android:id="@+id/backup"
  15. android:layout_width="wrap_content"
  16. android:layout_height="wrap_content"
  17. android:text="BACKUP"
  18. >
  19. </Button>
  20. <Button
  21. android:id="@+id/restore"
  22. android:layout_width="wrap_content"
  23. android:layout_height="wrap_content"
  24. android:text="RESTORE"
  25. >
  26. </Button>
  27. <Button
  28. android:id="@+id/addData"
  29. android:layout_width="wrap_content"
  30. android:layout_height="wrap_content"
  31. android:text="ADD DATA"
  32. >
  33. </Button>
  34. <ListView
  35. android:id="@+id/datalist"
  36. android:layout_width="wrap_content"
  37. android:layout_height="wrap_content"
  38. >
  39. </ListView>
  40. </LinearLayout>


主活动(MainActivity):

  1. class MainActivity : AppCompatActivity() {
  2. lateinit var db: TheDatabase
  3. lateinit var dao: AllDAO
  4. lateinit var backupButton: Button
  5. lateinit var restoreButton: Button
  6. lateinit var addDataButton: Button
  7. lateinit var listView: ListView
  8. var listAdapter: SimpleCursorAdapter? = null
  9. override fun onCreate(savedInstanceState: Bundle?) {
  10. super.onCreate(savedInstanceState)
  11. setContentView(R.layout.activity_main)
  12. backupButton = this.findViewById(R.id.backup)
  13. restoreButton = this.findViewById(R.id.restore)
  14. addDataButton = this.findViewById(R.id.addData)
  15. listView = this.findViewById(R.id.datalist)
  16. db = TheDatabase.getInstance(this)
  17. dao = db.getAllDao()
  18. setButtonListeners()
  19. setOrRefreshListView()
  20. }
  21. fun setButtonListeners() {
  22. addDataButton.setOnClickListener {
  23. dao.insert(TheTable(other = "DATA_${System.currentTimeMillis()}"))
  24. dao.insert(TheTable(other = "DATA_${System.currentTimeMillis()}"))
  25. dao.insert(TheTable(other = "DATA_${System.currentTimeMillis()}"))
  26. setOrRefreshListView()
  27. }
  28. backupButton.setOnClickListener {
  29. db.backupDatabase(this)
  30. }
  31. restoreButton.setOnClickListener {
  32. db.restoreDatabase(this)
  33. }
  34. }
  35. fun setOrRefreshListView() {
  36. var csr = db.getOpenHelper().writableDatabase.query("SELECT $THETABLE_ID_COLUMN AS _id, $TheTABLE_OTHER_COLUMN FROM $THETABLE_TABLENAME")
  37. if (listAdapter==null) {
  38. listAdapter = SimpleCursorAdapter(
  39. this,
  40. android.R.layout.simple_list_item_2,
  41. csr,
  42. arrayOf("_id", TheTABLE_OTHER_COLUMN),
  43. intArrayOf(android.R.id.text1,android.R.id.text2), 0
  44. )
  45. listView.adapter = listAdapter
  46. } else {
  47. listAdapter!!.swapCursor(csr)
  48. }
  49. }
  50. }

结果

当第一次运行时,则显示为:-
x1c 0d1x的数据

  • 理想情况下,恢复按钮不应该出现,因为在这个阶段没有什么可以恢复
  • 单击“恢复”按钮可以恢复,因为恢复功能会检查是否有任何恢复文件,如果没有,则会立即返回

如果单击备份按钮,则文件将被备份(即使没有数据(但文件中实际上会有一些数据,例如文件头和系统表))。
如果在单击“备份”按钮后,单击了“恢复”按钮(即使未添加数据),则实际上恢复了数据库并重新启动了应用程序。
如果单击“添加数据”按钮,将添加并显示3行数据(如果再次单击,将添加另外3行数据,依此类推)。



因为之前单击了“备份”按钮。单击恢复按钮将恢复到没有数据的备份,并重新启动显示没有数据的应用程序。



添加2组数据并单击“备份”按钮



添加另外2组数据
x1c4d 1x的
单击恢复,然后数据将恢复为6行数据


展开查看全部
omqzjyyz

omqzjyyz2#

这是一个CSV解决方案,将备份和恢复所有非系统/房间/Android表但是,它不处理BLOB(这是更复杂的)它可能有空间问题,它可能有问题,如果外键约束包括在内(备份文件是建议的方式,因为它是远远不太可能遇到问题,除了建议的应用程序重新启动,这只是一个不便)。
此外,为了简洁和方便起见,

  • a)在主线程上运行
  • B)将备份放在数据库文件夹中,而不是其他地方

它围绕着4个函数,在这个例子中,在@Database注解类(TheDatabase)中。这些职能是:

getTableColumnnames(参见示例/演示)

  • 这将使用提供的SupportSQLiteDatabase(均从createAutoCSV函数传递)返回提供的表名中的列列表
  • 列表被返回到调用函数。
    createAutoCSV(参见示例/演示)
  • 此函数从逐步构建的StringBuffer生成并返回CSV文件作为字符串。
  • 不确定大小限制(参见MaxCapacity of StringBuilder
  • 直接执行writeText可能更安全
  • 除了实际数据之外,CSV还有一个开始和结束指示符(实际上并不需要,因为它们只是被跳过了)以及每个表的指示符,该指示符位于该表的数据之前(请参阅restoreFromCSV函数)。
  • 如果数据包含逗号,逗号将替换为{COMMA}(值可以很容易地更改),就像许多值是常量一样。

String this返回的CSV示例如下:-

  1. {START}
  2. {TABLE},theTable
  3. 1,DATA1{COMMA}_1659427794369
  4. ....
  5. {TABLE},theOtherTable
  6. 1,OTHERDATA1_1659427794{COMMA},{COMMA}1659427794398{COMMA},
  7. ....
  8. {END}

字符串

  • 用于测试BLOB的注解用于theOtherTable中的第3列(但BLOB未正确处理)
  • 可以看出,{START}{END}表示数据的开始和结束(实际上不需要)。
  • 如图所示,表由{TABLE}表示,后面跟着逗号和表名本身。
    backupAsCSV()(参见示例演示)
  • 这只是写入数据,删除任何以前的版本(该示例仅使用单个备份文件,因此恢复只是到最新的备份)。
    restoreFromCSV(参见示例/演示)
  • 这,在一个单一的事务(所以应该只有一个写入数据库文件本身,尽管它可能有很多页)为每个表遇到(应该是所有非SQLite/房间/Android表):-
  • 删除所有现有行,然后
  • 依次插入每行
  • 请注意,对于从以前的备份文件恢复到已更改的模式(这很可能导致插入时失败(例如NOT NULL冲突)或后续失败)
  • 注意,也没有考虑关系的任何顺序逻辑(确保外键父项在子项之前填充)
  • 简而言之,正如建议的那样,备份数据库文件是一种方法,因为使用CSV文件的所有陷阱都是无关紧要的。唯一的缺点是建议在通过文件恢复后重新启动应用程序。
    工作示例/演示

下面是完整的代码:

  • 在两个ListView(每个表一个)中显示来自2个表的数据(按照上面的CSV示例)。
  • 允许通过单击按钮(2个按钮)备份数据:-
  • 数据库文件的副本,和/或
  • CSV文件
  • 允许恢复数据,通过点击按钮(2按钮)从:-
  • 数据库的文件副本
  • 注意:重启应用程序以确保数据库的完整性
  • 和/或CSV文件
  • 请注意,BLOB未正确备份/恢复(需要更复杂的处理来处理BLOB)

文件/类:-

RoomEntities.kt(为了方便/简洁起见,所有数据库代码都在一个文件中)

  1. /* Database level */
  2. const val THEDATABASE_DATABASE_NAME = "thedatabase.whatever"
  3. const val THEDATABASE_DATABASE_BACKUP_SUFFIX = "-bkp"
  4. const val THEDATBASE_DATABASE_BACKUP_CSV_SUFFIX = ".csv"
  5. const val SQLITE_WALFILE_SUFFIX = "-wal"
  6. const val SQLITE_SHMFILE_SUFFIX = "-shm"
  7. /* Tables */
  8. const val THETABLE_TABLENAME = "theTable"
  9. const val THEOTHERTABLE_TABLENAME = "theOtherTable"
  10. /* Columns TheTable */
  11. const val THETABLE_ID_COLUMN = THETABLE_TABLENAME + "_id"
  12. const val THETABLE_OTHER_COLUMN = THEOTHERTABLE_TABLENAME + "_other"
  13. /* Columns TheOtherTable */
  14. const val THEOTHERTABLE_ID_COLUMN = THEOTHERTABLE_TABLENAME + "_id"
  15. const val THEOTHERTABLE_OTHER_COLUMN = THEOTHERTABLE_TABLENAME + "_other"
  16. const val THEOTHERTABLE_ANOTHER_COLUMN = THEOTHERTABLE_TABLENAME + "_another"
  17. const val THEOTHERTABLE_YETANOTHER_COLUMN = THEOTHERTABLE_TABLENAME + "_yetanother"
  18. /*CSV level */
  19. const val CSV_NEWLINE = "\n"
  20. const val CSV_INDICATOR_START = "{START}"
  21. const val CSV_INDICATOR_END = "{END}"
  22. const val CSV_INDICATOR_TABLE = "{TABLE}"
  23. const val CSV_COMMA_REPLACE = "{COMMA}"
  24. @Entity(tableName = THETABLE_TABLENAME)
  25. data class TheTable(
  26. @PrimaryKey @ColumnInfo(name = THETABLE_ID_COLUMN) var id: Long?=null,
  27. @ColumnInfo(name = THETABLE_OTHER_COLUMN) var other: String
  28. )
  29. @Entity(tableName = THEOTHERTABLE_TABLENAME)
  30. data class TheOtherTable(
  31. @PrimaryKey @ColumnInfo(name = THEOTHERTABLE_ID_COLUMN) var id: Long? = null,
  32. @ColumnInfo(name = THEOTHERTABLE_OTHER_COLUMN) var other: String,
  33. @ColumnInfo(name = THEOTHERTABLE_ANOTHER_COLUMN) var another: String,
  34. @ColumnInfo(name = THEOTHERTABLE_YETANOTHER_COLUMN ) var yetanother: ByteArray = byteArrayOf(1,2,3,4,5,127,-127)
  35. )
  36. @Dao
  37. interface AllDAO {
  38. @Insert(onConflict = OnConflictStrategy.IGNORE)
  39. fun insert(theTable: TheTable): Long
  40. @Insert(onConflict = OnConflictStrategy.IGNORE)
  41. fun insert(theOtherTable: TheOtherTable): Long
  42. @Query("SELECT * FROM $THETABLE_TABLENAME")
  43. fun getAllFromTheTable(): List<TheTable>
  44. @Query("SELECT * FROM $THEOTHERTABLE_TABLENAME")
  45. fun getAllFromTheOtherTable(): List<TheOtherTable>
  46. }
  47. @Database(entities = [TheTable::class,TheOtherTable::class], exportSchema = false, version = 1)
  48. abstract class TheDatabase: RoomDatabase() {
  49. abstract fun getAllDao(): AllDAO
  50. companion object {
  51. private var instance: TheDatabase? = null
  52. fun getInstance(context: Context): TheDatabase {
  53. if (instance==null) {
  54. instance = Room.databaseBuilder(context,TheDatabase::class.java,
  55. THEDATABASE_DATABASE_NAME)
  56. .allowMainThreadQueries()
  57. .build()
  58. }
  59. return instance as TheDatabase
  60. }
  61. }
  62. /**
  63. * Backup the database
  64. */
  65. fun backupDatabase(context: Context): Int {
  66. var result = -99
  67. if (instance==null) return result
  68. val dbFile = context.getDatabasePath(THEDATABASE_DATABASE_NAME)
  69. val dbWalFile = File(dbFile.path + SQLITE_WALFILE_SUFFIX)
  70. val dbShmFile = File(dbFile.path + SQLITE_SHMFILE_SUFFIX)
  71. val bkpFile = File(dbFile.path + THEDATABASE_DATABASE_BACKUP_SUFFIX)
  72. val bkpWalFile = File(bkpFile.path + SQLITE_WALFILE_SUFFIX)
  73. val bkpShmFile = File(bkpFile.path + SQLITE_SHMFILE_SUFFIX)
  74. if (bkpFile.exists()) bkpFile.delete()
  75. if (bkpWalFile.exists()) bkpWalFile.delete()
  76. if (bkpShmFile.exists()) bkpShmFile.delete()
  77. try {
  78. dbFile.copyTo(bkpFile,true)
  79. if (dbWalFile.exists()) dbWalFile.copyTo(bkpWalFile,true)
  80. if (dbShmFile.exists()) dbShmFile.copyTo(bkpShmFile, true)
  81. result = 0
  82. } catch (e: IOException) {
  83. e.printStackTrace()
  84. }
  85. return result
  86. }
  87. /**
  88. * Restore the database and then restart the App
  89. */
  90. fun restoreDatabase(context: Context,restart: Boolean = true) {
  91. if(!File(context.getDatabasePath(THEDATABASE_DATABASE_NAME).path + THEDATABASE_DATABASE_BACKUP_SUFFIX).exists()) {
  92. return
  93. }
  94. if (instance == null) return
  95. val dbpath = instance!!.getOpenHelper().readableDatabase.path
  96. val dbFile = File(dbpath)
  97. val dbWalFile = File(dbFile.path + SQLITE_WALFILE_SUFFIX)
  98. val dbShmFile = File(dbFile.path + SQLITE_SHMFILE_SUFFIX)
  99. val bkpFile = File(dbFile.path + THEDATABASE_DATABASE_BACKUP_SUFFIX)
  100. val bkpWalFile = File(bkpFile.path + SQLITE_WALFILE_SUFFIX)
  101. val bkpShmFile = File(bkpFile.path + SQLITE_SHMFILE_SUFFIX)
  102. dbShmFile.delete() /* ADDED for VACUUM BACKUP */
  103. dbWalFile.delete() /* ADDED for VACUUM BACKUP */
  104. try {
  105. bkpFile.copyTo(dbFile, true)
  106. if (bkpWalFile.exists()) bkpWalFile.copyTo(dbWalFile, true)
  107. if (bkpShmFile.exists()) bkpShmFile.copyTo(dbShmFile,true)
  108. } catch (e: IOException) {
  109. e.printStackTrace()
  110. }
  111. if (restart) {
  112. val i = context.packageManager.getLaunchIntentForPackage(context.packageName)
  113. i!!.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
  114. context.startActivity(i)
  115. System.exit(0)
  116. }
  117. }
  118. fun backupAsCSV() {
  119. val csvFile = File(instance!!.getOpenHelper().writableDatabase.path + THEDATBASE_DATABASE_BACKUP_CSV_SUFFIX)
  120. csvFile.delete()
  121. csvFile.writeText(createAutoCSV())
  122. }
  123. fun restoreFromCSV() {
  124. var db = instance!!.getOpenHelper().writableDatabase
  125. db.beginTransaction() /* want to run in a single transaction */
  126. var currentTableName = ""
  127. val TAG = "CSVRESTORE"
  128. val csvFile = File(instance!!.getOpenHelper().writableDatabase.path + THEDATBASE_DATABASE_BACKUP_CSV_SUFFIX)
  129. csvFile.forEachLine {
  130. /* if Start header then skip */
  131. if (it.equals(CSV_INDICATOR_START)) {
  132. Log.d(TAG,"START INDICATED")
  133. } else {
  134. /* if end header then skip */
  135. if (it.equals(CSV_INDICATOR_END)) {
  136. Log.d(TAG, "END INDICATED")
  137. } else {
  138. /* If table header then delete all rows for the table */
  139. if (it.substring(0, CSV_INDICATOR_TABLE.length).equals(CSV_INDICATOR_TABLE)) {
  140. currentTableName = it.substring(CSV_INDICATOR_TABLE.length + 1)
  141. Log.d("CSVRESTORE","NEW TABLE INDICATED. TableName is $currentTableName")
  142. db.execSQL("DELETE FROM $currentTableName")
  143. Log.d(TAG,"$currentTableName cleared of data")
  144. } else {
  145. /* Otherwise it is actual data for a row so insert the data */
  146. /* Note replacing the comma replacement string with a comma */
  147. Log.d(TAG,"Actual Data is $it")
  148. /* split the CSV into individual elements (column data) */
  149. val splitdata = it.split(",")
  150. var resolvedData: ArrayList<String> = arrayListOf()
  151. val sql = StringBuilder().append("INSERT INTO $currentTableName VALUES(")
  152. var afterFirstData = false
  153. /* For each split element (column) */
  154. /* 1. add a ? for parameter binding */
  155. /* 2. add the resolved (commas reinstated) values for the parameters */
  156. for (s in splitdata) {
  157. if (afterFirstData) sql.append(",")
  158. afterFirstData = true
  159. sql.append("?")
  160. resolvedData.add(s.replace(CSV_COMMA_REPLACE,","))
  161. }
  162. sql.append(");")
  163. db.execSQL(sql.toString(),resolvedData.toArray())
  164. }
  165. }
  166. }
  167. }
  168. db.setTransactionSuccessful()
  169. db.endTransaction()
  170. }
  171. private fun createAutoCSV(): String {
  172. val replaceCommaInData = CSV_COMMA_REPLACE /* commas in the data will be replaced by this */
  173. val rv = StringBuilder().append(CSV_INDICATOR_START)
  174. val sql = StringBuilder()
  175. var afterFirstTable = false
  176. var afterFirstColumn = false
  177. var afterFirstRow = false
  178. val suppDb = instance!!.getOpenHelper().writableDatabase
  179. var currentTableName: String = ""
  180. val csr = instance!!.query(
  181. "SELECT name FROM sqlite_master " +
  182. "WHERE type='table' " +
  183. "AND name NOT LIKE('sqlite_%') " +
  184. "AND name NOT LIKE('room_%') " +
  185. "AND name NOT LIKE('android_%')",
  186. null
  187. )
  188. while (csr.moveToNext()) {
  189. sql.clear()
  190. sql.append("SELECT ")
  191. currentTableName = csr.getString(0)
  192. //if (afterFirstTable) rv.append("$CSV_NEWLINE")
  193. afterFirstTable = true
  194. afterFirstColumn = false
  195. rv.append(CSV_NEWLINE).append("$CSV_INDICATOR_TABLE,$currentTableName")
  196. for (columnName in getTableColumnNames(currentTableName,suppDb)) {
  197. if (afterFirstColumn) sql.append("||','||")
  198. afterFirstColumn = true
  199. sql.append("replace(`$columnName`,',','$replaceCommaInData')")
  200. }
  201. sql.append(" FROM `${currentTableName}`")
  202. val csr2 = instance!!.query(sql.toString(),null)
  203. afterFirstRow = false
  204. while (csr2.moveToNext()) {
  205. //if (!afterFirstRow) rv.append("$CSV_NEWLINE")
  206. afterFirstRow = true
  207. rv.append(CSV_NEWLINE).append(csr2.getString(0))
  208. }
  209. }
  210. rv.append(CSV_NEWLINE).append("$CSV_INDICATOR_END")
  211. return rv.toString()
  212. }
  213. private fun getTableColumnNames(tableName: String, suppDB: SupportSQLiteDatabase): List<String> {
  214. val rv = arrayListOf<String>()
  215. val csr = suppDB.query("SELECT name FROM pragma_table_info('${tableName}')",null)
  216. while (csr.moveToNext()) {
  217. rv.add(csr.getString(0))
  218. }
  219. csr.close()
  220. return rv.toList()
  221. }
  222. }

MainActivity(布局应该很容易构建-请参阅lateinit

  1. class MainActivity : AppCompatActivity() {
  2. lateinit var db: TheDatabase
  3. lateinit var dao: AllDAO
  4. lateinit var backupButton: Button
  5. lateinit var csvBackupButton: Button
  6. lateinit var restoreButton: Button
  7. lateinit var restoreFromCSVButton: Button
  8. lateinit var addDataButton: Button
  9. lateinit var listView: ListView
  10. lateinit var listViewOther: ListView
  11. var listAdapter: SimpleCursorAdapter? = null
  12. var listAdapterOther: SimpleCursorAdapter? = null
  13. override fun onCreate(savedInstanceState: Bundle?) {
  14. super.onCreate(savedInstanceState)
  15. setContentView(R.layout.activity_main)
  16. backupButton = this.findViewById(R.id.backup)
  17. csvBackupButton = this.findViewById(R.id.csvBackup)
  18. restoreButton = this.findViewById(R.id.restore)
  19. restoreFromCSVButton = this.findViewById(R.id.restoreFromCSV)
  20. addDataButton = this.findViewById(R.id.addData)
  21. listView = this.findViewById(R.id.datalist)
  22. listViewOther = this.findViewById(R.id.datalistOther)
  23. db = TheDatabase.getInstance(this)
  24. dao = db.getAllDao()
  25. setButtonListeners()
  26. setOrRefreshListView()
  27. }
  28. fun setButtonListeners() {
  29. addDataButton.setOnClickListener {
  30. dao.insert(TheTable(other = "DATA1,_${System.currentTimeMillis()}"))
  31. dao.insert(TheTable(other = "DATA2_,${System.currentTimeMillis()}"))
  32. dao.insert(TheTable(other = ",DATA3_${System.currentTimeMillis()}"))
  33. /*******************************************************************************************************/
  34. /* Note for CSV backup/restore BLOBS are NOT handled properly (that would entail even more complexity) */
  35. /*******************************************************************************************************/
  36. dao.insert(
  37. TheOtherTable(
  38. other = "OTHERDATA1_${System.currentTimeMillis() / 1000},",
  39. another = ",${System.currentTimeMillis()},",
  40. yetanother = byteArrayOf(0,0,0,0,0)
  41. )
  42. )
  43. dao.insert(
  44. TheOtherTable(
  45. other = "OTHERDATA2_${System.currentTimeMillis() / 1000},",
  46. another = "${System.currentTimeMillis()}")
  47. )
  48. dao.insert(
  49. TheOtherTable(
  50. other = "OTHER,DATA3_${System.currentTimeMillis() / 1000}",
  51. another = "${System.currentTimeMillis()}",
  52. yetanother = byteArrayOf(0x00,0x10,0x20,0x30,0x40,0x50,0x60,0x70))
  53. )
  54. dao.insert(
  55. TheOtherTable(other = "OTHERDATA,4_${System.currentTimeMillis() / 1000}",
  56. another = "${System.currentTimeMillis()}")
  57. )
  58. setOrRefreshListView()
  59. }
  60. backupButton.setOnClickListener {
  61. db.backupDatabase(this)
  62. }
  63. csvBackupButton.setOnClickListener {
  64. db.backupAsCSV()
  65. }
  66. restoreButton.setOnClickListener {
  67. db.restoreDatabase(this)
  68. setOrRefreshListView()
  69. }
  70. restoreFromCSVButton.setOnClickListener {
  71. db.restoreFromCSV()
  72. setOrRefreshListView()
  73. }
  74. }
  75. /* initialise or refresh the 2 ListViews */
  76. private fun setOrRefreshListView() {
  77. var csr = db.getOpenHelper().writableDatabase.query("SELECT $THETABLE_ID_COLUMN AS ${BaseColumns._ID}, $THETABLE_OTHER_COLUMN FROM $THETABLE_TABLENAME")
  78. if (listAdapter==null) {
  79. listAdapter = SimpleCursorAdapter(
  80. this,
  81. android.R.layout.simple_list_item_2,
  82. csr,
  83. arrayOf("_id", THETABLE_OTHER_COLUMN),
  84. intArrayOf(android.R.id.text1,android.R.id.text2), 0
  85. )
  86. listView.adapter = listAdapter
  87. } else {
  88. listAdapter!!.swapCursor(csr)
  89. }
  90. var csr2 = db.getOpenHelper().writableDatabase.query("SELECT $THEOTHERTABLE_ID_COLUMN AS ${BaseColumns._ID}, $THEOTHERTABLE_OTHER_COLUMN, $THEOTHERTABLE_ANOTHER_COLUMN FROM $THEOTHERTABLE_TABLENAME;")
  91. if (listAdapterOther==null) {
  92. listAdapterOther = SimpleCursorAdapter(
  93. this,
  94. android.R.layout.simple_list_item_2,
  95. csr2,
  96. arrayOf(THEOTHERTABLE_OTHER_COLUMN, THEOTHERTABLE_ANOTHER_COLUMN),
  97. intArrayOf(android.R.id.text1,android.R.id.text2),
  98. 0
  99. )
  100. listViewOther.adapter = listAdapterOther
  101. } else {
  102. listAdapterOther!!.swapCursor(csr2)
  103. }
  104. }
  105. }


单击一次【添加数据】(第一个表3行,第二个表4行),然后单击【备份】和【CSV备份】按钮后运行。
x1c 0d1x的数据
然后再次点击【添加数据】按钮,在备份后添加一些数据。



然后点击【CSV恢复】按钮



再次单击一次或多次“添加数据”,然后单击“恢复”按钮(从文件备份恢复)


  • 因此返回到原始数据,注意应用程序已重新启动。
展开查看全部
fruv7luv

fruv7luv3#

1.运行应用程序
1.在应用程序中执行操作以创建数据库,并填充所需的数据

  1. Android Studio >设备文件资源管理器


的数据
1.在下拉菜单中,选择具有要导出的数据库的设备。对我来说就是这个模拟器



1.导航到data/data/your.app.package.name/databases
1.右键单击DB文件>保存为


展开查看全部

相关问题