Android实现永久存储数据的三种方式(Kotlin实现)
1.通过文件的方式
1.1 适用范围
一些简单的文本数据
1.2 简单实例
作用:实现文本框可以在重新启动之后仍然保留上一次推出时候的数据
1.2.1 布局代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/editText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:hint="请输入"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
其中较难懂的参数为ems 表示为控件的字符串长度,超出部分将不再进行显示
1.2.2 Activity代码
package com.example.csdnsqlite
import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.android.synthetic.main.activity_main.*
import java.io.*
import java.lang.StringBuilder
import kotlin.math.log
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val text=load()
editText.setText(text)
editText.setSelection(text.length)
}
override fun onDestroy() {
//在每次活动结束时候 将文本进行存储
super.onDestroy()
val text=editText.text.toString()
save(text)
}
private fun save(Text:String){
try {
val output=openFileOutput("data", Context.MODE_PRIVATE)
val writer=BufferedWriter(OutputStreamWriter(output))
// writer.use { // use当写入完成之后,可以自动关闭流,不用再手动关闭
// it.write(Text)
// }
writer.write(Text) //这种方法需要自己手动关闭 注意关闭的顺序与开启的顺序刚好相反
writer.close()
output.close()
}catch (e:IOException){
e.printStackTrace()
}
}
private fun load():String{
val text=StringBuilder()
try {
val input=openFileInput("data")
val reader=BufferedReader(InputStreamReader(input))
// reader.use { //无需手动关闭
// reader.forEachLine {
// text.append(it)
// }
// }
reader.forEachLine { //与上面相同 这种方式需要手动进行关闭
text.append(it)
}
reader.close()
input.close()
}catch (e:IOException){
e.printStackTrace()
}
return text.toString()
}
}
首先获得文件的输出流,通过Context中的openFileOutput函数
openFileOutput接受两个参数
第一个:文件名称
也就是你存储的那个文件叫什么名字
第二个:关于写入方式的参数,有两个
MODE_PRIVATE表示若出现同名文件,则会进行覆盖;MODE_APPEND表示若出现同名文件,则继续往里面添加
之后根据获取的FileoutputStream对象,通过java流的方式写入文件
获取一个BufferedWriter对象,可以通过这个对象对文件进行写入
方法:BufferedWriter(OutputStreamWriter(刚获得的FileoutputStream))
要获得文件的输入流,通过刚刚类似的openFileInput
openFileOutput接受一个参数:
文件名称,就是你刚刚存储的那个文件的名称,至于为什么不需要路径,那是因为android这些文件都有固定的路径,只需要文件名称就可以唯一确定文件
根据获取的FileinputStream对象,可以对文件内容进行读取
方法:BufferedReader(InputStreamReader(刚获得的FileinputStream))
运行时:
2.通过sharedpreferences来进行存储
2.1适用范围
这种方式是通过键值对的方式来进行存储,常见的使用情况,有存储用户的偏好设置等情况
2.2 简单实例
一个可输入文本框,下面两个按钮,一个用来存储数据,一个用来读取数据
2.2.1 布局代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/editText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:ems="10"
android:hint="请输入"
android:inputType="textPersonName"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/buttonSave"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="save"
app:layout_constraintEnd_toStartOf="@+id/buttonLoad"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/buttonLoad" />
<Button
android:id="@+id/buttonLoad"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Load"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/buttonSave"
app:layout_constraintTop_toBottomOf="@+id/editText" />
</androidx.constraintlayout.widget.ConstraintLayout>
该布局主要是通过constraintlayout来完成的,其实大部分在图形化界面中完成的。。。。这种代码说实话有点难看懂,总之有一个文本输入框和两个按钮就可了。
2.2.2 Activity代码
package com.example.csdnsharedpreferences
import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
buttonSave.setOnClickListener {
val text=editText.text.toString()
val editor=getSharedPreferences("data",0).edit()
editor.putString("text",text)
editor.apply()
}
buttonLoad.setOnClickListener {
val prefs=getSharedPreferences("data", Context.MODE_PRIVATE)
val text=prefs.getString("text","")
editText.setText(text)
if (text != null) {
editText.setSelection(text.length)
}
}
}
}
写入:
-
通过getSharedPreferences的edit获取一个SharedPreferences.Editor!对象
getSharedPreferences接受两个参数:
第一个: 文件名称
也就是你存储的那个文件叫什么,这种方式是通过xml的文件格式来进行存储的
第二个:操作模式
目前只有Context.MODE_PRIVATE也就是0,其余的几种模式在Android发展过程中都被废弃了。。。 -
之后通过putString,putInt等一系列put方法往里面添加数据
添加格式为 key:value格式 ,两个参数一个为键,一个为值 -
添加完之后,一定要进行apply进行保存更改
读取:
-
通过getSharedPreferences获取一个SharedPreferences对象,参数与写入时候相同
-
根据获得的对象 调用getString,getInt等函数,获取所需要的数据
参数一般为两个:
第一个:键 第二个:没有找见的时候的默认值
2.2.3 运行效果
3.通过SQLite数据库来进行存储
3.1使用情况
在面对一些复杂的数据的时候,前面两种方式就显得比较老土了,肯定无法胜任,这就需要数据库出马了,android本身是内置数据库的,短小而精悍的SQLite数据库成为了不二之选。
3.2简单示例
3.2.1讲解
android为了方便进行数据库的管理,提供了一个名为SQLiteOpenHelper的类,来帮助我们,不过不能直接用,这个类是个抽象类,当我们想使用的时候,必须用自己的类去继承这个类,主要实现两个抽象的方法:onCreate和onUpdate,看看名字也能猜出来,一个是创建的时候进行调用,另一个是当数据库升级的时候进行调用。
SQLiteOpenHelper可以通过getReadableDatabase和getWritableDatabase来打开的数据库,不存在则创建,区别在于前一种方法当数据库无法写入的时候,例如存储空间满了的时候,会以只读的方式打开,而后者则会报错。
就不写布局文件了,就是一些简单的按钮,直接看Activity等文件吧
3.2.2 创建数据库
创建一个数据库,用于记录学生的信息,简单起见,就姓名,年龄,性别,学号吧。
如果学过SQL,创建表的话,应该这么写
CREATE TABLE Student(
id integer primary key autoincrement,
name text ,
age integer,
sex boolean,
stuNo text)
新建一个文件StudentDatabaseHelper来完成创建表的操作
package com.example.csdndatabase
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.widget.Toast
class StudentDatabaseHelper(val context: Context,name :String,version :Int):
SQLiteOpenHelper(context,name,null,version) {
private val createTable= "CREATE TABLE Student(\n" +
"id integer primary key autoincrement,\n" +
"name text ,\n" +
"age integer,\n" +
"sex boolean,\n" +
"stuNo text)"
override fun onCreate(db: SQLiteDatabase?) {
db?.execSQL(createTable)
Toast.makeText(context,"create",Toast.LENGTH_SHORT).show()
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
}
}
SQLiteOpenHelper接受四个参数
1.上下文对象,为了保证可以对数据库进行操作
2.数据库的名称
3.cursor一般情况下为null,查询时候会返回这么一个对象,里面有我们所查询到的信息
4.数据库的版本,为整型
MainActivity代码
package com.example.csdndatabase
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val dbHelper=StudentDatabaseHelper(this,"StuData.db",1)
buttonCreateDatabase.setOnClickListener {
dbHelper.writableDatabase //实际上调用了getWritableDatabase
}
}
}
再调用getWritableDatabase,因为没有创建数据库,会在调用时,自动创建数据库。
运行时:
可以看到,第一次点击的时候创建了数据库,之后再点击,没有响应,那是因为只有第一次才会调用oncreate函数,之后发现已经创建成功了,就不会再进行调用了。
怎么确定创建了数据库呢?
点击右下角的Device Explorer(根据版本的不同,你的可能不在那里,但是稍微找找,,应该很好找见)
之后按以下步骤就可以找见数据库了,在com.example.你的项目名称处进行寻找。里面有一个你创建的db文件,另一个则是生成的日志文件。
想看一下数据库里面的内容?
需下载DB Browser
选择File–Settings–Plugins 输入DB Browser 就可以下载安装了
找不见这个工具的话,ctrl+shift+A 输入DB Browser进行搜索
笔者用着这小程序有点问题 又下载了个sqlitebrowser
认识中国字 稍微看看 应该就会用了
打开后的内容如下:
数据库的基本操作是增删查改~ 接下来 对表进行插入
插入操作:
更改一下布局,在下面再增加一个按钮,用于添加数据
对MainActivity进行更改,在oncreate中添加如下代码:
buttonAddData.setOnClickListener {
val db=dbHelper.writableDatabase
val values=ContentValues().apply {
put("name","小明")
put("age",18)
put("sex",true)
put("stuNo","123456")
}
db.insert("Student",null,values) //插入数据
}
首先获取数据库对象,之后创建一个值,不用传入id,因为id会自动创建
最后进行插入
查看数据库内容,可以看出添加了几条数据:
更新操作:
继续增加按钮
MainActivity增加的代码如下:
buttonUpdate.setOnClickListener {
val db=dbHelper.writableDatabase
val values=ContentValues()
values.put("age",22)
db.update("Student",values,"name=?", arrayOf("小明"))
}
该代码将名字等于小明的年龄都改为了22
较难理解的是update的参数,共有四个:
第一个:
要进行改的表名
第二个:
你自己创建的ContentValues,其中包含要更改的信息
第三和第四个:
共同构成筛选条件,确定哪些行需要进行更改
更改后的数据如下:
删除操作:
例如需要删除id=3的那行
先在布局中继续添加按钮
在MainActivity中添加代码:
buttonDelete.setOnClickListener {
val db=dbHelper.writableDatabase
db.delete("Student","id==?", arrayOf("3"))
}
delete 第一个参数为表名,后两个决定删除的条件
删除数据后的结果如下:
查找操作:
查找操作较为复杂,通过query函数进行实现,其中包括七个参数
参数 | 对应的SQL语句 |
---|---|
table | from table_name |
columns | select column1,column2 |
selection | where column=value |
selectionArgs | 就是上面的value |
groupBy | group by column |
having | having column=value |
orderBy | order by column1,column2 |
举一个较为简单的例子,查询所有数据,查询数据的时候,要将查询到的数据放在一个cursor中,之后进行读取。
现在界面中再增加一个按钮,MainActivity增加如下的代码:
buttonSelect.setOnClickListener {
val db=dbHelper.writableDatabase
val cursor=db.query("Student",null,null,
null,null,null,null)
if (cursor.moveToFirst()){
do {
val name=cursor.getString(cursor.getColumnIndex("name"))
val age=cursor.getInt(cursor.getColumnIndex("age"))
val StuNo=cursor.getString(cursor.getColumnIndex("stuNo"))
Log.d("MainActivity","$name is $age years old ,StuNo is $StuNo")
}while (cursor.moveToNext())
}
}
一共有三条数据,可以看到打印的结果如下: