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

学更好的别人,

做更好的自己。

——《微卡智享》

0db1c82043453a1d1087606d9fa303bf.jpeg

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

前言

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

619a561a612bf07a04ee8e352f3d2cd2.png

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

A

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

实现效果

fd82ccd211662996edf2abe536ea50ac.gif

d4dbe4b87debfa89fa99f0cac51d7a20.png

1.本地三个数据库文件

df32ac705f55f89f8ec2fb6457abce75.png

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

e8130d9893b27433185449854a700dc5.png

3.点击备份数据库

688f97c6f84d7113c1cd6f98a6b4deff.png

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

1953e8415c80d6aca5b8752d6d7ad724.png

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

eb6c2429973d39d585bbd2dca955d4ce.png

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

276ee2b8b262804e8f11f52fdedb3748.png

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

46953c18ed9f0f850a5b88a82e99b879.png

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

核心代码

d35018e328ea1def14d2bd79a1acbfb3.png

微卡智享

备份和还原类(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<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界面显示还原的进度。

22046300583b9e82728aed8b749aaa0a.png

发送当前进度

d07e8a0e4a95089118dbf1707c80f7e7.png

UI显示当前进度

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

ed2f349fd39e0e3eeb09a7a828f7ff20.png

AndroidManifest中加入清单权限

0fa42a448811787f8b23eb2a48aecb57.png

还有存放的目录

05c525c05ad7b2a282a1dfd8b427640f.png

对应的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中动态申请权限 

3cfdee6879c47aa1ee86615103c5f53f.png

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

80c2a6c594f11585acb26086f9b26c1c.png

申请权限函数

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

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

b39b623baf39d76fa076de86a03d63cd.png

758a7f91f04137dbbb608afca210b63f.png

往期精彩回顾

 

a6fd5c3c576afcdce7260cce72969b72.jpeg

制作一个Android Sqlite远程运维小工具

 

 

94f9e0a9be7debba422ba7ba38e23665.jpeg

实现Android本地Sqlite数据库网络传输到PC端

 

 

1bf0908fdc6c6fc76840a0d0ba99d6e1.jpeg

Android Room数据库版本迁移的实战

 

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是使用Java程序备份还原SQLite数据库的示例代码: 1. 备份SQLite数据库: ```java import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class SQLiteBackup { public static void main(String[] args) { String sourceDBPath = "path/to/source.db"; // 源数据库文件路径 String backupDBPath = "path/to/backup.db"; // 备份数据库文件路径 try { File sourceDBFile = new File(sourceDBPath); File backupDBFile = new File(backupDBPath); FileInputStream fis = new FileInputStream(sourceDBFile); FileOutputStream fos = new FileOutputStream(backupDBFile); byte[] buffer = new byte[1024]; int length; while ((length = fis.read(buffer)) > 0) { fos.write(buffer, 0, length); } fos.flush(); fos.close(); fis.close(); System.out.println("SQLite database backup completed."); } catch (IOException e) { e.printStackTrace(); } } } ``` 2. 还原SQLite数据库: ```java import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class SQLiteRestore { public static void main(String[] args) { String backupDBPath = "path/to/backup.db"; // 备份数据库文件路径 String restoreDBPath = "path/to/restore.db"; // 还原数据库文件路径 try { File backupDBFile = new File(backupDBPath); File restoreDBFile = new File(restoreDBPath); FileInputStream fis = new FileInputStream(backupDBFile); FileOutputStream fos = new FileOutputStream(restoreDBFile); byte[] buffer = new byte[1024]; int length; while ((length = fis.read(buffer)) > 0) { fos.write(buffer, 0, length); } fos.flush(); fos.close(); fis.close(); System.out.println("SQLite database restore completed."); } catch (IOException e) { e.printStackTrace(); } } } ``` 请注意,你需要将代码中的`path/to/source.db`和`path/to/backup.db`替换为实际的数据库文件路径。同样,对于还原数据库,你需要将`path/to/backup.db`和`path/to/restore.db`替换为实际的数据库文件路径。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vaccae

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值