Android本地Sqlite数据库的备份和还原

学更好的别人,

做更好的自己。

——《微卡智享》

Android本地Sqlite数据库的备份和还原

本文长度为3024 ,预计阅读6 分钟

前言

互联网Android APP开发其实很多都是Android端写UI,业务逻辑通过API回调展示数据,而我这边主要是硬件设备还要打交道,平时也要考虑网络不通的情况下单机的正常使用,所以所有的业务逻辑都是在程序中实现,数据的本地化要求也高,那就需要用到Sqlite数据库,所以这篇文章就专门来说说Sqlite数据库的备份和还原。

Android本地Sqlite数据库的备份和还原

怎么实现Sqlite的数据库备份和还原?

A

其实实现Sqlite的备份和还原原理还是比较简单,就是将App中生成的Sqlite的数据库文件复制到存放区域,还原就是将复制的数据库文件拷回到程序指定的数据库目录即可。 但这里有个比较关键的问题,就是存放的目录必须是自己指定的外部目录,如果是备份数据库文件还是在程序包下的目录,遇到安装升级签名不对,或是手工打开应用程序点击了清除数据,那本地的数据库以及备份的数据库文件也会全部清空,那后果可想而知。。。。

实现效果

Android本地Sqlite数据库的备份和还原

Android本地Sqlite数据库的备份和还原

1.本地三个数据库文件

Android本地Sqlite数据库的备份和还原

2.SD卡目录下没有备份文件

Android本地Sqlite数据库的备份和还原

3.点击备份数据库

Android本地Sqlite数据库的备份和还原

4.SD卡目录下已经拷贝过来数据库了

Android本地Sqlite数据库的备份和还原

5.删除原来databases目录的数据库

Android本地Sqlite数据库的备份和还原

6.重新查询后什么也没显示

Android本地Sqlite数据库的备份和还原

7.点击还原数据库后databases目录下的文件已经拷贝回来了

Android本地Sqlite数据库的备份和还原

8.重新点击查询数据,可以看到显示的数据了

核心代码

Android本地Sqlite数据库的备份和还原

微卡智享

备份和还原类(DbBackupUtil‍)

package com.vaccae.roomdemo

import android.annotation.SuppressLint
import android.content.Context
import android.os.Environment
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException

/**
 * 作者:Vaccae
 * 邮箱:3657447@qq.com
 * 创建时间:18:22
 * 功能模块说明:
 */

class DbBackupUtil {

    private var mContext: Context? = null

    val path =
        Environment.getExternalStorageDirectory().absolutePath + File.separator + "RoomBackup" + File.separator

    private fun getInstance(context: Context) {
        mContext ?: run {
            synchronized(DbBackupUtil::class.java) {
                mContext = context
            }
        }
    }

    private fun createPath() {
        //安装包路径
        val updateDir = File(path)
        //创建文件夹
        if (!updateDir.exists()) {
            updateDir.mkdirs()
        }
    }

    suspend fun backup(context: Context): Flow = flow {
        getInstance(context)
        createPath()
        mContext?.let {
            val strs = it.databaseList()
            emit("共${strs.size}个数据库文件,开始备份")

            for (str in strs) {
                emit("正在备份${str}数据库。。。")

                //找到文件的路径  /data/data/包名/databases/数据库名称
                val dbFile = it.getDatabasePath(str)
                //val dstFile = it.getExternalFilesDir("db").toString() + "/" + str

                val dstFile = path + str;

                var fis: FileInputStream? = null
                var fos: FileOutputStream? = null
                try {
                    //文件复制到sd卡中
                    fis = FileInputStream(dbFile)
                    fos = FileOutputStream(dstFile)
                    var len = 0
                    val buffer = ByteArray(2048)
                    while (-1 != fis.read(buffer).also({ len = it })) {
                        fos.write(buffer, 0, len)
                    }
                    fos.flush()
                    emit("${str}数据库备份完成。。。")
                } catch (e: Exception) {
                    throw e
                } finally {
                    //关闭数据流
                    try {
                        fos?.close()
                        fis?.close()
                    } catch (e: IOException) {
                        throw e
                    }
                }
            }
            emit("所有数据库备份完成")
        } ?: kotlin.run { throw Exception("未定义Context") }
    }

    suspend fun restore(context: Context): Flow {
        return flow {
            getInstance(context)
            createPath()

            mContext?.let {
                //var dbfiles = it.getExternalFilesDir("db")
                var dbfiles = File(path)

                dbfiles.let { dbs ->
                    var files = dbs.listFiles()
                    if (files.isNotEmpty()) {
                        emit("共${files.size}个数据库文件,开始还原")

                        for (str in files) {
                            var dbFile = it.getDatabasePath(str.name)
                            dbFile.delete()

                            var fis: FileInputStream? = null
                            var fos: FileOutputStream? = null
                            try {
                                //文件复制到sd卡中
                                fis = FileInputStream(str)
                                fos = FileOutputStream(dbFile)
                                var len = 0
                                val buffer = ByteArray(2048)
                                while (-1 != fis.read(buffer).also({ len = it })) {
                                    fos.write(buffer, 0, len)
                                }
                                fos.flush()
                                emit("${str}数据库还原完成。。。")
                            } catch (e: Exception) {
                                throw e
                            } finally {
                                //关闭数据流
                                try {
                                    fos?.close()
                                    fis?.close()
                                } catch (e: IOException) {
                                    throw e
                                }
                            }
                        }
                        emit("所有数据库还原完成")
                    }
                }
            } ?: kotlin.run { throw Exception("未定义Context") }
        }
    }
}

Activity中的调用

//备份调用
        btnbackup.setOnClickListener {
            GlobalScope.launch(Dispatchers.Main) {
                DbBackupUtil().backup(applicationContext)
                    .flowOn(Dispatchers.IO)
                    .collect(collector = FlowCollector { t ->
                        tvshow.text = t
                    })
            }
        }

        //还原调用
        btnrestore.setOnClickListener {
            GlobalScope.launch(Dispatchers.Main) {
                DbBackupUtil().restore(applicationContext)
                    .flowOn(Dispatchers.IO)
                    .collect(collector = FlowCollector { t ->
                        tvshow.text = t
                    })
            }
        }

微卡智享

重点说明

1.备份和还原采用返回flow的形式,因为数据库文有三个,这样可以在UI界面显示还原的进度。

Android本地Sqlite数据库的备份和还原

发送当前进度

Android本地Sqlite数据库的备份和还原

UI显示当前进度

2.备份的数据库文件存放到SD卡自定义目录中,防止应用程序点击清除数据后,备份文件如果也是拷贝到程序包目录下的也会一起删除。 不过针对存储权限,在 Android 6.0 之后就变成了危险权限,而到了 Android 11 上面变成了特殊权限,必须加上授权所有文件管理权限才行。

Android本地Sqlite数据库的备份和还原

AndroidManifest中加入清单权限

Android本地Sqlite数据库的备份和还原

还有存放的目录

Android本地Sqlite数据库的备份和还原

对应的xml/file_pahts.xml中定义external-path

AndroidManifest.xml


file_path.xml


Activity中动态申请权限

Android本地Sqlite数据库的备份和还原

StartActivityforResult已经废弃了,所以改用registerForActivityResult来实现

Android本地Sqlite数据库的备份和还原

申请权限函数

MainActivity申请权限代码

class MainActivity : AppCompatActivity() {

    companion object {
        private const val REQUEST_CODE_PERMISSIONS = 10
        private val REQUIRED_PERMISSIONS = arrayOf(
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.ACCESS_NETWORK_STATE,
            Manifest.permission.CHANGE_NETWORK_STATE,
            Manifest.permission.INTERNET
        )
    }

    //StartActivity弃用后,使用registerForActivityResult来实现
    private val requestDataLauncher =
        registerForActivityResult(object : ActivityResultContract() {
            override fun createIntent(context: Context, input: Int?): Intent {
                val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
                intent.data = Uri.parse("package:$packageName")
                return intent
            }

            override fun parseResult(resultCode: Int, intent: Intent?): String {
                TODO("Not yet implemented")
            }
        }
        ) {
            Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
        }

    private fun allPermissionsGranted() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            // 先判断有没有权限
            if (Environment.isExternalStorageManager()) {
                REQUIRED_PERMISSIONS.all {
                    ContextCompat.checkSelfPermission(
                        baseContext,
                        it
                    ) == PackageManager.PERMISSION_GRANTED
                }
            } else {
                requestDataLauncher.launch(REQUEST_CODE_PERMISSIONS)
            }
        } else {
            REQUIRED_PERMISSIONS.all {
                ContextCompat.checkSelfPermission(
                    baseContext,
                    it
                ) == PackageManager.PERMISSION_GRANTED
            }
        }
    }

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

        //申请权限
        allPermissionsGranted()
    }
}

这样整个Android本地数据库的备份和还原就完成了,Demo我是直接加在的使用原来做的远程查询分析小工具的那个Demo里《》,完整的源码链接在下方:

源码地址

https://github.com/Vaccae/TransAndroidSqliteDBDemo.git

点击阅读原文可以看到”码云”的地址

Android本地Sqlite数据库的备份和还原

Android本地Sqlite数据库的备份和还原

往期精彩回顾

Original: https://blog.csdn.net/Vaccae/article/details/125611729
Author: Vaccae
Title: Android本地Sqlite数据库的备份和还原

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/815185/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球