移动开发笔记 (九) 数据储存 Kotlin高阶课堂

1.文件储存

1.1将手机储存在文件中
例:在数据回收前,储存数据
context openFileOutput()方法 文件操作模式主要有**MODE_PRIVATE**和**MODE_APPEND**

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

    }

    override fun onDestroy() {
        super.onDestroy()
        val input=editText.text.toString()
        save(input)
    }

    private fun save(inputText : String){
        try {
            val output=openFileOutput("data",Context.MODE_PRIVATE)
            val write =BufferedWriter(OutputStreamWriter(output))
            write.use {
                it.write(inputText)
            }
        }catch (e:Exception){
            e.printStackTrace()
        }
    }
}

注意这里使用了一个use函数,这是Kotlin提供的一个内置扩展函数,他会保证在Lambda表达式中的代码全部执行完之后自动将外层的流关闭。不用再写一个finally
使用Android studio右下角Device File Explorer工具可以查看/data/data/com.example.filepersistencetest/files/目录下已经生成一个data文件
1.2从文件中读取数据

  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,"读取缓存成功",Toast.LENGTH_SHORT).show()
        }
    }   

 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: java.lang.Exception){
            e.printStackTrace()
        }
        return content.toString()
    }

这里读取数据使用了一个forEachLine函数,这是Kotlin提供的一个内置扩展函数
调用EditText setSelection()方法将广播移动到文本末尾以便于继续输入

2.SharedPreferences

2.1储存数据
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","Brrils")
            editor.putInt("age",21)
            editor.putBoolean("married",false)
            editor.apply()
        }
    }

在data/data/com.example.sharedpreferencestest/shared_prefs/下生成一个data.xml文件
2.2读取数据

 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)
            Toast.makeText(this,"${name}已经${age}岁了",Toast.LENGTH_SHORT).show()
        }

2.3登陆保存密码实战
activity_main.xml文件内

<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">
<EditText
    android:id="@+id/accountid"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="账号"
    ></EditText>
    <EditText
        android:id="@+id/passwordid"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
        android:hint="密码"
        ></EditText>
<CheckBox
    android:id="@+id/rememberPass"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"></CheckBox>
    <Button
        android:id="@+id/login"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="登陆"></Button>
</LinearLayout>

MainActivity中

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //
        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","")
            accountid.setText(account)
            passwordid.setText(password)
            rememberPass.isChecked=true

        }
        //点击登陆按钮
        login.setOnClickListener {
            val account =accountid.text.toString()
            val password=passwordid.text.toString()
            val edit=prefs.edit()
            if (account=="123456"&&password=="123456"){
                if(rememberPass.isChecked){
                    edit.putBoolean("remember_password",true)
                    edit.putString("account",account)
                    edit.putString("password",password)
                }
                Toast.makeText(this,"登陆成功,欢迎回来!",Toast.LENGTH_SHORT).show()
            }else{
                edit.clear()
                Toast.makeText(this,"登陆失败,请检查账号密码!",Toast.LENGTH_SHORT).show()
            }
            edit.apply()

        }
    }
}

3.SQLite数据库储存

3.1创建数据库
Android为了更方便的管理数据库,专门提供了一个SQLiteOpenHelper帮助类
SQLiteHelper接收4个参数1.Context,2.数据库名,3.允许我们在查询数据时返回一个自定义的Cursor,一般传入null,4.表示当前数据库版本号,可用于对数据库升级
数据库文件会存放在 data/data/< packagename >/databases/目录下

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

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

    override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
        TODO("Not yet implemented")
    }
}

integer表示整型
real表示浮点型
text表示文本类型
blob表示二进制类型

修改MainActivity中的代码

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

3.2升级数据库
在MyDatabaseHelper中添加几行代码

class MyDatabaseHelper(val context: Context,name: String,verson:Int) : SQLiteOpenHelper(context,name,null,verson){
    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)
        db?.let{
            it.execSQL(createBook)
            it.execSQL(createCategory)
        }
        Toast.makeText(context,"Create succeeded",Toast.LENGTH_SHORT).show()
    }
//升级功能
    override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
        db?.let {
        //如果存在Book和Category表,就将两个表删除掉
            it.execSQL("drop table if exists Book")
            it.execSQL("drop table if exists Category")

        }
    onCreate(db)

    }
}

再修改MainActivity中的版本参数

class MainActivity : AppCompatActivity() {

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

3.3插入数据
添加新按钮,在MainActivity中添加代码

...
addData.setOnClickListener {
            val db=dbHelper.writableDatabase
            val value1=ContentValues().apply {
                put("name","The Da Vinci Code")
                put("author","Dan Brown")
                put("pages", 454)

            }
            db.insert("Book",null,value1)
            val value2=ContentValues().apply {
                put("name","The Lost Symbol")
                put("author","Dan Brown")
                put("pages", 510)

            }
            db.insert("Book",null,value2)
        }
        ...

3.4更新数据
添加新按钮,在MainActivity中添加代码

...
updateData.setOnClickListener {
            val db=dbHelper.writableDatabase
            val values=ContentValues()
            values.put("pages",455)
            db.update("Book",values,"name=?", arrayOf("The Da Vinci Code"))
        }
...

3.5删除数据

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

3.6查询数据

...
    queryData.setOnClickListener {
            val db=dbHelper.writableDatabase
            //查询Book表中所有数据
            val cursor = db.query("Book",null,null,null,null,null,null,null)
       if(cursor.moveToFirst()){
           do{
               val name =cursor.getString(cursor.getColumnIndex("name"))
               val author=cursor.getString(cursor.getColumnIndex("author"))
               val pages=cursor.getInt(cursor.getColumnIndex("pages"))
               Log.d("MainActivity","书名: $name 作者:$author 有 $pages 页")
           }while (cursor.moveToNext())
       }
            cursor.close()
        }
...

调用moveToFirst()将数据指针移动到第一行位置
query()方法参数
1.table | fromtable_name | 指定查询的表名
2.columns | select column1,column2 指定查询的列名
3.selection | where column=value | 指定where的约束条件
4.selectionArgs | - | 为where中的占位符提供具体的值
5.groupBy | group by column | 指定需要group by的列
**6.having | having column = value | 对group by后的结果进一步约束 **
7.orderBy | order by column1,colum2 | 指定查询结果的排序方式

3.7使用SQL操作数据库
添加数据

db.execSQL("insert into Book(name , author , pages , price) values(?,?,?,?)",arrayOf("A","aa","66","16.96"))

更新数据

db.execSQL("update Book set price=? where name = ? ",arrayOf("10.99,"The Da)")

删除数据

db.execSQL("delete from Book where pages > ?" , arrayOf("500"))

查询数据

val cursor = db.rawQuery("select * from Book",null)

3.8使用事务

...
 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)
                  }
                db.insert("Book",null,values)
                db.setTransactionSuccessful()//事务已经执行成功
            }catch (e : Exception){
                e.printStackTrace()
            }finally {
                db.endTransaction() //结束事务
            }

        }
...

3.9升级数据库的最佳写法
每个版本的数据库都会对应一个版本号,当指定的数据库版本号大于当前数据库版本号的时候,就会进入onUpgrade()方法。
修改MyDatabaseHelper

class MyDatabaseHelper(val context: Context,name: String,verson:Int) : SQLiteOpenHelper(context,name,null,verson){
    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)
        db?.let{
            it.execSQL(createBook)
            it.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")
        }
    onCreate(db)

    }
}

我们在Book表的建表语句中添加了一个category_id列,如果覆盖安装,就会进入升级数据库的操作中,在onUpgrade()方法中。

4.Kotlin高阶课堂

4.1简化SharedPreferences
用高阶函数简化SharedPreferences的用法

fun sharedPreferences.open(block : SharedPreferences.Editor.() -> Unit){
	val editor = edit()
	editor.block()
	editor.apply()
}

定义好了open函数以后,我们可以在项目中使用SharedPreferences储存数据

getSharedPreferences("data",Context.MODE_PRIVE).open{
putString("name","Tom")
putInt("age",18)
putBoolean("married",false)
}

在Google提供的KTX扩展库中已经包含了上述SharePreferences方法,这个扩展库会在Android Studio创建项目的时候自动引入build.gradle的dependencies中
因此我们可以直接在项目中使用如下方法向sharedPreferences储存数据

getSharedPreferences("data",Context.MODE_PRIVE).edit{
putString("name","Tom")
putInt("age",18)
putBoolean("married",false)
}

4.2简化ContentValues的用法
新建一个ContentValues.kt文件,然后定义一个cvOf()方法

fun cvOf(vararg pairs : Pair<String , Any?>) : ContentValues{
	val cv = ContentValues()
	for(par in pairs){
		val key = pair.first
		val value = pair.second
		when (value){
			is Int -> cv.put(key,value)
			is Long -> cv.put(key,value)
			is Short -> cv.put(key,value)
			is Float -> cv.put(key,value)
			is Double -> cv.put(key,value)
			is Boolean -> cv.put(key,value)
			is String -> cv.put(key,value)
			is Byte -> cv.put(key,value)
			is ByteArray -> cv.put(key,value)
			null -> cv.putNULL(key)
		}
	}
}

首先cvOf()方法接收了一个Pair参数,也就是使用A to B语法结构创造出来的参数类型。在参数前面加一个vararg关键字,vararg关键字对应的就是java中可变参数列表。
ContentValues的值可以有多种类型,所以我们需要将pair值的泛型指定成Any?,因为Any是Kotlin中所有类的共同基类,相当于Java的Object,而Any?则表示可以传入空值
结合高阶函数进一步优化

fun cvOf(vararg pairs : Pair<String , Any?>)  = ContentValues().apply{
	for(par in pairs){
		val key = pair.first
		val value = pair.second
		when (value){
			is Int -> cv.put(key,value)
			is Long -> cv.put(key,value)
			is Short -> cv.put(key,value)
			is Float -> cv.put(key,value)
			is Double -> cv.put(key,value)
			is Boolean -> cv.put(key,value)
			is String -> cv.put(key,value)
			is Byte -> cv.put(key,value)
			is ByteArray -> cv.put(key,value)
			null -> cv.putNULL(key)
		}
	}
}

有了这个cvOf()方法之后,我们可以这样使用

val values = cvOf("name" to "Game of Thrones" , "author" to "George Martin" , "pages" to 720,"price" to 20.85)

KTX库中也提供了一个具有同样功能的方法

val values = contentValuesOf("name" to "Game of Thrones" , "author" to "George Martin" , "pages" to 720,"price" to 20.85)
db.insert("Book",null,values)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章