结束 Android 开发(入门)课程 的第三部分《访问网络》后,来到最后的第四部分《数据与数据库》,这部分课程要完成一个 Pets App,这个应用采用不同于之前的数据结构来构建,允许用户获取宠物的信息,并能够进行添加、更新、删除等操作。其中涉及到数据持久性的概念,用到了 SQLite 数据库等内容。
第四部分的课程分为四节课完成,每节课的内容如下:
- Intro to SQLite Databases
介绍数据持久性的概念,以及 SQLite 数据库的基本操作。 - Databases in Android
应用 Android 中的数据库。 - Content Providers
通过内容提供器向数据库读取或添加数据。 - Finishing the App
实现应用的更新和删除数据功能,最终完成应用开发。
这是第四部分《数据与数据库》的第一节课,导师是 Jessica Lin 和 Katherine Kuan。这节课的重点是练习 SQLite 数据库的操作。
关键词:数据持久性、SQLite 数据库、CRUD
数据持久性
之前课程中的应用都使用变量来保存数据,变量的生命周期与 Activity 紧密联系,当 Activity 被销毁时,变量也会被清除,存储在变量中的数据自然也随着被清除。显然,对于一些数据来说,例如应用的偏好设置、用户数据,需要永久保存下来,直到被主动删除,这就是数据持久性的概念。
计算机(包括手机、平板、电脑)通常有两种类型的存储器:临时存储器和永久存储器。临时存储器可理解为用于短期记忆,它适用于数值计算或屏幕显示等场景,特点是快速存取和生命周期短,例如 RAM 就是一种临时存储器;永久存储器可理解为用于长期记忆,它适用于保存用户数据,特点是数据可永久保持,但存取较慢,例如硬盘就是一种永久存储器。关于两种存储器更详细的介绍可参考这篇文章。
Android 提供了多种永久存储数据的方法,包括将数据保存为文件放入设备存储器或上传至服务器;或者将数据保存至 SQLite 数据库;另外,应用的偏好设置也属于永久存储数据的一种方法,详细介绍可参考 这篇 Android Developers 文档。
其中,将数据保存为文件的方法适用于大型的媒体文件,例如相机应用将照片保存为 png 文件,音乐应用将歌曲保存为 mp3 文件;偏好设置则可以顾名思义,用于保存应用的用户偏好设置,一般通过 SharedPreference 的键/值对保存原始类型数据或字符串;最后,SQLite 数据库适用于大规模且易于存取的格式化数据,这是接下来讨论的重点。
值得注意的是,一个 Android 应用通常不止使用一种永久存储数据的方法,例如相机应用将照片保存为文件的同时,也会将照片的拍摄时间、地点等详细信息保存至 SQLite 数据库。
SQLite 数据库
数据库是由特定结构组织的数据集合,使其数据可轻易操作和分析。SQLite 就是一种组织结构为一个或多个表格的数据库,一个表格是存储一种对象的列表,每一行表示一个对象,每一列表示对象的一个 state 或属性 (Attribute)。其中,SQL (Structured Query Language) 指结构化查询语言,与应用于 Android 的 XML 和 Java 相同,SQL 也是一种编程语言,专门用于数据库的表格创建,以及其中的条目添加、读取、更新、删除操作。SQLite 就是一种能够理解 SQL 语言的程序,
SQLite 是一种开源的轻量化数据库(对应 MySQL 重量级数据库),用于创建本地数据库,无需服务器。同时 SQLite 是 Android 自带的数据库,所以 Android 应用无需下载任何内容就可以直接使用 SQLite 数据库。这节课先在 Android 之外熟悉 SQLite,即在电脑的终端使用 SQLite。
Note:
MacBook 通常都预装了 SQLite。要查看系统是否预装了 SQLite,打开终端,输入 sqlite3,再按下 return 键。如果返回的结果中包含 SQLite 版本等信息,那么系统中就已经安装了 SQLite。具体的指令如下所示。hsujin ~ $ sqlite3 SQLite version 3.19.3 2017-06-27 16:48:08 Enter ".help" for usage hints. Connected to a transient in-memory database. Use ".open FILENAME" to reopen on a persistent database. sqlite>
从上面的指令可以看到,在终端输入 sqlite3
,并按下 return 键后,就启动了 sqlite3 程序,就可以开始操作数据库了。还有一种方法是在终端输入 sqlite3 shelter.db
,即可启动 sqlite3 程序并直接打开指定的数据库文件,如果该文件不存在,则会创建一个新的数据库文件。
在 sqlite3 中包含两种指令,一种是 sqlite3 的程序指令,用于操作 sqlite3 软件本身,不属于 SQL 语言,特点是指令前面都有一个点 .
,大小写敏感,例如
-
.open shelter.db
打开指定的数据库文件。如果该文件不存在,则会创建一个新的数据库文件。 -
.quit
退出 sqlite3 程序。 -
.tables
查看当前数据库中的所有表格。 -
.schema ?PATTERN?
查看创建指定表格的指令。 -
.mode MODE ?TABLE?
选择表格的输出模式,支持 csv,column,tabs 等格式。例如指令.mode column
指定表格的输出模式为向左对齐的列,这样在输出表格时就清晰地把每一列显示出来,列之间用空格分隔。 -
.headers on|off
选择是否输出表格的表头,例如.headers on
表示在输出表格时表头会显示出来。 -
.help
查看所有可用的程序命令。
另一种指令是 SQL 指令,即使用 SQL 语言来操作表格,对 SQLite 数据库表格的操作可以简单总结为 CRUD,表示 Create(添加)、Read(读取)、Update(更新)、Delete(删除)。指令大小写不敏感,但是为了区别 sqlite3 的程序指令,一般都保持全部大写的习惯。指令最后以分号 ;
结束,如果在 sqlite3 中输入 SQL 指令时没有添加分号,那么 sqlite3 会使指令头显示省略号 ...>
表示当前指令未输入完全,提示用户继续输入内容,直到 sqlite3 检测到分号。开发者可以利用这一点,在输入较长的指令时分成多行,使指令可视化。
常用的 SQL 指令可以参考这个 Cheatsheet,下面详细介绍每种指令的用法。
一、创建表格
CREATE TABLE pets (_id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
breed TEXT NOT NULL,
gender INTEGER NOT NULL,
weight INTEGER NOT NULL DEFAULT 0);
使用 CREATE TABLE
关键字创建了一个名为 pets 的表格,有五个属性,分别为 _id
、name
、breed
、gender
、weight
。其中,第一个属性为 _id
,这是因为在一般情况下,每个对象都要有一个唯一识别码,下划线 _
是 Android 的命名惯例。
对象的每个属性都需要指定一种存储类 (Storage Class)。这与 Java 变量需要指定一种数据类型不同,SQLite 采用了更宽泛的动态类型系统,即一个值的数据类型与值本身相关,例如 SQLite 的整数 (Integer) 存储类就包括了六种不同字节大小的整数数据类型。关于 SQLite 存储类的详细介绍可参考 这篇文章。
SQLite 有五种存储类:
- NULL
表示这个值为空值。 - INTEGER
表示这个值为整数值,根据值的大小存储在 1,2,3,4,6,8 字节中。 - REAL
表示这个值为浮点值。 - TEXT
表示这个值为字符串,支持 UTF-8,UTF-16BE,UTF-16LE 编码方式。 - BLOB
表示数据按输入的格式原样保存,例如图片、二进制数据等。
值得注意的是,SQLite 的存储类没有布尔类型,对于布尔类型数据的存储,通常用 INETGER 代替,例如 0 表示 false,1 表示 true。事实上,对于一些固定选项的数据,通常都用 INETGER 来存储,例如性别信息,可以使用 0 表示未知,1 表示男性,2 表示女性。这种做法使数据存储更准确,同时更节省存储空间。
上述代码,_id
属性指定为 INTEGER 存储类,表示每个对象的唯一识别码数值;name
属性指定为 TEXT 存储类,表示宠物名字的字符串;breed
属性指定为 TEXT 存储类,表示宠物种类的字符串;gender
属性指定为 INTEGER 存储类,表示宠物性别的数值代码,其中 0 表示未知,1 表示雄性,2 表示雌性;weight
属性指定为 INTEGER 存储类,表示宠物体重值。
在指定对象属性的存储类后,通常还会添加 SQL 关键字对属性作出限制,常用的关键字有
- PRIMARY KEY
保证该属性的唯一性,一个表格中只能有一个属性应用这个 SQL 关键字。 - AUTOINCREMENT
当表格添加新对象时,自动为该属性新增一个数值,通常用于 ID 属性。 - NOT NULL
表示该属性不能为空,在表格添加新对象时必须带有该属性的值。 - DEFAULT <value>
当表格添加新对象时,该属性如果没有输入值,那么就会采用指定的默认值。
上述代码,_id
属性带有 PRIMARY KEY
和 AUTOINCREMENT
两个关键字,关键字之间用空格分隔,表示对象的唯一识别码,并且在添加新对象时,无需输入值,ID 值会自动加一,如果输入了已存在的 ID 值,sqlite3 会返回错误,保证 ID 值的唯一性;其余属性都带有 NOT NULL
关键字,表示该属性不能为空,在添加新对象时必须带有该属性的值;其中 weight
属性还带有 DEFAULT 0
关键字,表示如果没有输入宠物的体重值,那么就默认为零。
通过 SQL 指令 PRAGMA TABLE_INFO(?PATTERN?)
显示指定表格的属性信息,显示格式由 sqlite3 指令 .mode
决定,还可以使用 .headers on
指令使表头显示出来。
二、插入数据
INSERT INTO pets (_id, name, breed, gender, weight)
VALUES (1, "Tommy", "Pomeranian", 1, 4);
使用 INSERT INTO
关键字向 pets 表格添加了一个新对象,输入了每个属性的值,格式类似键/值对的形式,属性值按顺序放入 VALUES()
关键字内。对于未设置 NOT NULL
的属性,可以不输入值,例如根据创建表格时的设置,ID 值会在添加新对象时自动加一。注意指令最后添加分号。
三、读取数据
SELECT * FROM pets;
使用 SELECT
指令选中 pets 表格的所有列,其中 *
表示通配符,FROM
用于指定表格。sqlite3 返回的数据如下,显示格式设置为 .mode column
,并设置了 .headers on
显示表头。
_id name breed gender weight
---------- ---------- ------------------ ---------- ----------
1 Tommy Pomeranian Terrier 1 3
2 Garfield Tabby 1 4
3 Binx Bombay 1 7
4 Lady Cocker Spaniel 2 5
5 Cat Tabby 0 19
6 Baxter Border Terrier 1 6
7 Arlene 2 3
如果要读取指定一行的数据,需要用到 WHERE
关键字,例如
SELECT name, breed FROM pets WHERE _id == 1;
sqlite3 会返回 ID 值为 1 的对象的 name
和 breed
两列,列之间用逗号 ,
分隔,显示格式由 sqlite3 指令 .mode
决定,还可以使用 .headers on
指令使表头显示出来。
事实上,WHERE
关键字相当于一个条件语句,使 sqlite3 仅返回符合 WHERE
后面的条件的对象,例如指令 SELECT * FROM pets WHERE weight > 15;
仅返回体重为 15 以上的宠物信息。
除了 WHERE
条件语句外,还可以用 ORDER BY
关键字对读取的数据进行排序,例如
SELECT name, weight FROM pets WHERE gender == 1 ORDER BY weight DESC;
sqlite3 会返回雄性宠物的名字和体重两列,按照体重大小降序排列。其中 DESC
表示降序,ASC
表示升序。
四、更新数据
UPDATE pets SET weight = 18 WHERE _id = 5;
使用 UPDATE
关键字将 ID 值为 5 的宠物体重设置为 18,其中 SET
关键字实现属性的更新设置;WHERE
关键字作为条件语句指定要更新的对象,如果更新指令没有 WHERE
条件,那么更新的作用域会是整个表格,即更新表格内的所有对象。
五、删除数据
DELETE FROM pets WHERE _id = 1;
使用 DELETE FROM
关键字将 pets 表格内 ID 值为 1 的对象删除,这里同样用到了 WHERE
关键字作为条件语句指定要删除的对象,如果删除指令没有 WHERE
条件,那么就会删除表格内的所有对象,所以使用这条指令要谨慎。
六、删除表格
DROP TABLE pets;
使用 DROP TABLE
关键字删除 pets 表格。
Tips:
在终端输入指令时,按下键盘的向上和向下按钮,可以浏览之前输入的指令记录,这对于一些反复使用的指令非常有用;另外,在上一条指令输入有误时,也可以按下向上按钮,快速呼出指令直接进行修改。