Android开发 | 数据持久化(上)

数据持久化方法之文件存储、SharedPreferences存储,及SQLite数据库存储

数据持久化方法之文件存储、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文件如下:

/androiddev-charpter6-datapersistence-a/storedXml.webp
.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);

注意到此时dbHelperversion参数已经为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()方法用于查询数据,其可用参数如下表:

/androiddev-charpter6-datapersistence-a/SQLiteQuery.webp
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;”。

给作者倒杯卡布奇诺 ~
Albresky 支付宝支付宝
Albresky 微信微信