0%

第6章:数据存储全方案:详解持久化技术

Android系统主要提供了3种方式用于简单地实现数据持久化功能——文件存储、SharedPreference存储以及数据库存储。

文件存储

文件存储是Android中最基本的存储方式,它不对存储内容进行任何的格式化处理,因而比较适合用于存储一些简单的文本数据或者二进制数据

将数据存储到文件

Context类提供了一个openFileOutput()方法,可以用于将数据存储到指定文件,需要两个参数,第一个参数是文件名,纯粹的名称,不可以包含路径,因为所有的文件都是默认存储到/data/data//files/目录下;还有个参数是操作模式,主要有两种(其他2种在4.2被废弃了):

  • MODE_PRIVATE:默认的操作模式,表示当指定同样文件名的时候,所写入的内容将会覆盖原来文件中的内容。
  • MODE_APPEND:表示如果该文件已经存在,就往文件里面追加内容,不存在就创建新文件。

保存文件的一般如以下代码操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void save(){
String dataStr = "data to save";
FileOutputStream out = null;
BufferedWriter writer = null;
try{
//文件名是data
out = openFileOutput("data",Context.MODE_PRIVATE);
writer = new BufferedWriter(new OutputStreamWriter(out));
writer.write(dataStr);
}catch(IOException e){
e.printStackTrace();
}finnaly{
try{
if(writer != null){
writer.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
}

存储成功后,可以通过Android Device Monitor 进入File Explorer标签,在目录中/data/data//files/中就能找到 data 文件。同理,读取存到文件中的代码应如下所示:

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
public String load(){
FileInputStream in = null;
BufferedReader reader = null;
StringBuilder content = new StringBuilder();
try{
in = openFileInput("data");
reader = new BufferedReader(new InputStreamReader(in));
String line = "";
while((line = reader.readLine()) != null){
content.append(line);
}

}catch(IOException e){
e.printStackTrace();
}finnaly{
try{
if(reader != null){
reader.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
return content.toString();
}

SharedPreference

SharedPreference是使用键值对的方式来存储数据的,保存一条数据的时候,需要给这条数据提供一个对应的键,读取数据时通过这个键把对应的值读取出来,SharedPreference文件都是存放在/data/data//shared_prefs目录下。要想存储数据,首先要获取到SharedPreference对象,Android主要提供了3中方式:

  • Context类中的getSharedPreference()方法:此方法接收两个参数,第一个用于指定文件名称,第二个用于指定操作模式,目前只有MODE_PRIVATE可选(其他的几种在4.2或者6.0版本被废弃了),并且是默认的操作模式,表示只有当前应用程序才可以对这个文件进行读写。

  • Activity中的getPreferences()方法:和Context类中的getSharedPreference()方法类似,只不过它只接受一个操作模式参数,因为使用这个方法时会自动将当前Activity的类名作为SharedPreference的文件名。

  • PreferenceManager类中的getDefaultSharedPreferences()方法:它接受一个Context参数,并自动使用当前应用程序的包名作为前缀来命名SharedPreference文件。

获取到SharedPreference对象之后,就可以开始存储数据了,主要分为3步实现:

  1. 调用SharedPreference对象的edit()方法获取SharedPreference.Editor对象
  2. 向SharedPreference.Editor对象添加数据。
  3. 调用apply()方法提交,从而完成存储操作。

代码形式应该是这样的:

1
2
3
4
5
6
7
SharedPreferences.Editor editor = getSharedPreferences("data",MODE_PRIVATE).edit();
editor.putString("name","Tom");
editor.apply();

//存储完成后,读取数据
SharedPreferences pref = getSharedPreferences("data",MODE_PRIVATE);
String name = pref.getString("name","");

SQLite数据库存储

文件存储和SharedPrefrences存储只适用于保存一些简单的数据和键值对,要存储大量复杂的关系型数据的时候,有点难以应付了。

创建数据库

Android为了让我们更方便地管理数据库,专门提供了一个SQLiteOpenHelper抽象类,要想使用的话,我们就需要创建一个自己的类去继承它,它有两个抽象方法,onCreate和onUpgrade用来创建和升级数据库,其它两个重要的实例方法:getReadableDatabase和getWritableDatabase,他们都可以创建或者打开一个现有的数据库(没有就创建),在数据库不可写入的时候(如磁盘满了),前者以只读的形式打开数据库,后者会出现异常。它有两个构造方法可重写,一般使用哪个参数较少的即可,总共4个参数,第一个context,第二个是数据库名,第三个是自定义的Cursor,一般传null,第四个表示当前的数据库版本号,用于对数据库进行升级操作。一般代码如下图所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyDatabaseHelper extends SQLiteOpenHelper{
public static final CREATE_BOOK = "create table Book ("
+ "id integer primary key autoincrement,"
+ "author text,"
+ "price real,"
+ "pages integer,"
+"name text)";

@Override
public void onCreate(SQLiteDatabase db){
db.exeSQL(CREATE_BOOK);
}
}

使用的时候应该是这样子的:

1
2
3
dbHelper = new MyDatabaseHelper(this,"BookStore.db",null,1);
//就会创建成功了
dbHelper.getWritableDatabase();

上例创建了一个Book表,使用primary key 将id设置为主键,并用autoincrement关键字表示id是自增长的。可以使用

adb shell

命令,之后cd到/data/data//databases/目录下用ls列出该目录的文件,可以看到BookStore.db文件,以及BookStore.db-journal文件,后者是数据库的临时文件。SQLite没有其他数据库一样有很多繁杂的数据类型,它的数据类型很简单:integer表示整型,real表示浮点型,text表示文本,blob表示二进制类型

升级数据库

此时项目中有一张Book表用于存放输的各种详细数据了,但是如果再想添加一张Category表用于记录图书的分类,如果仅仅直接在MyDatabaseHelper的onCreate中写成:

1
2
3
4
5
@Override
public void onCreate(SQLiteDatabase db){
db.exeSQL(CREATE_BOOK);
db.exeSQL(CREATE_CATEGORY);
}

是行不通的,因为使用的时候先初始化helper:dbHelper = new MyDatabaseHelper(this,”BookStore.db”,null,1)再获取数据库:dbHelper.getWritableDatabase(),而由于此时已经存在数据库BookStore.db了,因此不会再执行helper的onCreate方法了。此时清除app数据可以做到创建Category表,但是这在实际应用中不合理,而我们可以用onUpgrade方法来解决,我们前面构造了MyDatabaseHelper,第4个参数是版本号,我们目前是1,所以只要传入的值大于当前版本号1,onUpgrade方法就可以执行,因此我们可以这样增加Category表:

1
2
3
4
5
6
7
8
9
10
11
12
public class MyDatabaseHelper extends SQLiteOpenHelper{

...

@Override
public void onUpgrade(SQLiteDatabase db,int oldVersion,int newVersion){
db.exeSQL("drop table if exists Book");
db.exeSQL("drop table if exists Category");
onCreate(db);
}

}

上述代码执行了两条drop语句,发现数据库已经存在Book表和Category表了就删除,然后调用onCreate方法重新创建,因此在onCreate中也得写成:

1
2
3
4
5
@Override
public void onCreate(SQLiteDatabase db){
db.exeSQL(CREATE_BOOK);
db.exeSQL(CREATE_CATEGORY);
}

在使用的时候也得升级版本号:

1
2
3
dbHelper = new MyDatabaseHelper(this,"BookStore.db",null,2);
//就会创建成功了
dbHelper.getWritableDatabase();

获取到数据库,接下来可以对其CRUD操作,其中C代表添加(Create),R代表查询(retrieve),U代表更新(Update),D代表删除(Delete)。Android开发者水平参差不齐,并非每一个都会SQL语言,Android提供了一系列的辅助性方法,是的在Android中即使不去编写SQL语句,也能轻松完成所有CRUD操作。getReadableDatabase与getWriteableDatabase方法不仅可以用来创建和升级数据库,他们还会返回一个SQLiteDatabase对象,借助这个对象就可以轻松CRUD:

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
/**添加数据**/
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("name","thinking in java");
values.put("price",16.96);
values.put("pages",512);
//插入时指定表名为"Book"
db.insert("Book",null,values);

/**以下是更新**/
values.clear();
values.put("price",20);
//第三个参数对应SQL语句中的where部分,表示更新所有name等于?的行,而?是一个占位符,
//可以通过第四个参数提供的一个字符串数组为第三个参数中的每个占位符指定相应内容
db.update("Book",values,"name=?",new String[]{"thinking in java"});

/**以下是删除**/
//表示删除pages的值大于500的数据
db.delete("Book","pages > ?",new String[]{"500"});

/**以下是查询**/
Cursor cusor = db.query("Book",null,null,null,null,null,null);
if(cursor.moveToFirst()){
do{
String name = cursor.getString(cursor.getColumnIndex("name"));
String pages = cursor.Double(cursor.getColumnIndex("price"));
}while(cursor.moveToNext());
}
cusor.close();

当然,可以直接使用SQL语句直接完成上述操作:

1
2
3
4
5
6
7
8
9
10
11
//添加
db.execSQL("insert into Book (name,pages,price) values(?,?,?)",new String[]{"thinking in java","512","20"});

//升级
db.execSQL("update Book set price = ? where name = ",new String[]{"20","thinking in java"});

//删除
db.execSQL("delete from Book where pages > ?",new String[]{"500"});

//查询
db.execSQL("select * from Book",null);

使用LitePal

谢谢你的鼓励