Android三种数据存储的方式

Android数据存储技术

  • 在任何一个程序当中说白了就是和数据在不停的打交道
  • 如果我们的应用程序没有数据那么就变成了一种空壳子,对用户来说没有实际的意义
  • 并且我们想要对关键数据进行保存,就需要用到数据的持久化了

持久化技术

  • 数据持久化就是指将那些内存当中的数据保存到设备当中,保证手机在关机的情况下这些数据仍然不会丢失.
  • 保存在内存当中的数据是瞬时状态的,保存在设备当中的数据是持久状态的
  • 持久化技术提供了一种机制,可以让数据在瞬时状态和持久状态之间进行转换
  • 在Android系统当中主要提供了三种方式进行持久化:文件存储,SharePreferences存储以及数据库存储

文件存储

  • 文件存储时Android中最基本的数据存储方式,他不对存储的内容进行任何格式化处理,所有的数据都是原封不动的保存在文件当中的,因为他比较适合存储一些简单的文本数据和二进制数据
  • 如果需要使用文件存储的方式来保存一些较为复杂的数据结构,就需要定义一套自己的格式规范,方便之后将数据从文件当中重新解析出来.

将数据存储到文件当中

  • 在Context类当中提供了一个openFileOutput()方法,用于将数据存储到文件当中该方法接收两个参数.
  • 第一个是文件名,在文件创建的时候进行使用,注意这里指定的文件名不可以包含路径,因为所有的文件都默认存储到/data/data//files/目录下面
  • 第二个参数是文件的操作模式,主要有MODE_PRIVATE和MODE_APPEND两种模式可以选择,默认是MODE_PRIVATE,表示当指定相同文件名的时候,所写入的内容就会覆盖原先的内容,而MODE_APPEND则代表文件如果存在的话,就往文件当中追加内容,不存在就新创建文件.
  • 本来还有另外两种操作模式:MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE.这两种模式表示允许其他应用程序对我们程序中的文件进行读写操作,但是这两种模式过于危险,很容易引发安全漏洞,早在安卓4.2版本就放弃了.
  • openFileOutput()方法的返回值是一个FileOutputStream对象,得到这个对象之后就可以使用Java流的方式将数据写入文件当中了.
  • 以下是一段示例,表示如何将一段文本保存到文件当中
fun save(inputText: String) {
    
    
    try {
    
    
        val output = openFileOutput("data", Context.MODE_PRIVATE)
        val writer = BufferWriter(OutputStreamWriter(output))
        writer.use {
    
    
            it.write(inputText)
        }
    } catch (e: IOException) {
    
    
        e.printStackTrace()
    }
}
  • 上述代码的主要意思就是先使用openFileOutput()方法得到一个FileOutputStream对象,然会借助这个FileOutputStream对象构建出一个OutputStreamWriter对象,然后再借助OutputStreamWriter对象构建出一个BufferWriter对象,然后使用use扩展方法,在该扩展方法中使用Lambda表达式调用write方法将文本写入到文件当中.
  • 其中use方法是Kotlin内置的扩展函数,他会保证在Lambda表达式中的代码全部执行完毕之后自动将外层的流进行关闭这样我们就不需要再编写一个finally语句,手动去关闭流,这是一个非常好的扩展函数.
  • 在Kotlin当中是没有异常检查机制的,这就意味着Kotlin编写的所有代码都不会强制要求你将异常进行捕捉和抛出.

示例_将数据存储到文件当中

  • MainActivity代码
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.stackTrace
        }
    }
}
  • 重写了onDestroy()方法,这样就可以保证在Activity销毁之前一定会调用这个方法.
  • 在onDestroy()方法中,我们获取了EditText中输入的内容,并调用save()方法将输入的内容存储到文件当中,文件命名为data
  • activity_main.xml代码
<?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">

    <EditText
        android:id="@+id/editText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:hint="Type something here" />

</LinearLayout>
  • 布局文件中就是给我们的程序提供了一个输入框,用于输入我们要保存的数据

示例_从文件当中读取数据

  • 类似于将数据存储到文件当中,Context类中还提供了一个openFileInput,用于从文件当中读取数据,这个方法只需要接收一个参数就是文件名,然后自动会到/data/data//files/目录下面加载这个文件,并且返回一个FileInputStream对象,通过这个对象我们就可以将数据读取出来了
  • 编写读取数据的load函数
    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.stackTrace
        }
        return content.toString()
    }
  • 在onCreate()方法当中调用load函数实现我们启动MainActivity的时候,文本框能够保留我们上次输入的内容
override fun onCreate(savedInstanceState: Bundle?) {
    
    
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val inputText = load()
    if (inputText.isNotEmpty()) {
    
    
        //设置editText的内容为我们从文件中读取出来的内容
        editText.setText(inputText)
        //将光标移动到editText的末尾位置
        editText.setSelection(inputText.length)
        //弹出一个提示
        Toast.makeText(this, "succeeded", Toast.LENGTH_SHORT).show()
    }
}

SharedPreferences存储

  • 不同于文件的存储方式,SharedPreferences是使用键值对的方式来存储数据的.
  • 也就是说当保存当保存一条数据的时候,需要给这条数据提供一个对应的键,这样在读取数据的时候,就可以通过这个键将数据读取出来.
  • SharedPreferences支持多种不同的数据类型存储,如果存储的数据类型是整形,那么读取出来的数据也是整形的
  • 如果存储的数据是字符串,那么读取出来的数据也就是一个字符串.
  • 所以整体来说,SharedPreferences存储数据的方式比文件存储的方式更加方便.

将数据存储到SharedPreferences

  • 想要使用SharedPreferences存储数据,首先需要获取SharedPreferences对象,Android中主要提供了以下两种方式用于得到SharedPreferences对象

1.Context类中getSharedPreferences()方法

  • 此方法接收两个参数
  • 第一个参数:用于指定SharedPreferences文件的名称,如果指定的文件不存在则会创建一个,SharedPreferences文件都是存放在/data/data//shared_prefs/目录下
  • 第二个参数:用于指定操作模式,目前只有默认的MODE_PRIVATE这一种模式可选,他和直接传入0的效果是相同的,表示只有当前程序才可以对这个SharedPreferences文件进行读写
  • 其他的几种操作模式已经被废除

2.Activity类中的getSharedPreferences()方法

  • 这个方法和Context中的方法非常相似,不过他只接收一个参数,就是操作模式
  • 因为这个方法会自动将当前Activity的类名作为SharedPreferences的文件名
  • 得到SharedPreferences对象之后就可以往SharedPreferences文件中存储数据了,主要分为三个步骤:
  1. 调用SharedPreferences对向的edit()方法获取一个SharedPreferences.Editor对象
  2. 向SharedPreferences.Editor对象中添加数据,比如添加一个布尔类型的对象就使用putBoolean()方法
  3. 调用apply()方法将添加的数据提交,从而完成数据存储操作

往SharedPreferences文件中存放数据示例

  • 创建一个项目SharedPreferencesTest
  • 编写activity_main.xml中的代码
<?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">

    <Button
        android:id="@+id/saveButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Save Data" />
    
</LinearLayout>
  • 在页面当中放置了一个按钮,用于将一些数据存储到SharedPreferences文件当中
  • 然后修改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", 18)
            editor.putBoolean("married", false)
            editor.apply()
        }
    }
}
  • 在上述的代码当中,给按钮注册了一个点击事件,然后在点击事件当中通过getSharedPreferences()方法指定SharedPreferences的文件名为data,并得到SharedPreferences.Editor对象.
  • 接着给这个对象当中提交了三条不同类型的数据
  • 最后调用apply()方法进行提交,从而完成了数据存储的操作.
  • 打开相应的路径看到数据已经保存在一个xml文件当中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B96B6iOU-1670159871271)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AE%89%E5%8D%93/image-20221201211807440.png)]

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <string name="name">Tom</string>
    <boolean name="married" value="false" />
    <int name="age" value="18" />
</map>
  • 那么就看看如何从SharedPreferences文件当中读取数据

从SharedPreferences文件当中读取数据

  • 根据上面的示例来说,使用SharedPreferences来存储数据是比较简单的,从SharedPreferences中读取数据也是非常简单的
  • SharedPreferences对象中提供了一系列的get方法,用于读取存储的数据,每种get方法对应了SharedPreferences.Editor中的一种put方法,比如读取一个布尔类型的数据就是用getBoolean()方法,这些get方法都接受两个参数
  • 第一个参数是键,传入存储数据的时候使用的键就可以得到相应相应的值
  • 第二个参数是默认值,即表示当传入的键找不到对应的值的时候以什么样的默认值进行返回.

从SharedPreferences文件当中读取数据的例子

  • 在activity_main.xml当中添加一个读取数据的按钮

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jON2cuAb-1670159871272)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AE%89%E5%8D%93/image-20221202104602516.png)]

  • 修改MainActivity当中的代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9IszAY3v-1670159871273)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AE%89%E5%8D%93/image-20221202104702962.png)]

利用SharedPreferences实现一个记住密码的功能

  • 修改之前登录界面的案例
  • 在activity_login.xml文件当中添加一个复选控件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5kGOKa0I-1670159871274)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AE%89%E5%8D%93/image-20221202105336856.png)]

  • 在上述代码当中添加了一个chechkBox复选框,用户可以通过点击这个复选框确定自己是否需要记住密码
  • 修改LoginActivity当中的代码逻辑
  • 根据记住密码的状态选择是否将用户的信息填充到输入框
//如果remember_password对应的是true说明用户之前记住了密码,就可以使用存储的密码来填充输入框
if (isRemember) {
    
    
    //将账号和密码都取出之后填充到输入框当中
    val account = prefs.getString("account", "")
    val password = prefs.getString("password", "")
    accountEdit.setText(account)
    passwordEdit.setText(password)
    //将复选框设置为选中
    rememberPass.isChecked = true
}
  • 点击登录按钮,根据复选框当前被点击的状态判断是否保存当前用户输入的信息
        login.setOnClickListener {
    
    
            //先从Edit当中获取账户名和密码
            val account = accountEdit.text.toString()
            val password = passwordEdit.text.toString()
            //验证账户名和密码的逻辑
            if (account == "admin" && password == "123456") {
    
    
                //如果账号和密码都没有问题,那么就跳转到MainActivity
//                val intent = Intent(this, MainActivity::class.java)
//                startActivity(intent)
//                finish()
                //先获取一个editor对象
                val edit = prefs.edit()
                //说明账号和密码都没有问题,现在我们要根据用户是否选择复选框来决定将用户的账号和密码添加到SharedPreferences进行保存
                if (rememberPass.isChecked) {
    
    
                    //如果复选框被选中,那么我们就将用户的信息进行保存
                    //保存账号
                    edit.putString("account", account)
                    //保存密码
                    edit.putString("password", password)
                    //保存是否基础密码这个状态
                    edit.putBoolean("remember_password", true)
                    //提交保存信息
                    edit.apply()
                } else {
    
    
                    //说明用户这一次没有选择记住密码,那么我们调用clear()方法清空SharedPreferences文件当中的信息
                    edit.clear()
                }
                val intent = Intent(this, MainActivity::class.java)
                startActivity(intent)
                finish()
            } else {
    
    
                //使用Toast提示一段账号或者密码错误的文本
                Toast.makeText(this, "account or password is invalid", Toast.LENGTH_SHORT).show()
            }
        }

SQLite数据库

  • SQLite数据库是安卓系统内置的数据库,是一款轻量级的关系型数据库
  • SQLite数据库运算速度非常快,占用资源很少
  • 支持标准的SQL语法还支持数据库的ACID事务

创建数据库

  • Android为了更加方便我们使用SQLite数据库,专门提供了一个SQLiteOpenHelper帮助类,借助这个类可以非常简单的对数据库进行创建和升级.
  • SQLiteOpenHelper是一个抽象类,我们需要创建自己的帮助类去继承它,在这个类当中有两个抽象方法:onCreate()和onUpgrade().我们必须在自己写的子类中重写这两个方法,然后对数据库进行创建和升级
  • 在SQLiteOpenHelper类当中还有非常重要的两个实例方法:getReadableDatabase()和getWriteableDatabase()
  • 这两个方法都可以创建或者打开一个现有的数据(如果数据库以及存在则直接打开,否则创建一个新的数据库),并且返回一个可对数据库进行读写操作的对象.
  • 不同的是,当数据库不可以写入的时候(比如磁盘已经满了),getReadDatabase()方法返回的对象将会以只读的方式打开数据库,而getWriteDatabase()则会抛出异常.
  • SQLiteOpenHelper中有两个构造参数提供给我们进行选择,一般使用参数少一点的哪个构造参数即可,这个构造参数接收四个参数
  • 第一个参数是:Context
  • 第二个参数是数据库的名字,创建数据库的时候使用的就是这里指定的名字
  • 第三个参数允许我们在查询数据的时候返回一个自定义的Cursor,一般传入null即可
  • 第四个参数表示当前数据库的版本号,可以用来给数据库进行升级操作.
  • 构建出SQLiteOpenHelper的实例之后就可以调用两个重要的实例方法就可以创建数据库了,数据库的文件会存放在/data/data//databases/目录下.此时重写的onCreate()方法也会得到执行,所以通常会在这里处理一些创建表的逻辑.

使用SQLite的示例

  • 创建一个BookStore.db数据库,然后在这个数据库当中创建一个Book表
  • 首先新建一个MyDatabaseHelper类继承SQLiteOpenHelper类,然后再该类中的onCreate()方法中创建一个Book表
class MyDatabaseHelper(val context: Context, val name: String, val version: Int) :
    SQLiteOpenHelper(context, name, null, version) {
    
    
    
    //创建Book表的SQL语句
    private val createBookSQL = "create table Book (" +
            "id integer primary key autoincrement," +
            "author text," +
            "price integer," +
            "pages integer," +
            "name text)"

    override fun onCreate(db: SQLiteDatabase) {
    
    
        //调用execSQL创建表
        db.execSQL(createBookSQL)
        Toast.makeText(context, "Create succeeded", Toast.LENGTH_SHORT).show()
    }

    override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
    
    
        TODO("Not yet implemented")
    }
}
  • 在activity_main.xml文件当中添加一个按钮用于创建数据库
<?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">

    <Button
        android:id="@+id/createDatabase"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="create database"
        android:textSize="18sp" />
</LinearLayout>
  • 在MainActivity中编写该按钮的逻辑
class MainActivity : AppCompatActivity() {
    
    
    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //创建一个MyDatabaseHelper的实例
        val myDatabaseHelper = MyDatabaseHelper(this, "BookStore.db", 1)
        createDatabase.setOnClickListener {
    
    
            myDatabaseHelper.writableDatabase
        }
    }
}
  • 在onCreate()方法当中构建了一个MyDatabaseHelper对象,并在该对象当中指定了数据库和版本号,然后在按钮当中调用了getWritableDatabase()方法
  • 这样当第一次点击按钮的时候就会检查当前程序中有没有BookStore.db这个数据库,于是会创建好该数据库并且调用MyDatabaseHelper类中onCreate()方法来创建Book表.
  • 然后我们在插件市场下载Database Navigator即可使用可视化的界面管理我们刚刚创建的数据库

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nOdZnLpl-1670159871275)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AE%89%E5%8D%93/image-20221202125113538.png)].

升级数据库

  • onUpgrade()方法是对于数据库进行升级的,他在整个数据库的管理工作当中起着非常重要的作用
  • 之前我们给数据库创建了一张Book表,现在我们想给数据库再创建一张分类表Category,我们将这条语句添加到MyDatabaseHelper中代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EzrjNCdT-1670159871277)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AE%89%E5%8D%93/image-20221202160407105.png)]

  • 看上去都是没有问题的,但是我们重新运行一下程序,并点击"Create Database"按钮,但是没有弹出创建成功的提示.通过DB Browser工具到数据库中再去检查一下,发现Category表还是没有创建成功.
  • 创建不成功的原因主要是,BookStore.db数据库已经存在了,之后不管我们再怎样点击"Create Database"按钮,MyDatabaseHelper中的onCreate()方法都是不会再进行执行,因此新添加的表也就无法进行创建了.
  • 解决这个问题的办法也比较简单,只需要先将程序进行卸载,然后再进行重新运行,这时候BookStore.db数据库就已经不存在了,此时再点击按钮就可以进行onCreate()方法当中逻辑的执行.
  • 不过通过卸载一个程序来添加一张表比较不合理,这时候我们只需要巧妙运用onUpgrade()方法即可解决这个问题,修改MyDatabaseHelper中的代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qnX46Jkq-1670159871278)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AE%89%E5%8D%93/image-20221202161530662.png)]

  • 可以看到我们在onUpgrade()方法当中执行了两条drop语句,如果发现数据库中已经存在Book表和Category表,就将这两张表删除,然后调用onCreate()方法直接重新创建,这里先将已经存在的表删除了,是因为如果在创建表时发现这张表已经存在了,就会直接进行报错.
  • 接下来我们就需要想办法触发onUpgrade()方法进行执行,这个就只需要我们在创建MyDatabaseHelper实例的时候将传入的版本号进行升级即可
  • 之前创建数据库的时候传递的是1,现在只要传递一个比1大的数即可,表示我们想要对数据库进行升级

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a5Nb9jty-1670159871279)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AE%89%E5%8D%93/image-20221202221823747.png)]

添加数据

  • 使用SQLiteOpenHelper的getReadableDatabase()方法或者getWriteableDatabase()方法可以用于对数据库进行进行创建和升级,不仅如此这两个方法还会返回一个SQLiteDatabase对象,借助这个对象就可以对数据进行CRUD操作了.
  • SQLiteDatabase中提供了一个inset()方法,专门用于添加数据,他接收三个参数.
  • 第一个参数是表名
  • 第二个参数用于在未指定添加数据的情况下給某些可以为空的列自动赋值为NULL,一般我们用不到这个功能,直接传入null即可.
  • 第三个参数是ContentValues对象,它提供了一系列的put()方法重载,用于向ContentValues中添加数据,只需要将表中的每个列名以及相应的待添加数据传入即可.
  • 在activity_main.xml中编写一个添加数据的按钮

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xRub9z9O-1670159871280)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AE%89%E5%8D%93/image-20221202223239778.png)]

  • 在该按钮的点击事件当中添加逻辑
addData.setOnClickListener {
    
    
    val db = myDatabaseHelper.writableDatabase
    val values1 = ContentValues().apply {
    
    
        //开始组装第一条数据
        put("name", "The Da Vinci Code")
        put("author", "Dan Brown")
        put("pages", 454)
        put("price", 19.67)
    }
    //插入第一条数据
    db.insert("Book", null, values1)
    val values2 = ContentValues().apply {
    
    
        //开始组装第二条数据
        put("name", "The Lost Symbol")
        put("author", "Dan Brown")
        put("pages", 404)
        put("price", 10.67)
    }
    //插入第二条数据
    db.insert("Book", null, values2)
    val values3 = ContentValues().apply {
    
    
        //开始组装第三条数据
        put("category_name", "文学")
        put("category_code", 10)
    }
    db.insert("Category", null, values3)
}

更新数据的操作

  • SQLiteDatabase中提供了一个updata()方法,用于对数据进行更新,这个方法接收4个参数
  • 第一个参数是表明,用来指定修改那张表当中的数据
  • 第二个参数是ContentValues对象,要将更新的数据组装进去
  • 第三,四个参数用于约束更新某一行或者某几行中的数据,不指定的话默认会更新所有行
  • 在activity_main.xml中添加一个更新数据的按钮
<Button
    android:id="@+id/upDate"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="upDate" />
  • 在MainActivity中编写该按钮的逻辑
upDate.setOnClickListener {
    
    
    val db = myDatabaseHelper.writableDatabase
    val values = ContentValues()
    values.put("price", 10.01)
    db.update("Book", values, "name = ?", arrayOf("The Da Vinci Code"))
}

删除数据的操作

  • 同样SQLiteDatabase给我们提供了删除数据的方法delete(),该方法有三个参数
  • 第一个参数:表名
  • 第二个,三个参数用于约束某一行或者某几行的数据,不指定的话默认删除所有的数据
  • 在activity_main.xml当中添加一个删除数据的按钮
<Button
    android:id="@+id/deleteData"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="deleteData" />
  • 在MainActivity当中添加按钮对应的逻辑
deleteData.setOnClickListener {
    
    
    val db = myDatabaseHelper.writableDatabase
    db.delete("Book", "pages > ?", arrayOf("450"))
}

查询数据

  • SQLiteDatabase给我们提供了一个query()方法以便于我们对数据进行查询,这个方法的参数还是比较复杂的,最短的一个都需要7个参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GvO9kEoy-1670159871281)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AE%89%E5%8D%93/image-20221204200043567.png)]

  • 调用query()方法后会返回一个Cursor对象,查询到的所有数据都将从这个对象中取出.
  • 在activity_main.xml当中定义一个查询数据的按钮
<Button
    android:id="@+id/queryData"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="query Data" />
  • 在MainActivity当中编写该按钮的逻辑
queryData.setOnClickListener {
    
    
    val db = myDatabaseHelper.writableDatabase
    //查询所有的数据
    val cursor = db.query("Book", null, null, null, null, null, null)
    //将数据的指针移动到第一行的位置
    if (cursor.moveToFirst()) {
    
    
        do {
    
    
            //遍历Cursor对象,取出数据并打印
            //利用getColumnIndex()方法传入字段名获得该字段的下标,然后将这个下标传入对应的取值方法取出相应的值
            val name = cursor.getString(cursor.getColumnIndex("name"))
            val author = cursor.getString(cursor.getColumnIndex("author"))
            val pager = 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 name is $pager")
            Log.d("MainActivity", "book name is $price")
        } while (cursor.moveToNext())
    }
    cursor.close()
}

使用SQL操作数据库

  • 虽然安卓给我们提供了很多的API用于操作数据库,但是还给我们保留了原生SQL的方式进行数据库的操作
//添加数据
db.execSQL("insert into Book (name, author, pages, price) values (?, ?, ?, ?)", arrayOf("The Da Vinci Code", "Dan Brown", "444", "19.78"))
//更新数据
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("selete * from Book", null)

事务的使用

  • 现在我们想要将Book表当中的数据进行更新,需要将原先的数据全部删除,然后将新的数据插入进去,我们要保证插入新数据和删除旧数据都要执行成功,否则就保留原数据
  • 修改activity_main.xml当中的代码,添加一个替换数据的按钮
    <Button
        android:id="@+id/replaceDate"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="replace Date" />
  • 在MainActivity当中编写这个按钮的执行逻辑
replaceDate.setOnClickListener {
    
    
    //先获得一个SQLiteDatabase对象
    val db = myDatabaseHelper.writableDatabase
    //下面我们就要利用这个SQLiteDatabase对象来进行两个数据操作了,所以在进行数据操作之前我们就应该开启事务了
    db.beginTransaction() //开启事务
    try {
    
    
        db.delete("Book", null, null)
        //在这个地方为了演示事务的作用我们在这里手动抛出一个空指针异常
        if (true) {
    
    
            //手动抛出一个异常让事务执行失败
            throw NullPointerException()
        }
        val values = ContentValues().apply {
    
    
            put("name", " Game")
            put("author", "drl")
            put("pages", "10")
            put("price", 5)
        }
        db.insert("Book", null, values)
        db.setTransactionSuccessful()//事务执行成功
    } catch (e: Exception) {
    
    
        e.printStackTrace()
    } finally {
    
    
        db.endTransaction() //结束事务
    }
}

升级数据库的最佳写法

  • 之前添加一个表的时候,采用了删除当前所有的表,然后强制重新执行一遍onCreate()方法,这种方法每次添加一个表的时候,会使得旧表当中的数据全部不存在了,这样当项目上线之后,用户产出自己的数据,当我们的数据库进行更新,会造成用户的数据丢失,这样肯定是不行的.
  • 其实只需要一些合理的控制,就可以保证升级数据库的时候数据并不会丢失.
  • 我们只需要在onUpgrade()方法当中,为每一个版本号的数据库做对应的数据改动即可.
  • 比如我们第一版的数据库就是如下这样
class MyDatabaseHelper(val context: Context, val name: String, val version: Int) :
    SQLiteOpenHelper(context, name, null, version) {
    
    

    //创建Book表的SQL语句
    private val createBookSQL = "create table Book (" +
            "id integer primary key autoincrement," +
            "author text," +
            "price real," +
            "pages integer," +
            "name text)"

    override fun onCreate(db: SQLiteDatabase) {
    
    
        //执行创建Book表的SQL
        db.execSQL(createBookSQL)
    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
    
    
    }
}
  • 之后我们想要再添加一张Categroy表可以这样做
class MyDatabaseHelper(val context: Context, val name: String, val version: Int) :
    SQLiteOpenHelper(context, name, null, version) {
    
    

    //创建Book表的SQL语句
    private val createBookSQL = "create table Book (" +
            "id integer primary key autoincrement," +
            "author text," +
            "price real," +
            "pages integer," +
            "name text)"

    //创建Category表的SQL语句
    private val createCategorySQL = "create table Category (" +
            "id integer primary key autoincrement," +
            "category_name text," +
            "category_code integer)"

    override fun onCreate(db: SQLiteDatabase) {
    
    
        //执行创建Book表的SQL
        db.execSQL(createBookSQL)
        //创建Category表的SQL
        db.execSQL(createCategorySQL)
    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
    
    
        //当我们旧数据库的版本是<=1的时候,我们再做数据覆盖的时候只执行createCategorySQL
        if(oldVersion <= 1) {
    
    
            db.execSQL(createCategorySQL)
        }
    }
}
  • 接下来我们想要创建一个字段将两张表进行关联,比如再Book表当中创建一个category_id字段
class MyDatabaseHelper(val context: Context, val name: String, val version: Int) :
    SQLiteOpenHelper(context, name, null, version) {
    
    

    //创建Book表的SQL语句
    private val createBookSQL = "create table Book (" +
            "id integer primary key autoincrement," +
            "author text," +
            "price real," +
            "pages integer," +
            "name text," + 
            "category_id integer)"

    //创建Category表的SQL语句
    private val createCategorySQL = "create table Category (" +
            "id integer primary key autoincrement," +
            "category_name text," +
            "category_code integer)"

    override fun onCreate(db: SQLiteDatabase) {
    
    
        //执行创建Book表的SQL
        db.execSQL(createBookSQL)
        //创建Category表的SQL
        db.execSQL(createCategorySQL)
    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
    
    
        //当我们旧数据库的版本是<=1的时候,我们再做数据覆盖的时候只执行createCategorySQL
        if(oldVersion <= 1) {
    
    
            db.execSQL(createCategorySQL)
        }
        if(oldVersion <= 2) {
    
    
            db.execSQL("alter table Book add column category_id integer")
        }
    }
}
  • 在这里需要注意一个细节就是,每当我们升级一个版本的时候,onUpgrade()方法里都一定要写一个if条件语句,保证App在跨版本升级的时候,每一次的数据库修改都能被全部执行,比如当用户从第二版升级到第三版的时候,那么之后第二条if语句才会执行,而如果用户从第一版直接升级到第三版的时候两条判断语句就都会执行.不管怎样升级都能保证数据全部被更新且用户的数据不会被丢失.

猜你喜欢

转载自blog.csdn.net/weixin_45809829/article/details/128177677