移動開發筆記 (九) 數據儲存 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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章