Android 数据存储方案

时间:2021-09-16 14:57:14

       我们每天都在和数据打交道,在编写应程序以及使用应用程序的过程中时时刻刻存在着数据之间的交互,平时我们上 QQ、微信、微博,其实都是在产生数据,为了使关机后数据不丢失,我们就需要数据的持久化,这里简单的说一下数据的持久化,数据的持久化其实就是将那些在内存中的瞬时数据保存到存储设备中,保证即使在设备关机的情况下这些数据任然不会丢失,保存到内存中的数据是处于瞬时状态的,而保存在存储设备中的的数据是处于持久化状态的,持久化技术则是提供了一种解决数据持久化的机制,而我们今天要讨论的是 Android 的数据持久化技术,Android 系统主要提供了 3 种方式用于实现简单的数据持久化功能:如下

Android 数据存储方案


       上面就是我们 Android 系统提供的数据存储方案,另外我们也可以存放在我们手机的 SD 卡中,但可能相对来说不是太安全,下面我们具体来一一分析这些数据的存储方式


一、文件存储


       文件存储是 Android 中最基本的一种数据存储方式,它不对内容进行任何的格式化处理,所有数据都是原封不动的保存到文件当中,因此文件存储比较适合于存出一些二进制和简单的文本数据,对于较为复杂的数据存储则不方便,需要自定义一套自己的格式规范,以便之后从文件中解析

当文件被保存在内部存储中时,默认情况下,文件是应用私有的,其它应用不能访问,当用户卸载应用时,这些文件才能被删除

文件默认的存储位置:/data/data/包名/files/文件名


1.将数据存储到文件中


Context 类中提供了一个 openFileOutput() 方法,可以用于将数据存储到指定的文件夹中,默认的存储位置:/data/data/包名/files/文件名,以下是代码示例:

public void save(String inputData) {
FileOutputStream out = null;
BufferedWriter writer = null;
try {
out = openFileOutput("data", Context.MODE_PRIVATE);
writer = new BufferedWriter(new OutputStreamWriter(out));
writer.write(inputData);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (writer != null) {
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

我们对上面的代码进行简单的分析,调用 Context 的 openFileOutput() 方法,从上面我们可以看到 openFileOutput() 方法接受两个参数,它会返回给一个 FileOutputStream 对象:

1.1 第一个参数是文件名,也就是文件在创建的时候的文件名,需要注意的是这里指定的文件名不可以包含路径(不能包含路径分隔符 “/” ,如果文件不存在,Android 会自动创建它),因为所有的文件都是默认的存储位置:/data/data/包名/files/目录下的,

1.2 第二个参数是用于指定文件的操作模式,分为 4 种


1)context.MODE_PRIVATE:

是默认的操作模式,代表该文件是私有数据,在该模式下,写入的内容会覆盖原文件的内容
2)context.MODE_APPEND:

则表示如果该文件已经存在,就往文件里面追加内容,否则就创建新文件

3)context.MODE_READABLE:

表示当前文件可以被其他应用读取
4)context.MODE_WORLD_WRITEABLE:
表示当前文件可以被其他应用写入
这里需要注意的是由于 3)4)这两种模式允许其他应用程序对我们应用程序中的文件进行读写操作,所以很容易引起安全性漏洞,已经在 Android 4.2 版本中废弃
总结一下主要分为 3 步:
1)调用 Context 的 openFileOutput() 方法,填入文件名和操作模式,返回 FileOutputStream 对象
2)通多 FileOutputStream 对象的 write() 方法写入数据
3)通过 FileOutputStream 对象的 close() 方法关闭数据流

2.从文件中读取数据

Context 类提供了 openFileInput() 方法,用于从文件中读取数据,示例代码如下:
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();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return content.toString();
}

       同上我们也来简单的分析一下这段代码,在这段代码中,首先通过调用 openFileInput() 方法,获取到 FileInputStream 对象,然后借助它又构建出一个 InputStreamReader 对象,接着再使用 InputStreamReader 对象构建出一个 BufferedReader 对象,这样就能通过 BufferedReader 进行一行一行的读取,把文件中的文本内容全部读取出来,并存放在一个 StringBuilder 对象中,最后读取到的内容返回就可以了,总结如下:

1)调用 openFileInput() 方法,参数中填入文件名,返回 FileInputStream 对象
2)调用 Java 流对象的 read() 方法读取字节
3)调用 Java 流对象的 close() 方法关闭流

到这里我们就已经把文件存储方面的知识学习完了,最后我们来回顾一下,其实所用到的核心技术就是 Context 类中提供的 openFileInput() 和 openFileOutput() 方法,之后就是用到 Java 流的方式来进行读写操作,不过文件存储方式并不适合存储一些比较复杂的数据,因此接下来我们学习 Android 中的另一种数据持久化方式,它比文件存储更加简单易用,而且对某些指定的数据进行读写操作更加方便,如下



二、SharedPreferences存储


      不同于文件的存储方式,SharedPreferences 是通过键值对的方式来存储数据的,也就是说当保存一条数据的时候,需要给这条数据提供一个对应的键,这样在读取数据的时候就可以通多这个键把相应的值取出来,而且 SharedPreferences 还支持不同数据类型的存储,如果存储的数据类型是整型,那么读取出来的数据也是整型的,如果存储的是字符串,那么读取出来的数据仍然是字符串,这里我们就能明显的感觉到使用 SharedPreferences 来进行数据持久化技术比文件存储更加方便


1.将数据存储到 SharedPreferences 中


要想使用 SharedPreferences 存储数据,首先要获取到 SharedPreferences 对象,Android 中主要提供了 3 种方法用于得到 SharedPreferences 对象:


1)Context 类中的 getSharedPreferences() 方法


这个方法接受两个参数,第一个参数用于指定 SharedPreferences 的文件名称,如果指定的文件不存在则会创建一个,SharedPreferences 文件都是存放在/data/data/包名/shared_prefs/目录下的,第二个参数则用于指定操作模式,目前只有 MODE_PRIVATE 这一种模式可选,是默认的操作模式,和直接传入 0 效果是一样的,表示只有当前应用程序才可以对这个 SharedPreferences 文件进行读写,其他几种方式已经都被废弃 


2)Activity 类中的 getPreferences() 方法


这个方法和 Context 类中的 getSharedPreferences() 方法很相似,不过它只接受一个操作模式参数,因为使用这个方法时会自动将当前活动的类名作为 SharedPreferences 的文件名


3)PreferenceManager 类中 getDefaultSharedPreferences() 方法


这是一个静态方法,它接受一个 Context 参数,并自动使用当前应用程序的包名作为前缀来命名 SharedPreferences 文件,得到了 SharedPreferences 对象之后,就可以开始向 SharedPreferences 文件中存储数据了,代码如下:

// SharedPreferences 调用 edit() 方法即获取 Editor 对象
SharedPreferences.Editor editor = getSharedPreferences("data", MODE_PRIVATE).edit();
editor.putString("name", "USER_NAME");
editor.putInt("age", 25);
editor.apply(); //

可以看到主要分为 3 个步骤来实现

第一、调用 SharedPreferences 对象的 edit() 方法来获取 SharedPreferences.Editor 对象


第二、向 SharedPreferences.Editor 对象中添加数据,比如添加一个布尔类型的数据就使用 putBoolean() 方法,添加一个字符串则使用 putString() 方法,依次类推


第三、调用 apply() 方法将添加的数据提交,从而完成数据存储操作


除此之外,Editor 对象还包含clear()和remove()等方法


2.从 SharedPreferences 中读取数据


相对于 SharedPreferences 存储数据则从 SharedPreferences 中读取数据则更加简单,SharedPreferences 对象中提供了一系列的 get 方法,用于对存储数据进行读取,每种方法都对应了 SharedPreferences.Editor 中的一种 put 方法,比如读取一个布尔类型的数据就使用 getBoolean() 方法,读取一个字符串就是用 getString() 方法,这些 get 方法都接受两个参数,第一个参数是键,传入存储数据时使用的键就可以得到相应的值了,第二个参数是默认值,即表示当传入的键找不到对应的值时会以什么样的默认值进行返回,示例代码如下:

SharedPreferences pref = getSharedPreferences("data", MODE_PRIVATE);
String name = pref.getString("name", "");
int age = pref.getInt("age", 0);

到这里关于 SharedPreferences 的内容我们就学习完毕了,接下来我们要学习最重要的持久化技术,Android 中的数据库技术



三、SQLite 数据库存储


我们在前面学习了文件存储和SharedPreferences存储,但这些毕竟都属于保存一些简单的数据和键值对,当需要存储大量复杂的关系型数据的时候,上面的两种就不是那么的得心应手了,SQLite 是一款轻量级的关系型数据库,它的运算速度快,占用资源少,同长只需要几百 KB 的内存就够了,因而特别适合在移动设备上使用,SQLite 不仅支持标准的 SQL 语法,还遵循了数据库的 ACID 事务,所以只要你以前使用过其他关系型数据库,就可以很快地上手使用 SQLite,Android 正是把这个极为强大的数据库嵌入到系统当中,使得本地化功能有了质的飞跃


1.创建数据库


Android 为了让我们更加方便的管理数据库,专门提供了一个 SQLiteOpenHelper 帮组类,借助这个类就可以非常简单的对数据库进行创建和升级,首先 SQLiteOpenHelper 是一个抽象类,这就是说,我们如果使用它,就需要创建一个自己的帮助类去继承它,SQLiteOpenHelper 中有两个抽象方法,分别是 onCreat() 和 onUpgrade(),我们必须在自己的帮助类中重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑


SQLiteOpenHelper 中还有两个重要的方法: getReadableDatabase() 和 getWritableDatabase() 方法这两个方法都可以创建或打开一个现有的数据库(如果数据库存在则直接打开,否则创建一个新的数据库)并返回一个可对数据库进行读写操作的对象,不同的是,当数据库不可写入的时候(如磁盘空间已满),getReadableDatabase() 方法返回的对象将以读的方式去打开数据库,而 getWritableDatabase() 方法将出现异常


另外 SQLiteOpenHelper 中有两个构造方法可供重写,一般使用参数少一点的那个构造方法即可,这个方法接受 4 个参数,第一个参是 Context,第二个参数是数据库名,创建数据库的时候就是这里指定的名称,第三个参数容许我们在查询数据的时候返回一个自定义的 Cursor,一般都是传入 null,第四个参数表示当前数据库的版本号,可以对数据库进行升级操作,构建出 SQLiteOpenHelper 的实例以后,再掉用 getReadableDatabase() 和 getWritableDatabase() 方法就能创建数据库了,数据库文件会存放在/data/data/包名/databases/目录下,此时从写 onCreate() 方法也会得到执行,所以通长会在这里去处理一些创建表的逻辑

这里我们新建 MyDatabaseHelper 继承自 SQLiteOpenHelper,代码如下:

/**
* MyDatabaseHelper 帮助类
*/

public class MyDatabaseHelper extends SQLiteOpenHelper {

/**
* text 表示文本类型,integer 表示整型
* primary key 表示将 id 设为主键,并用 autoincrement 键字表示 id 是自增长的
*/
private static final String CREATE_BOOK = "create table book(" +
"id integer primary key autoincrement," +
"name text," +
"pages integer)";

private Context mContext;

/**
* 构造方法
*
* @param context 上下文
* @param name 数据库名,创建数据库时使用的就是这里的名称
* @param factory 允许我们在查询数据时返回一个自定义的Cursor,一般传入null
* @param version 数据库版本号
*/
public MyDatabaseHelper(Context context, String name,
SQLiteDatabase.CursorFactory factory,
int version) {
super(context, name, factory, version);
mContext = context;
}

/**
* 创建数据库
*
* @param db
*/
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
}

/**
* 升级数据库
*
* @param db
* @param oldVersion
* @param newVersion
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}



接下来我们就可以在应用程序中创建数据库了,代码如下:

MyDatabaseHelper dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 1);
dbHelper.getWritableDatabase();

这里我们构建了一个 MyDatabaseHelper 对象,并且通过构造函数的参数将数据库名指定为 BookStore.db,版本号指定为 1,并调用 getWritableDatabase() 方法,这样数据库也就得到了创建


2.升级数据库


细心的人估计已经注意到了,下面的这个方法我们还没有用:

    /**
* 升级数据库
*
* @param db
* @param oldVersion
* @param newVersion
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
升级数据库只需要将版本号增加,然后执行 onUpgrade() 方法就可以了


3. SQLite 数据库的增删改查


现在我们已经学习了创建和升级数据库的方法,接下来我们来一起学习下如何对表中的数据进行操作,其实我们可以对数据进行的操作也就 4 种,即 CRUD 


C  Create   添加  insert
R Retrieve 查询 select
U Update 更新 update
D Delete 删除 delete

首先创建数据库表的实体类BookBean:

/**
* Book 实体类
*/

public class BookBean {

private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getPages() {
return pages;
}

public void setPages(int pages) {
this.pages = pages;
}

private int pages;

}


前面我们已经知道,调用 SQLiteOpenHelper 的  getReadableDatabase() 和 getWritableDatabase() 方法是可以用于创建和升级数据库的,不仅如此,这两个方法还都会返回一个  SQLiteDatabase 对象,借助这儿对象就可以对数据库进行  CRUD 操作了

SQLiteDatabase db = dbHelper.getWritableDatabase();

接下来将数据库 book 表的增删改查封装起来,如下:

/**
* Book 表
*/

public class Book {
private MyDatabaseHelper dbHelper;
public Book(Context context){
dbHelper = new MyDatabaseHelper(context, "BookStore.db", null, 1);
}

/**
* 增
*/
public boolean add(BookBean bean){
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("name", bean.getName());
values.put("pages", bean.getPages());
/**
* table: 表名
* null: 一般传null
* values: 表中添加的一行的数据内容, ContentValues以Map封装
*/
long result = db.insert("book", "null", values); // 内部在拼装sql语句
// 关闭数据库
db.close();
if (result == -1) {
return false;
}
return true;
}

/**
* 删
*/
public int delete(String name){
SQLiteDatabase db = dbHelper.getWritableDatabase();
/**
* table: 表名
* whereClause: 删除条件
* whereArgs: 删除条件占位符的参数
* 返回值是: 成功删除了多少行
*/
int result = db.delete("book", "name = ?", new String[]{name});
// 关闭数据库
db.close();
return result;
}

/**
* 改
*/
public int update(BookBean bean){
SQLiteDatabase db = dbHelper.getWritableDatabase();
/**
* table: 表名
* values: 更改字段的内容, 以map封装
* whereClause: 更新条件
* whereArgs: 更新条件占位符的参数
* sql: update person set pages='10' where name='jianai';
*/
ContentValues values = new ContentValues();
values.put("pages", bean.getPages());
// 返回的值: 成功修改了多少行
int result = db.update("book", values, "name = ?", new String[]{bean.getName()});
// 关闭数据库
db.close();
return result;
}

/**
* 查
*/
public BookBean query(String name){
SQLiteDatabase db = dbHelper.getReadableDatabase();
BookBean bean = new BookBean();
/**
* table: 指定查询的表名
* columns: 指定查询的列名(null:查询所有)
* selection: 指定 where 的约束条件
* selectionArgs: 为 where 中的占位符提供具体的值
* groupBy: 指定需要 group by 的列
* having: 对 group by 后的结果进一步约束
* orderBy: 指定查询结果的排序方式
*/
Cursor cursor = db.query("book", new String[]{"id","name","pages"}, "name = ?",
new String[] {name}, null, null, "id desc");
/**
* 判断结果集中是否有数据
*/
if(cursor != null && cursor.getCount() >0){
// 循环遍历结果集获取结果集的内容
while(cursor.moveToNext()){
// 获取这一行上所有的数据内容
int id = cursor.getInt(0);
bean.setName(cursor.getString(1));
bean.setPages(Integer.parseInt(cursor.getString(2)));
}
}

// 关闭数据库
db.close();
return bean;
}
}


到这里我们就可以通过创建 Book 的实例,调用该实例的方法来增删改查数据库 book 表了:

Book book = new Book(context);
// 增
BookBean bean = new BookBean();
bean.setName("jianai");
bean.setPages("15");
book.add(bean);
// 删
book.delete("jianai");
// 改
BookBean bean = new BookBean();
bean.setName("jianai");
bean.setPages("40");
book.update(bean);
// 查
BookBean bean = book.query("lisi");

使用 SQL 语句操作数据库

首先打开数据库对应的对象:

MyDatabaseHelper dbHelper = new MyDatabaseHelper(context, "BookStore.db", null, 1);
SQLiteDatabase db = dbHelper.getWritableDatabase();

然后我们就可以直接使用 SQL 语句操作数据库:

增  db.execSQL("insert into book (name ,pages) values (?, ?)",new String[]{"gangtie","30"});
删 db.execSQL("delete from book where pages>?",new String[]{"200"});
改 db.execSQL("update person set pages=? where name=?",new String[]{"20","wangwu"});
查 db.rawQuery("select * from book",null);

到这里我们就把 SQLite 数据库学习完了,其实在真正的项目实际开发中我们通常也使用很多第三方的数据库,大家可以根据自己的喜好自行选择