DataBase in Android
我們知道, 如果我們的數(shù)據(jù)僅僅是存儲在變量中的, 那么其生命周期可能只是和 Activity 一樣長, 在我們推出 APP 之后, 我們的數(shù)據(jù)就會丟失. 我們需要數(shù)據(jù)持久化(data persistence). 我們可以將數(shù)據(jù)存儲在文件或者數(shù)據(jù)庫中. 這里我們討論的是數(shù)據(jù)庫的方式.
計算機存儲
計算機有兩大主要的存儲設(shè)備, 一類是內(nèi)存, 一類是外存. 內(nèi)存(Memory), 是一種臨時存儲器, 讀寫速度快, 斷電數(shù)據(jù)丟失, 所以不能長期保存數(shù)據(jù). 外存, 包括硬盤, 軟盤, 光盤等等, 讀寫速度慢, 但是可以長期保存數(shù)據(jù), 斷電不丟失. 我們平時所稱的安裝 APP, 是安裝在外存中, 只有當程序運行的時候, 才會被加載到內(nèi)存中(也必須被加載到內(nèi)存中才能運行). 我們在程序中使用的變量, 他們的生命周期是隨著程序的結(jié)束而結(jié)束的, 在程序啟動時被創(chuàng)建和使用, 在程序結(jié)束時被系統(tǒng)銷毀, 所以不能作為長久保存數(shù)據(jù)的工具.
Android 中的幾種不同的數(shù)據(jù)存儲選擇
我們這里所討論的內(nèi)容, 是將數(shù)據(jù)存儲到我們的 Android 設(shè)備中, 不包括存儲到云端等方式.
- Files, 文件
- Shared Preferences
- SQLite Databases, SQLite 數(shù)據(jù)庫
Files, 保存到文件中
將保存為文件, 常見的有圖片, 音樂, 視頻的存儲等.
Shared Preferences(首選項)
數(shù)據(jù)以鍵值對的形式來保存, (Key - Unique string, Value - Primitive types and Strings), 即包括一個唯一的字符串 key 和其對應的值, 值可以是各種原始數(shù)據(jù)類型或者是 String.
Shared Preferences 不適合存儲大量的用戶數(shù)據(jù), 適合存儲少量的關(guān)鍵的用戶信息.
SQLite Databases
SQLLite 是一種數(shù)據(jù)庫, 數(shù)據(jù)庫簡單來講, 是一種有組織的數(shù)據(jù)結(jié)構(gòu).
SQLite 的基本組成簡單來講, 可以理解為表, SQLite 數(shù)據(jù)庫由一張張的表來組成.
數(shù)據(jù)庫可以讓我們輕松的分享大量相關(guān)的結(jié)構(gòu)化數(shù)據(jù), 同時, 這些數(shù)據(jù)可能隨著使用而不斷增長. 比如我們的通訊錄, 里面存儲的每一個聯(lián)系人的各種信息, 可以理解為一張表, 記錄各種的屬性, 比如姓名, 手機, 郵箱等等. 這些聯(lián)系人之間, 屬性和屬性之間是相關(guān)的, 他們都是大致相同的格式化數(shù)據(jù). 屬性之間是相關(guān)的, "張三" 的手機號是 "130XXXXXXXX" 而不是其他. 同時, 通訊錄還會因為我們的聯(lián)系人的增多而添加內(nèi)容, 還可以修改和刪除內(nèi)容. 對于這些數(shù)據(jù), 我們可以輕松的檢索到我們需要的信息.
SQLite
安裝 SQLite
最新版的 MacOS 中已經(jīng)預裝了 SQLite, 打開終端輸入 sqlite3, 可以檢查是否有 SQLite.
SQL
當我們輸入 sqlite3 命令進入 SQLite 之后會出現(xiàn)一下界面.
dcrdeMacBook-Pro:~ Dcr$ sqlite3
SQLite version 3.16.0 2016-11-04 19:09:39
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite>
這里我們可以使用 SQL(Structured query language, 結(jié)構(gòu)化查詢語言).
SQLite 中的類型
SQLite 中的 Storage Class:
- NULL
- INTEGER: 有符號整數(shù)
- REAL: 浮點數(shù)
- TEXT: 字符串
- BLOB: 原始數(shù)據(jù), 比如圖片或者二進制數(shù)據(jù).
對于 booleans, 我們可以使用 INTEGER 來代替:
- 0 - false
- 1 - true
語法
下面介紹 SQL 的基本語法.
CRUD
CRUD 是 Create, Read, Update, Delete 的縮寫. 是數(shù)據(jù)庫中的四項重要操作.
CREATE
Create a database
在 SQLite 中, 創(chuàng)建一個新的數(shù)據(jù)庫, 可以在命令行中使用 sqlite3 <database_name>.db 來創(chuàng)建. 或者在進入 sqlite 之后, 使用 .open <database_name>.db 來創(chuàng)建.
Create a table
創(chuàng)建一張新的表需要指定表名和列名和對應的數(shù)據(jù)類型. 語法如下:
CREATE TABLE <table_name>(
<column_name_1> <data_type_1>,
<column_name_2> <data_type_2>,
...
);
上面是創(chuàng)建表的基本語法, 我們創(chuàng)建時, 還可以給每列對應的參數(shù)添加屬性, 基本的屬性有:
-
PRIMARY KEY: 主鍵, 每張表只能有一個主鍵. -
AUTOINCREMENT: 添加行時, 該屬性自動加一, 可以用于指定每行的 ID. -
NOT NULL: 非空, 表示添加行時, 該元素必須有值. -
DEFAULT <value>: 默認值, 如果創(chuàng)建時沒有提供值, 那么以默認值填充.
INSERT data in a table
向表中添加行:
INSERT INTO <table_name> (
<column_name_1>,
<column_name_2>,
...) VALUES (
<values_1>,
<values_2>,
...
);
READ
讀操作使用的是 SELECT 關(guān)鍵字, 基本操作如下:
SELECT <columns>
FROM <table_name>;
SELECT 后跟的列項可以用 * 表示所有列.
執(zhí)行上面的操作, 是查詢一個表的所有行, 我們要查詢我們所需要的行時, 可以使用 WHERE 關(guān)鍵字.
SELECT <columns>
FROM <table_name>
WHERE <condition>;
我們還可以指定結(jié)果的排序方式, 通過 ORDER BY 關(guān)鍵字.
SELECT <columns>
FROM <table_name>
WHERE <condition>
ORDER BY <ASC|DESC>
-
ASC: 升序 -
DESC: 降序
UPDATE
UPDATE 用于更新表中的數(shù)據(jù).
UPDATE <table_name>
SET <column_name> = <value>
WHERE <condition>;
同樣, 使用 WHERE 指定所要修改的行.
DELETE
刪除行
刪除滿足 WHERE 條件的行.
注意: 使用 DELETE 一定要非常小心, 比如, DELETE FROM table_name 就會刪除所有行.
DELETE FROM <table_name>
WHERE <condition>;
刪除表
刪除表使用 DROP TABLE 關(guān)鍵字:
DROP TABLE <table_name>;
SQLite 中的命令
-
.shema <table_name>: 顯示創(chuàng)建表的命令. -
PRAGMA TABLE_INFO(<table_name>);: 顯示表的列信息
SQLite in Android
Why is a database always represented with a cylinder?
確定數(shù)據(jù)庫的架構(gòu)
- 確定表的名稱
- 每一列數(shù)據(jù)的名稱和數(shù)據(jù)類型
TABLE_NAME = "pets"
| Column name | Data types | Attributes |
|---|---|---|
| _id | INTEGER | PRIMARY KEY AUTOINCREEMENT |
| name | TEXT | NOT NULL |
| breed | TEXT | |
| gender | INTEGER | NOT NULL DEFAULT 0 |
| weight | INTEGER | NOT NULL DEFAULT 0 |
Contract
我們在使用數(shù)據(jù)庫時, 會用到很多的變量, 為了方便和不出錯, 我們可以將這些變量單獨拿出來, 這就是 Contract 類.
Contract 類中, 定義了我們將用到的許多常量, 比如, URI, 表名, 列名以及一些有特殊意義的整數(shù)值等等. 下面是一個簡單的例子: 例子中, 重寫了私有的構(gòu)造函數(shù), 因為我們并不需要該類的對象.
public final class FeedReaderContract {
// To prevent someone from accidentally instantiating the contract class,
// make the constructor private.
private FeedReaderContract() {}
/* Inner class that defines the table contents */
public static class FeedEntry implements BaseColumns {
public static final String TABLE_NAME = "entry";
public static final String COLUMN_NAME_TITLE = "title";
public static final String COLUMN_NAME_SUBTITLE = "subtitle";
}
}
有了 Contract 類之后, 我們就可以寫出我們常用的 SQL 語句了. 比如, 下面就是我們常用的 CREATE TABLE 和 DROP TABLE 語句.
private static final String SQL_CREATE_ENTRIES =
"CREATE TABLE " + FeedEntry.TABLE_NAME + " (" +
FeedEntry._ID + " INTEGER PRIMARY KEY," +
FeedEntry.COLUMN_NAME_TITLE + " TEXT," +
FeedEntry.COLUMN_NAME_SUBTITLE + " TEXT)";
private static final String SQL_DELETE_ENTRIES =
"DROP TABLE IF EXISTS " + FeedEntry.TABLE_NAME;
通過 SQLHelper 創(chuàng)建數(shù)據(jù)庫
在 Android 中, 數(shù)據(jù)庫默認存儲在應用的私有區(qū)域的, 這一區(qū)域默認情況下其他應用是無法訪問的, 所以是安全的.
我們可以使用 SQLiteOpenHelper 類來獲取數(shù)據(jù)庫的引用, 此時, 系統(tǒng)會在需要的時候才會執(zhí)行 creating 和 update 數(shù)據(jù)庫這些費時的操作, 而不是在應用啟動的時候, 我們需要做的就是去調(diào)用 getWritableDatabase() 或者 getReadableDatabase() 方法.
注意: 因為這樣的操作可能是長時間操作, 所以, 務必請在后臺線程調(diào)用
getWritableDatabase()或者getReadableDatabase()方法.
使用時, 我們可以繼承 SQLiteOpenHelper 類重寫 onCreate(), onUpgrade(), 和 onOpen() 回調(diào)方法, 在需要時, 也可以重寫 onDowngrade() 方法. 比如:
public class FeedReaderDbHelper extends SQLiteOpenHelper {
// If you change the database schema, you must increment the database version.
public static final int DATABASE_VERSION = 1;
public static final String DATABASE_NAME = "FeedReader.db";
public FeedReaderDbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
public void onCreate(SQLiteDatabase db) {
db.execSQL(SQL_CREATE_ENTRIES);
}
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// This database is only a cache for online data, so its upgrade policy is
// to simply to discard the data and start over
db.execSQL(SQL_DELETE_ENTRIES);
onCreate(db);
}
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
onUpgrade(db, oldVersion, newVersion);
}
}
我們在需要數(shù)據(jù)庫的時候, 使用下面的語句.
FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext());
在數(shù)據(jù)庫中存入數(shù)據(jù)
我們在將數(shù)據(jù)存入數(shù)據(jù)庫中時, 可以使用 ContentValues 對象來進行.
ContentValues 對象就是鍵值對組, 操作起來也比較簡單. 操作步驟如下:
// 1. 獲取 SQLiteDatabase 對象
SQLiteDatabase db = mDbHelper.getWritableDatabase();
// 2. 創(chuàng)建值的 map, keys 是列名
ContentValues values = new ContentValues();
values.put(FeedEntry.COLUMN_NAME_TITLE, title);
values.put(FeedEntry.COLUMN_NAME_SUBTITLE, subtitle);
// 3. 加入到數(shù)據(jù)庫中去
long newRowId = db.insert(FeedEntry.Table_name, null, values);
從數(shù)據(jù)庫中讀取數(shù)據(jù)
method query()
首先我們可以先了解一下 query() 方法.
SQLiteDatabase.query():
Cursor query (String table,
String[] columns,
String selection,
String[] selectionArgs,
String groupBy,
String having,
String orderBy,
String limit)
| Parameters | |
|---|---|
| table | String: 要查詢的表 |
| column | String[]: 要查詢的列的數(shù)組。 |
| selection | String: 對應 SQL 中的 WHERE 語句部分(不包括 WHERE 本身). |
| selectionArgs | String[], 在 selection 參數(shù)中, 可能包含 ? 作為參數(shù)的占位符, 在這個參數(shù)中給出. |
| groupBy | String: 對應 SQL 語句中的 GROUP BY 語句(不包括 GROUP BY 本身). |
| having | String: 對應 HAVING 語句(不包括 HAVING 本身). |
| orderBy | String: SQL ORDER BY 語句(不包括 ORDER BY 本身) |
| limit | String: 返回的行數(shù), LIMIT 分句的格式 |
SQLiteDatabase db = mDbHelper.getReadableDatabase();
// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
FeedEntry._ID,
FeedEntry.COLUMN_NAME_TITLE,
FeedEntry.COLUMN_NAME_SUBTITLE
};
// Filter results WHERE "title" = 'My Title'
String selection = FeedEntry.COLUMN_NAME_TITLE + " = ?";
String[] selectionArgs = { "My Title" };
// How you want the results sorted in the resulting Cursor
String sortOrder =
FeedEntry.COLUMN_NAME_SUBTITLE + " DESC";
Cursor cursor = db.query(
FeedEntry.TABLE_NAME, // The table to query
projection, // The columns to return
selection, // The columns for the WHERE clause
selectionArgs, // The values for the WHERE clause
null, // don't group the rows
null, // don't filter by row groups
sortOrder // The sort order
);
Cursor
我們的數(shù)據(jù)庫查詢語句返回一個 Cursor 對象, 那么什么是 Cursor 呢? Cursor 是查詢結(jié)果中行的集合.
首先, 顧名思義, Cursor 有一個 position 的指針, 指向的就是當前的行. 行的編號是從 0 開始的, 初始 position = -1.
其工作模式就是, 通過一系列 move 方法, 來選中行, 再通過不同的 get 方法來獲取我們需要的數(shù)據(jù).
在確定列的時候, 我們需要知道列名, 然后通過 getColumnIndex() 或者 getColumnIndexOrThrow() 來獲取 column index. 有了 column index, 我們就可以通過 getInt() getFloat() 等一系列方法來獲取具體的數(shù)據(jù).
-
getCount(): 返回 cursor 的行數(shù). -
getType(int columnIndex): 返回給定的列的類型 -
getDouble(int columnIndex)getFloat(int columnIndex)getInt(int columnIndex)getLong(int columnIndex)getString(int columnIndex)getShort(int columnIndex): 返回對應類型的值.
所有的 move 方法返回一個 boolean 值, 表示能否移動到該行.
-
moveToFirst(): 移動到第一行 -
moveToLast(): 移動到最后一行 -
moveToNext(): 移動到下一行 -
moveToPosition(int position): 移動到指定位置 -
moveToPrevious(): 移動到前一次移動的行