数据存储

数据存储全方案

2021.2.22

Gary哥哥的哥哥的哥哥

详解持久化操作

持久化技术简介

数据持久化就是指那些内存中的瞬时数据保存到存储设备上,保证即使设备开机关机,这些数据仍然不会丢失

Android主要提供了三种方式用于简单实现数据持久化功能:

  • 文件存储
  • SharedPreference存储
  • 数据库存储

下面对上面的三种方式一一展开讲解

文件存储

写入数据

Android中最基本的数据存储方式,他不对存储内容进行任何格式化的处理

  • 比较适合存储一些简单的文本数据或二进制数据
  • 如果你想保存一些较为复杂的结构化数据,就需要定义一套自己的格式规范了,方便之后数据从文件中重新解析出来

Context类中提供一个openFileOutput()方法,可以用于将数据存储到指定的文件中,两个参数

  • p1为文件名
    • 文件创建时候使用,注意,不带路径的,有一个默认存储的位置,/data/data/com.workaholiclab.savefile/files/data
      • 这里要打开Android studio右下角的Device File Explorer这个工具(如果没找到就ctrl+Shift+A 输入进去)
  • p2为文件的操作模式
    • MODE_PRIVATE,覆盖原文件内容
    • MODE_APPEND,加载源文件后面
    • (默认是第一个)
  • 返回的是一个FileOutputStream对象,得到这个对象后,可以使用Java流的方式将数据写入文件中
  • 注意两种模式,如果文件不存在的话,都会自动创建!!!

下面是简单的代码示例:

    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()
        }
    }

前面的操作和Java很类似

这里还用到了一个use函数,是一个Kotlin提供的一个内置扩展函数

它会保证Lambda表达式中的代码全部执行完之后自动将外层的流关闭,这样就不惜要手动写一个finnally语句手动关闭流

下面修改一下activity_main.xml的代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

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

</LinearLayout>
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)
    }

    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()
        }
    }
}

从文件中读取数据

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()
    }
}

注意下面这条语句:

editText.setSelection(inputText.length)

将输入光标移动到文本的末尾位置便于继续输入

SharedPreferences 存储

保存数据

  • 是一种键值对的方式来存储数据
  • 支持多种不同的数据结构
  • 存储数据和读取的数据是同一类型
  • 数据持久化比使用文件方便很多

想要存储数据,就必须先获得SharedPreferences对象,下面提供两种方法获得对象

  • Context类中的getSharedPreferences方法
    • p1文件的名称,指定目录在:/data/data/com.workaholiclab.savefile/shared_prefs目录下
    • p2指定模式,其实目前只有一种模式科学:MODE_PRIVATE,他和直接传入0的效果是一致的
      • 表示只有当前应用程序可以对这个SharedPreferences文件进行读写
  • Activity类中的getSharedPreferences方法
    • 只接受一个操作模式的参数
    • 自动将类名作为文件名

得到对象过后,开始想SharedPreferences文件进行存储数据,主要分三步实现:

  • 调用SharedPreferences对象的edit方法获取SharedPreferences.Editor对象
  • 向上面获得的对象添加数据,比如一个整型的数据就使用putInt()
  • 调用apply的方法将添加的数据提交

下面用代码演示一下:

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

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

</LinearLayout>
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","Wendy")
            editor.putInt("age",20)
            editor.putBoolean("married",false)
            editor.apply()
        }
    }
}

看到数据文件是以xml方式保存的

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

读取数据

操作很类似

<?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:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Save Data"
        android:textAllCaps="false"
        android:id="@+id/saveButton"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Restore Data"
        android:id="@+id/restoreButton"/>

</LinearLayout>
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","Wendy")
            editor.putInt("age",20)
            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)
            println("$name $age $married")
        }
    }
}

get***的第二个参数数默认值,找不到对应的key就当成默认值

记住密码功能实现

利用SharedPreferences存储可以实现简单的记住密码的功能:

  • 下面通过上一章节的登录页面修改来实现功能:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".LoginActivity"
    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:textSize="18sp"
            android:text="Account:"/>
        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_gravity="center_vertical"
            android:id="@+id/accountEdit"/>
    </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:textSize="18sp"
            android:text="Password:"/>
        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_gravity="center_vertical"
            android:id="@+id/passwordEdit"
            android:inputType="textPassword"/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/rememberPass"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Login"
            android:textAllCaps="false"/>
    </LinearLayout>
    <Button
        android:layout_width="200dp"
        android:layout_height="60dp"
        android:layout_gravity="center_horizontal"
        android:text="Login"
        android:id="@+id/login"
        android:textAllCaps="false"/>

</LinearLayout>

class LoginActivity : AppCompatActivity() {
    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()
            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()
            }
        }
    }
}

SQLite 数据库存储

Android是内置了数据库的

SQLite是一款轻量级的关系型数据库,迅速按书读非常快,占用资源很少

  • 不仅支持SQL语法
  • 遵循数据库的ACID事物

应用:

  • 存储大量关系复杂的数据型数据

这里以后有空再把全部的示例讲解代码补上,现在先看看书吧

一系列的增删改查(CRUD)操作

我这里知识简单演示一下代码

  • 具体的函数用法见书本p288开始
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()
    }

    //升级数据库,比如像加多一张表
    override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
        db?.execSQL("drop table if exists Book")
        db?.execSQL("drop table if exists Category")
        onCreate(db)
    }
}
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
        }
        //添加数据
        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",19.95)
            }
            db.insert("Book",null,values1)//插入第一条数据
            val values2=ContentValues().apply {
                //开始组装第一条数据
                put("name","First Code")
                put("author","guolin")
                put("pages",692)
                put("price",99.00)
            }
            db.insert("Book",null,values2)//插入第二条数据

            Toast.makeText(this,"成功添加数据",Toast.LENGTH_SHORT).show()
        }

        //更新数据
        updateData.setOnClickListener {
            val db=dbHelper.writableDatabase
            val values=ContentValues()
            values.put("price",10.99)
            db.update("Book",values,"name = ?", arrayOf("The Da Vinvi Code"))
            Toast.makeText(this,"更新数据成功",Toast.LENGTH_SHORT).show()
        }

        //删除数据
        deleteData.setOnClickListener {
            val db=dbHelper.writableDatabase
            db.delete("Book","pages > ?", arrayOf("500"))
            Toast.makeText(this,"删除数据成功",Toast.LENGTH_SHORT).show()
        }

        //查询数据
        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"))
                    println("$name $author $pages $price")
                }while (cursor.moveToNext())
            }
            cursor.close()
            Toast.makeText(this,"查询数据成功",Toast.LENGTH_SHORT).show()
        }

        //使用事务
        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:IOException){
                e.printStackTrace()
            }finally {
                db.endTransaction()//结束事务
            }
        }
    }
}

SQLite的最佳实现

SQLite数据库是支持事务的,事务的特性可以保证让一些列操作要么全部完成,要么一个都不会完成。

  • 如,银行转账双方
 //使用事务
        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:IOException){
                e.printStackTrace()
            }finally {
                db.endTransaction()//结束事务
            }
        }

升级数据库的最佳写法

在升级数据库中,在onUpgrade方法调用onCreate方法是非常粗暴的,在开发时候可以使用,但产品上线之后千万不要使用

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,"+
            "category_id integer)"
    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()
    }

    //升级数据库,比如像加多一张表
    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")
        }
    }
    
}

MainActivity中版本记得也修改为3哦

加多一个属性:

db?.execSQL("alter table Book add column category_id integer")

数据库博客完更

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值