当前位置:网站首页>Android本地Sqlite数据库的备份和还原
Android本地Sqlite数据库的备份和还原
2022-07-05 12:57:00 【Vaccae】
学更好的别人,
做更好的自己。
——《微卡智享》
本文长度为3024字,预计阅读6分钟
前言
互联网Android APP开发其实很多都是Android端写UI,业务逻辑通过API回调展示数据,而我这边主要是硬件设备还要打交道,平时也要考虑网络不通的情况下单机的正常使用,所以所有的业务逻辑都是在程序中实现,数据的本地化要求也高,那就需要用到Sqlite数据库,所以这篇文章就专门来说说Sqlite数据库的备份和还原。
怎么实现Sqlite的数据库备份和还原?
A
其实实现Sqlite的备份和还原原理还是比较简单,就是将App中生成的Sqlite的数据库文件复制到存放区域,还原就是将复制的数据库文件拷回到程序指定的数据库目录即可。但这里有个比较关键的问题,就是存放的目录必须是自己指定的外部目录,如果是备份数据库文件还是在程序包下的目录,遇到安装升级签名不对,或是手工打开应用程序点击了清除数据,那本地的数据库以及备份的数据库文件也会全部清空,那后果可想而知。。。。
实现效果
1.本地三个数据库文件
2.SD卡目录下没有备份文件
3.点击备份数据库
4.SD卡目录下已经拷贝过来数据库了
5.删除原来databases目录的数据库
6.重新查询后什么也没显示
7.点击还原数据库后databases目录下的文件已经拷贝回来了
8.重新点击查询数据,可以看到显示的数据了
核心代码

微卡智享
备份和还原类(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
* 邮箱:[email protected]
* 创建时间: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<String> = 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<String> {
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界面显示还原的进度。
发送当前进度
UI显示当前进度
2.备份的数据库文件存放到SD卡自定义目录中,防止应用程序点击清除数据后,备份文件如果也是拷贝到程序包目录下的也会一起删除。不过针对存储权限,在 Android 6.0 之后就变成了危险权限,而到了 Android 11 上面变成了特殊权限,必须加上授权所有文件管理权限才行。
AndroidManifest中加入清单权限
还有存放的目录
对应的xml/file_pahts.xml中定义external-path
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.vaccae.roomdemo">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:exported="true"
android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>
file_path.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!--最终设置里面NAME和PATH都要和文件夹的名称一样,才会找到对应的文件-->
<external-path name="RoomBackup" path="RoomBackup"/>
</paths>
Activity中动态申请权限
StartActivityforResult已经废弃了,所以改用registerForActivityResult来实现
申请权限函数
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<Int, String>() {
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里《制作一个Android Sqlite远程运维小工具》,完整的源码链接在下方:
源码地址
https://github.com/Vaccae/TransAndroidSqliteDBDemo.git
点击阅读原文可以看到“码云”的地址
完
往期精彩回顾
边栏推荐
- 初次使用腾讯云,解决只能使用webshell连接,不能使用ssh连接。
- Rocky基础命令3
- OpenHarmony应用开发之Navigation组件详解
- Navigation property and entityset usage in SAP segw transaction code
- Datapipeline was selected into the 2022 digital intelligence atlas and database development report of China Academy of communications and communications
- APICloud Studio3 API管理与调试使用教程
- MySQL 巨坑:update 更新慎用影响行数做判断!!!
- Pycharm installation third party library diagram
- Halcon 模板匹配实战代码(一)
- 阿里云SLB负载均衡产品基本概念与购买流程
猜你喜欢
国际自动机工程师学会(SAE International)战略投资几何伙伴
自然语言处理系列(一)入门概述
Principle and performance analysis of lepton lossless compression
Write macro with word
Developers, is cloud native database the future?
A specific example of ABAP type and EDM type mapping in SAP segw transaction code
I'm doing open source in Didi
MySQL giant pit: update updates should be judged with caution by affecting the number of rows!!!
将函数放在模块中
RHCSA5
随机推荐
leetcode:221. Maximum square [essence of DP state transition]
How to protect user privacy without password authentication?
碎片化知识管理工具Memos
简单上手的页面请求和解析案例
Small case of function transfer parameters
Rocky基础命令3
Default parameters of function & multiple methods of function parameters
Navigation property and entityset usage in SAP segw transaction code
CAN和CAN FD
Shi Zhenzhen's 2021 summary and 2022 outlook | colorful eggs at the end of the article
RHCSA2
蜀天梦图×微言科技丨达梦图数据库朋友圈+1
同事半个月都没搞懂selenium,我半个小时就给他整明白!顺手秀了一波爬淘宝的操作[通俗易懂]
OpenHarmony应用开发之Navigation组件详解
Introduction to sap ui5 flexiblecolumnlayout control
Detailed explanation of navigation component of openharmony application development
[cloud native] use of Nacos taskmanager task management
Le rapport de recherche sur l'analyse matricielle de la Force des fournisseurs de RPA dans le secteur bancaire chinois en 2022 a été officiellement lancé.
Write macro with word
初识Linkerd项目