当前位置: 首页 > news >正文

【Android入门】4、数据持久化:文件、SharedPreferences 和 Sqlite

文章目录

  • 五、数据持久化
    • 5.1 文件持久化
      • 5.1.1 文件写入
      • 5.1.2 文件读取
    • 5.2 SharedPreferences 持久化
      • 5.2.1 数据写入
      • 5.2.2 数据读取
      • 6.2.3 实现记住密码功能
    • 5.3 Sqlite 数据库持久化
      • 5.3.1 创建数据库
      • 5.3.2 升级数据库
      • 5.3.3 添加数据
      • 5.3.4 更新数据
      • 5.3.5 删除数据
      • 5.3.6 查询数据
      • 5.3.7 直接用 SQL 操作数据库
      • 5.3.8 使用事务

五、数据持久化

Android的数据持久化3种方式: 文件存储, SharedPreferences, 数据库存储

5.1 文件持久化

5.1.1 文件写入

默认写在/data/data/com.example.FilePersistenceTest/files/data中, 当按back键时即写入该文件

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
    override fun onDestroy() {
        super.onDestroy()
        val inputText = editText.text.toString()
        save(inputText)
    }
    private fun save(inputText: String) {
        try {
            val output = openFileOutput("data", Context.MODE_PRIVATE)
            val writer = BufferedWriter(OutputStreamWriter(output))
            writer.use {
                it.write(inputText)
            }
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }
}

5.1.2 文件读取

程序加载时, 读取到editText中

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val inputText = load()
        if (inputText.isNotEmpty()) {
            editText.setText(inputText)
            editText.setSelection(inputText.length)
            Toast.makeText(this, "Restoring succeeded", Toast.LENGTH_SHORT).show()
        }
    }
    private fun load(): String {
        val content = StringBuilder()
        try {
            val input = openFileInput("data")
            val reader = BufferedReader(InputStreamReader(input))
            reader.use {
                reader.forEachLine {
                    content.append(it)
                }
            }
        } catch (e: IOException) {
            e.printStackTrace()
        }
        return content.toString()
    }
}

5.2 SharedPreferences 持久化

是键值对的形式, 且保持数据结构(如整型, 如字符串等), 更易用

5.2.1 数据写入

有2种获取SharedPreferences的方式

  • 第一种是Context类中的getSharedPreferences()方法, 其有2个参数
    • 第1个参数是路径, 在/data/data//shared_prefs/
    • 第2个参数是操作模式, 默认是MODE_PRIVATE, 即只有当前应用程序才可对此读写
  • 第二种是Activity类的getPreferences()方法
    • 只有1个参数
    • 自动将当前activity雷鸣作为SharedPreferences的文件名
    • 调用方式如下3步骤
      • 调用SharedPreferences对象的edit()方法获取一个SharedPreferences.Editor对象
      • 向SharedPreferences.Editor对象中添加数据,比如添加一个布尔型数据就使用putBoolean()方法,添加一个字符串则使用putString()方法,以此类推。
      • 调用apply()方法将添加的数据提交,从而完成数据存储操作

5.2.2 数据读取

布局如下

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/saveButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Save Data" />

    <Button
        android:id="@+id/restoreButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Restore Data" />
</LinearLayout>
  • MainActivity如下
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        saveButton.setOnClickListener {
            val editor = getSharedPreferences("data", Context.MODE_PRIVATE).edit()
            editor.putString("name", "Tom")
            editor.putInt("age", 28)
            editor.putBoolean("married", false)
            editor.apply()
        }
        restoreButton.setOnClickListener {
            val prefs = getSharedPreferences("data", Context.MODE_PRIVATE)
            val name = prefs.getString("name", "")
            val age = prefs.getInt("age", 0)
            val married = prefs.getBoolean("married", false)
            Log.d("MainActivity", "name is $name")
            Log.d("MainActivity", "age is $age")
            Log.d("MainActivity", "married is $married")
        }
    }
}

通过点击按钮, 会在/data/data/com.example.sharedpreferencestest/shared_prefs/data.xml中存放如下数据

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <string name="name">Tom</string>
    <boolean name="married" value="false" />
    <int name="age" value="28" />
</map>

点击load按钮, 即可加载数据, 在控制台打印如下

// 刚开始点击load按钮后的打印, 为空值
2022-08-12 09:56:59.452 3376-3376/com.example.sharedpreferencestest D/MainActivity: name is 
2022-08-12 09:56:59.452 3376-3376/com.example.sharedpreferencestest D/MainActivity: age is 0
2022-08-12 09:56:59.452 3376-3376/com.example.sharedpreferencestest D/MainActivity: married is false
// 点击save按钮后, 再点击load按钮后的打印, 为save的值
2022-08-12 10:04:05.721 5566-5566/com.example.sharedpreferencestest D/MainActivity: name is Tom
2022-08-12 10:04:05.721 5566-5566/com.example.sharedpreferencestest D/MainActivity: age is 28
2022-08-12 10:04:05.721 5566-5566/com.example.sharedpreferencestest D/MainActivity: married is false

在这里插入图片描述

6.2.3 实现记住密码功能

新建一个LoginActivity,布局如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:orientation="horizontal">

        <TextView
            android:layout_width="90dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:text="Account:"
            android:textSize="18sp" />

        <EditText
            android:id="@+id/accountEdit"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_weight="1" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:orientation="horizontal">

        <TextView
            android:layout_width="90dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:text="Password:"
            android:textSize="18sp" />

        <EditText
            android:id="@+id/passwordEdit"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_weight="1"
            android:inputType="textPassword" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <CheckBox
            android:id="@+id/rememberPass"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Remember password"
            android:textSize="18sp" />
    </LinearLayout>

    <Button
        android:id="@+id/login"
        android:layout_width="200dp"
        android:layout_height="60dp"
        android:layout_gravity="center_horizontal"
        android:text="Login" />
</LinearLayout>
  • LoginActivity如下
class LoginActivity : BaseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
        val prefs = getPreferences(Context.MODE_PRIVATE)
        val isRemember = prefs.getBoolean("remember_password", false)
        if (isRemember) {
            // 将账号和密码都设置到文本框中
            val account = prefs.getString("account", "")
            val password = prefs.getString("password", "")
            accountEdit.setText(account)
            passwordEdit.setText(password)
            rememberPass.isChecked = true
        }
        login.setOnClickListener {
            val account = accountEdit.text.toString()
            val password = passwordEdit.text.toString()
            // 如果账号是admin且密码是123456,就认为登录成功
            if (account == "admin" && password == "123456") {
                val editor = prefs.edit()
                if (rememberPass.isChecked) { // 检查复选框是否被选中
                    editor.putBoolean("remember_password", true)
                    editor.putString("account", account)
                    editor.putString("password", password)
                } else {
                    editor.clear()
                }
                editor.apply()
                val intent = Intent(this, MainActivity::class.java)
                startActivity(intent)
                finish()
            } else {
                Toast.makeText(
                    this, "account or password is invalid",
                    Toast.LENGTH_SHORT
                ).show()
            }
        }
    }
}

在这里插入图片描述

5.3 Sqlite 数据库持久化

5.3.1 创建数据库

SQLiteOpenHelper类, 有onCreate()和onUpgrade()方法, 实现数据库创建和升级
getReadableDatabase()和getWriteableDatabase()获取数据库实例
点击按钮则创建database, 其中databaseHelper专门负责调用sqliteOpenHelper来调用数据库

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val dbHelper = MyDatabaseHelper(this, "BookStore.db", 1)
        createDatabase.setOnClickListener {
            dbHelper.writableDatabase
        }
    }
}

class MyDatabaseHelper(val context: Context, name: String, version: Int) :
    SQLiteOpenHelper(context, name, null, version) {
    private val createBook = "create table Book (" +
            " id integer primary key autoincrement," +
            "author text," +
            "price real," +
            "pages integer," +
            "name text)"

    override fun onCreate(db: SQLiteDatabase) {
        db.execSQL(createBook)
        Toast.makeText(context, "Create succeeded", Toast.LENGTH_SHORT).show()
    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
    }
}

在/data/data/com.example.databasetest/databases/BookStore.db有sqlite文件, 可导出并用DBBrowser插件查看

5.3.2 升级数据库

目前有了Book表,若希望增加Category表, 则可添加如下代码, 多次调用onCreate(), 但只有第一次才会生效, 因为只有第一次才会建库BookStore.db, 后续若此库存在则不会再建库BookStore.db, 除非卸载app或手动删除BookStore.db文件

class MyDatabaseHelper(val context: Context, name: String, version: Int) :
    SQLiteOpenHelper(context, name, null, version) {
    private val createBook = "create table Book (" +
            " id integer primary key autoincrement," +
            "author text," +
            "price real," +
            "pages integer," +
            "name text)"
    private val createCategory = "create table Category (" +
            "id integer primary key autoincrement," +
            "category_name text," +
            "category_code integer)"

    override fun onCreate(db: SQLiteDatabase) {
        db.execSQL(createBook)
        db.execSQL(createCategory)
        Toast.makeText(context, "Create succeeded", Toast.LENGTH_SHORT).show()
    }

    // 为了保证升级兼容性, 一般会在onUpgrade()方法内根据version判断执行不同版本的升级逻辑
    // 从低version开始, 保证任何版本都能成功执行
    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
         if (oldVersion <= 1) {
             db.execSQL(createCategory)
         }
         if (oldVersion <= 2) {
             db.execSQL("alter table Book add column category_id integer")
         }
    }
}

若传入的version大于旧值则调用onUpgrade, 否则若version相等则调onUpdate

val dbHelper = MyDatabaseHelper(this, "BookStore.db", 2)
// 传入的version>1时, 即会调用onUpgrade()

5.3.3 添加数据

通过如下, 可将数据插入数据库中

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val dbHelper = MyDatabaseHelper(this, "BookStore.db", 2)

        addData.setOnClickListener {
            val db = dbHelper.writableDatabase
            val values1 = ContentValues().apply {
                // 开始组装第一条数据
                put("name", "The Da Vinci Code")
                put("author", "Dan Brown")
                put("pages", 454)
                put("price", 16.96)
            }
            db.insert("Book", null, values1) // 插入第一条数据
            val values2 = ContentValues().apply {
                // 开始组装第二条数据
                put("name", "The Lost Symbol")
                put("author", "Dan Brown")
                put("pages", 510)
                put("price", 19.95)
            }
            db.insert("Book", null, values2) // 插入第二条数据
        }
    }
}

最终会发现落库成功

sqlite3 BookStore.db
SQLite version 3.35.4 2021-04-02 15:20:15
Enter ".help" for usage hints.
sqlite> select * from Book;
1|Dan Brown|16.96|454|The Da Vinci Code
2|Dan Brown|19.95|510|The Lost Symbol
3|Dan Brown|16.96|454|The Da Vinci Code
4|Dan Brown|19.95|510|The Lost Symbol

5.3.4 更新数据

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val dbHelper = MyDatabaseHelper(this, "BookStore.db", 2)
        updateData.setOnClickListener {
            val db = dbHelper.writableDatabase
            val values = ContentValues()
            values.put("price", 10.99)
            db.update("Book", values, "name = ?", arrayOf("The Da Vinci Code"))
        }
    }
}

更新后如下

sqlite3 BookStore.db
SQLite version 3.35.4 2021-04-02 15:20:15
Enter ".help" for usage hints.
sqlite> select * from Book;
1|Dan Brown|10.99|454|The Da Vinci Code
2|Dan Brown|19.95|510|The Lost Symbol
3|Dan Brown|10.99|454|The Da Vinci Code
4|Dan Brown|19.95|510|The Lost Symbol

5.3.5 删除数据

deleteData.setOnClickListener {
    val db = dbHelper.writableDatabase
    db.delete("Book", "pages > ?", arrayOf("500"))
}

5.3.6 查询数据

在这里插入图片描述

下例为获取表中所有行

queryData.setOnClickListener {
    val db = dbHelper.writableDatabase
    // 查询Book表中所有的数据
    val cursor = db.query("Book", null, null, null, null, null, null)
    if (cursor.moveToFirst()) {
        do {
            // 遍历Cursor对象,取出数据并打印
            val name = cursor.getString(cursor.getColumnIndex("name"))
            val author = cursor.getString(cursor.getColumnIndex("author"))
            val pages = cursor.getInt(cursor.getColumnIndex("pages"))
            val price = cursor.getDouble(cursor.getColumnIndex("price"))
            Log.d("MainActivity", "book name is $name")
            Log.d("MainActivity", "book author is $author")
            Log.d("MainActivity", "book pages is $pages")
            Log.d("MainActivity", "book price is $price")
        } while (cursor.moveToNext())
    }
    cursor.close()
}

查出后打印日志如下

2022-08-12 15:10:34.264 7666-7666/com.example.databasetest D/MainActivity: book name is The Da Vinci Code
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book author is Dan Brown
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book pages is 454
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book price is 10.99
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book name is The Lost Symbol
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book author is Dan Brown
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book pages is 510
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book price is 19.95
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book name is The Da Vinci Code
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book author is Dan Brown
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book pages is 454
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book price is 10.99
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book name is The Lost Symbol
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book author is Dan Brown
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book pages is 510
2022-08-12 15:10:34.265 7666-7666/com.example.databasetest D/MainActivity: book price is 19.95

5.3.7 直接用 SQL 操作数据库

db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)",
 arrayOf("The Da Vinci Code", "Dan Brown", "454", "16.96")
)
db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)",
 arrayOf("The Lost Symbol", "Dan Brown", "510", "19.95")
)
db.execSQL("update Book set price = ? where name = ?", arrayOf("10.99", "The Da Vinci Code"))
db.execSQL("delete from Book where pages > ?", arrayOf("500"))
val cursor = db.rawQuery("select * from Book", null)

5.3.8 使用事务

先开启事务, 然后在try catch 和 finally中捕获exception, 做对应处理

replaceData.setOnClickListener {
    val db = dbHelper.writableDatabase
    db.beginTransaction() // 开启事务
    try {
        db.delete("Book", null, null)
        if (true) {
            // 手动抛出一个异常,让事务失败
            throw NullPointerException()
        }
        val values = ContentValues().apply {
            put("name", "Game of Thrones")
            put("author", "George Martin")
            put("pages", 720)
            put("price", 20.85)
        }
        db.insert("Book", null, values)
        db.setTransactionSuccessful() // 事务已经执行成功
    } catch (e: Exception) {
        e.printStackTrace()
    } finally {
        db.endTransaction() // 结束事务
    }
}

日志就会打印printStackTrace对应的方法

2022-08-12 15:30:39.075 8005-8005/com.example.databasetest W/System.err: java.lang.NullPointerException
2022-08-12 15:30:39.080 8005-8005/com.example.databasetest W/System.err:     at com.example.databasetest.MainActivity.onCreate$lambda-7(MainActivity.kt:71)
2022-08-12 15:30:39.080 8005-8005/com.example.databasetest W/System.err:     at com.example.databasetest.MainActivity.$r8$lambda$Ff3cxcjnoRHI0B8DD9nQh8YQ2cY(Unknown Source:0)

相关文章:

  • style样式优先级问题【display:block依旧无法显示DOM元素】
  • (编程语言界的丐帮 C#).NET MD5 HASH 哈希 加密 与JAVA 互通
  • 面试宝典------经典
  • node.js环境搭建
  • 【5G核心网】手把手教你将Open5gs托管到k8s(KubeSphere)
  • 空城机在CSDN的四周年创作纪念日
  • C++ Reference: Standard C++ Library reference: C Library: clocale: struct lconv
  • JavaSE进阶--集合(2万字总结)
  • CKA考题 [k8s1.21]
  • AcWing第 70 场周赛题解
  • 读FFA-net: Feature Fusion Attention Network for Single Image Dehazing
  • 测试需求平台4-Flask实现API服务入门实战
  • js单行代码-----dom
  • 模型压缩常用方法简介
  • css常用属性
  • 【347天】每日项目总结系列085(2018.01.18)
  • 3.7、@ResponseBody 和 @RestController
  • echarts花样作死的坑
  • ECMAScript6(0):ES6简明参考手册
  • JAVA多线程机制解析-volatilesynchronized
  • React-flux杂记
  • React的组件模式
  • Vue小说阅读器(仿追书神器)
  • vue自定义指令实现v-tap插件
  • 给第三方使用接口的 URL 签名实现
  • 力扣(LeetCode)357
  • 前嗅ForeSpider采集配置界面介绍
  • 三栏布局总结
  • 腾讯视频格式如何转换成mp4 将下载的qlv文件转换成mp4的方法
  • 新书推荐|Windows黑客编程技术详解
  • TPG领衔财团投资轻奢珠宝品牌APM Monaco
  • #stm32驱动外设模块总结w5500模块
  • (二)Pytorch快速搭建神经网络模型实现气温预测回归(代码+详细注解)
  • (附源码)springboot社区居家养老互助服务管理平台 毕业设计 062027
  • (附源码)ssm高校实验室 毕业设计 800008
  • (附源码)计算机毕业设计SSM教师教学质量评价系统
  • (九)c52学习之旅-定时器
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理 第13章 项目资源管理(七)
  • (十)DDRC架构组成、效率Efficiency及功能实现
  • (原創) 物件導向與老子思想 (OO)
  • (转)清华学霸演讲稿:永远不要说你已经尽力了
  • .bat批处理(一):@echo off
  • .NET Core MongoDB数据仓储和工作单元模式封装
  • .net core 微服务_.NET Core 3.0中用 Code-First 方式创建 gRPC 服务与客户端
  • .NET 程序如何获取图片的宽高(框架自带多种方法的不同性能)
  • .Net 路由处理厉害了
  • .NET/C# 编译期能确定的字符串会在字符串暂存池中不会被 GC 垃圾回收掉
  • .NET/C# 使用 ConditionalWeakTable 附加字段(CLR 版本的附加属性,也可用用来当作弱引用字典 WeakDictionary)
  • .net遍历html中全部的中文,ASP.NET中遍历页面的所有button控件
  • .NET中 MVC 工厂模式浅析
  • .ui文件相关
  • .w文件怎么转成html文件,使用pandoc进行Word与Markdown文件转化
  • [Android Pro] Notification的使用
  • [ARM]ldr 和 adr 伪指令的区别
  • [C++]类和对象【下】