数据持久化方法之文件存储、SharedPreferences存储,及SQLite数据库存储
什么是数据持久化?就是将内存中的数据保存到存储设备中,保证在软件结束运行、手机关机重启等操作后,程序依然能够载入之前加载的数据。比如腾讯QQ的历史登录账号的保存、登录时的记住密码,以及微信朋友圈中曾经浏览过的图片,在手机断网、重启后再次打开朋友圈,依然能看到之前朋友发过的照片。上述情形均是数据持久化的体现,都属于数据持久化的范畴。
Android系统的数据持久化主要提供了3种方式:文件存储 、SharedPreferences存储 ,以及数据库存储 。
本文主要总结了文件存储的简单读写操作、SharedPreferences存储的基本流程,以及Android内置的数据库存储方案之一——SQLite的增删改查等基本操作。
一、文件存储
文件的写入依靠Context类
提供的openFileOutput()
方法,该方法返回一个FileOutputStream对象
,此时便可通过Java流的方式进行文件写入。
示例代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void save ( String str , boolean isAppend ) {
//save to file
if ( str == null ) {
return ;
}
FileOutputStream outputStream = null ;
BufferedWriter writer = null ;
try {
outputStream = openFileOutput ( "data" , isAppend ? Context . MODE_APPEND : Context . MODE_PRIVATE );
writer = new BufferedWriter ( new OutputStreamWriter ( outputStream ));
writer . write ( str );
} catch ( IOException e ) {
e . printStackTrace ();
} finally {
try {
if ( writer != null ) {
writer . close ();
// Toast.makeText(this, "Saved", Toast.LENGTH_SHORT).show();
}
} catch ( IOException e ) {
e . printStackTrace ();
}
}
}
其中,openFileOutput()
方法提供两个参数,第一个参数是写入的文件名(文件不存在将自动创建),第二个参数是写入模式,MODE_APPEND
代表追加写入文件,MODE_PRIVATE
表示覆盖写入文件。
类似的,Context类
还提供了一个openFileInput()
方法,用于从文件读入数据,不过该方法仅需一个参数用于传递文件名,相对openFileOutput()
方法更为简洁。该方法返回一个FileInputStream对象
,通过Java流的方式进行文件读入。
示例代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private String read ( String path ) {
FileInputStream in = null ;
BufferedReader reader = null ;
StringBuilder content = new StringBuilder ();
try {
in = openFileInput ( path );
reader = new BufferedReader ( new InputStreamReader ( in ));
String line = "" ;
while (( line = reader . readLine ()) != null ) {
content . append ( line );
}
} catch ( IOException e ) {
e . printStackTrace ();
} finally {
try {
if ( reader != null ) {
reader . close ();
}
} catch ( IOException e ) {
e . printStackTrace ();
}
}
return content . toString ();
}
二、SharedPreferences存储
SharePreferences存储是使用键值对来存储数据的,类似于yaml
的序列化格式,不过SharedPreferences存储的是.xml
文件。此外,SharedPreferences能够支持多种数据类型,可以存储和读写整型、浮点型、字符串等数据。
Android提供了3中方法来获取SharedPreferences对象:
Context类
提供的getSharedPreferences()
方法
Activity类
提供的getPreferences()
方法
PreferenceManager类
提供的getDefaultSharedPreferences()
方法
数据的写入也主要是分三步完成:
调用SharedPreferences对象
的edit()
方法来获取一个SharedPreferences.Editor对象
;
向SharedPreferences.Editor对象
中添加数据,比如添加一个布尔型数据,则调用putBoolean()
方法;
调用apply()
方法提交数据,以完成数据存储。
示例代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private SharedPreferences pref ;
private SharedPreferences . Editor editor ;
@Override
protected void onCreate ( Bundle savedInstanceState ) {
···
···
btnLogin . setOnClickListener ( v -> {
pref = PreferenceManager . getDefaultSharedPreferences ( this );
editor = pref . edit ();
editor . putBoolean ( "remember" , true );
editor . putString ( "account" , account );
editor . putString ( "password" , password );
editor . apply ();
};
}
存储的.xml
文件如下:
.xml文件
类似地,读取数据只需使用getString()
、getBoolean()
等方法即可。
三、SQLite数据库存储(CRUD操作)
Android系统内置了一套轻量级的关系型数据库——SQLite
,它不仅支持标准的SQL语法,还遵循数据库的ACID事务 。SharedPreferences存储尽管使用方便,但它仅限于简单的场景,一旦处理到复杂、庞大的数据,它就会有些捉襟见肘了。
3.1 Create创建数据库
Android提供了一个SQLiteOpenHelper帮助类
,这个类能够简化我们对数据库的操作流程,但它是一个抽象类 ,这意味着我们在使用时必须对它的某些方法进行重写。它有两个抽象方法:onCreate()
和onUpdate()
,分别用于创建数据库和更新数据库。
在这里笔者新建一个类MyDatabaseHelper
,使其继承于SQLiteOpenHelper类
,并定义好创建一个图书的SQL语句,并在onCreate()
方法中执行它并提交事务:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class MyDatabaseHelper extends SQLiteOpenHelper {
public static final String CREATE_BOOK = "create table Book ("
+ "id integer primary key autoincrement, "
+ "author text, "
+ "price real, "
+ "pages integer, "
+ "name text)" ;
private Context mContext ;
public MyDatabaseHelper ( Context context , String name , SQLiteDatabase . CursorFactory factory , int verison ) {
super ( context , name , factory , verison );
mContext = context ;
}
@Override
public void onCreate ( SQLiteDatabase db ) {
db . execSQL ( CREATE_BOOK );
Toast . makeText ( mContext , "Book table created" , Toast . LENGTH_SHORT ). show ();
}
@Override
public void onUpgrade ( SQLiteDatabase db , int oldVersion , int newVersion ) {
}
}
SQL语句中的“autoincrement”
表示该属性值依次递增。构造函数中的参数SQLiteDatabase.CursorFactory factory
为查询数据库时返回的自定义光标位置,一般置空即可。int verison
为当前数据库版本号,在数据库更新时起作用。
在活动的onCreate()
方法中构造一个MyDatabaseHelper对象
,传入数据库名“BookStore
”,并指定版本号为1
。
1
2
3
4
5
MyDatabaseHelper dbHelper = new MyDatabaseHelper ( context , "BookStor.db" , null , 1 );
btn_createBookTable . setOnClickListener ( v -> {
dbHelper . getWritableDatabase ();
});
通过外部使用sqlite3命令查看数据库发现,按钮点击一次后,再次点击按钮便不会再重复创建数据库了,可以发现数据库已存在时无法覆盖旧数据库。
3.2 Update更新数据库
此时添加一张CREATE_CATEGORY
表,以记录书的类别:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class MyDatabaseHelper extends SQLiteOpenHelper {
public static final String CREATE_BOOK = "create table Book ("
+ "id integer primary key autoincrement, "
+ "author text, "
+ "price real, "
+ "pages integer, "
+ "name text)" ;
public static final String CREATE_CATEGORY = "create table Category ("
+ "id integer primary key autoincrement, "
+ "category_name text, "
+ "category_code integer)" ;
private Context mContext ;
public MyDatabaseHelper ( Context context , String name , SQLiteDatabase . CursorFactory factory , int verison ) {
super ( context , name , factory , verison );
mContext = context ;
}
@Override
public void onCreate ( SQLiteDatabase db ) {
db . execSQL ( CREATE_BOOK );
db . execSQL ( CREATE_CATEGORY );
Toast . makeText ( mContext , "Book table created" , Toast . LENGTH_SHORT ). show ();
}
@Override
public void onUpgrade ( SQLiteDatabase db , int oldVersion , int newVersion ) {
}
}
但是运行程序发现,压根就无法创建Category表,这是因为BookStore.db
数据库已经存在,前面已发现此时无法再在活动中创建相同名称的表。不过不用担心,此时还可以通过版本号来创建表:
1
yDatabaseHelper dbHelper = new MyDatabaseHelper ( context , "BookStor.db" , null , 2 );
注意到此时dbHelper
的version
参数已经为2
了,那么此时点击创建数据库按理应该能够创建成功。但是实际上还是失败了,这是因为之前已经创建过了一个Book表,此时再创建便会报错。此时在onUpdate()
方法中执行SQL语句,删除已存在的待创建表,重新执行onCreate()
方法即可:
1
2
3
4
5
6
@Override
public void onUpgrade ( SQLiteDatabase db , int oldVersion , int newVersion ) {
db . execSQL ( "drop table if exists Book" );
db . execSQL ( "drop table if exists Category" );
onCreate ( db );
}
3.3 Insert插入表数据
使用SQL语句的方法不赘述了,这里简要总结一下Android提供的insert()
、put()
方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
btn_insertBook . setOnClickListener ( v -> {
Log . d ( "MainActivity" , "Insert book" );
SQLiteDatabase db = dbHelper . getWritableDatabase ();
ContentValues values = new ContentValues ();
values . put ( "name" , "The Da Vinci Code" );
values . put ( "author" , "Dan Brown" );
values . put ( "price" , 16 . 96 );
values . put ( "pages" , 454 );
db . insert ( "Book" , null , values );
values . clear ();
values . put ( "name" , "The Shawshank Redemption" );
values . put ( "author" , "Frank Darabont" );
values . put ( "price" , 14 . 99 );
values . put ( "pages" , 657 );
db . insert ( "Book" , null , values );
});
insert()
方法提供三个参数,分别表示待插入数据的表名、一般直接传入null
(在未指定添加数据的情况下给某些可为空的列自动赋值null
)、ContentValues对象
,ContenValues对象
通过put()
方法将对应的列名及数据传入。
3.4 Update更新表数据
步骤与插入数据类似,不同的是调用update()
方法。
3.5 Delete删除表数据
通过delete()
方法,传入三个参数:表名、约束、约束。
例如:删除名字叫“张三
”的犯罪嫌疑人:
1
2
SQLiteDatabase db = dbHelper . getWritableDatabase ();
db . delete ( "Suspects" , "name = ?" , new String [] { "张三" });
例如:删除限制民事行为能力人的信息:
1
2
SQLiteDatabase db = dbHelper . getWritableDatabase ();
db . delete ( "delete from Suspects where (age >= ? and age <= ?)" , new String [] { "8" , "16" });
3.6 查询表数据
SQLiteDatabase
提供了query()
方法用于查询数据,其可用参数如下表:
SQLiteDatabase的query()方法简介
示例代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
btn_queryBook . setOnClickListener ( v -> {
display . setText ( "" );
SQLiteDatabase db = dbHelper . getWritableDatabase ();
Cursor cursor = db . query ( "Book" , null , null , null , null , null , null );
StringBuilder content = new StringBuilder ();
if ( cursor . moveToFirst ()) {
do {
int indexName = cursor . getColumnIndex ( "name" );
int indexAuthor = cursor . getColumnIndex ( "author" );
int indexPrice = cursor . getColumnIndex ( "price" );
int indexPages = cursor . getColumnIndex ( "pages" );
if ( indexName <= 0 || indexAuthor <= 0 || indexPrice <= 0 || indexPages <= 0 ) {
Log . d ( "MainActivity" , "Column not found" );
return ;
}
String name = cursor . getString ( indexName );
String author = cursor . getString ( indexAuthor );
double price = cursor . getDouble ( indexPrice );
int pages = cursor . getInt ( indexPages );
content . append ( "《" + name + "》| Author: " + author + " | $" + price + " | Pages: " + pages + "\n" );
Log . d ( "MainActivity" , content . toString ());
} while ( cursor . moveToNext ());
}
display . setText ( content );
cursor . close ();
});
上述代码也只实现了一句SQL
语句“select * from Book;
”。