Android系統(tǒng)內(nèi)建“SQLite”數(shù)據(jù)庫(kù),它是一個(gè)開放的小型數(shù)據(jù)庫(kù),它跟一般商用的大型數(shù)據(jù)庫(kù)有類似的架構(gòu)與用法,例如MySQL數(shù)據(jù)庫(kù)。應(yīng)用程式可以建立自己需要的數(shù)據(jù)庫(kù),在數(shù)據(jù)庫(kù)中使用Android API執(zhí)行資料的管理和查詢的工作。儲(chǔ)存資料的數(shù)量是根據(jù)裝置的儲(chǔ)存空間決定的,所以如果空間足夠的話,應(yīng)用程式可以儲(chǔ)存比較大量的資料,在需要的時(shí)候隨時(shí)可以執(zhí)行數(shù)據(jù)庫(kù)的管理和查詢的工作。
一般商用的大型數(shù)據(jù)庫(kù),可以提供快速存取與儲(chǔ)存非常大量的資料,也包含網(wǎng)絡(luò)通訊和復(fù)雜的存取權(quán)限管理,不過它們都會(huì)使用一種共通的語言“SQL”,不同的數(shù)據(jù)庫(kù)產(chǎn)品都可以使用SQL這種數(shù)據(jù)庫(kù)語言,執(zhí)行資料的管理和查詢的工作。SQLite數(shù)據(jù)庫(kù)雖然是一個(gè)小型數(shù)據(jù)庫(kù),不過它跟一般大型數(shù)據(jù)庫(kù)的架構(gòu)與用法也差不多,同樣可以使用SQL執(zhí)行需要的工作,Android另外提供許多數(shù)據(jù)庫(kù)的API,讓開發(fā)人員使用API執(zhí)行數(shù)據(jù)庫(kù)的工作。
這一章會(huì)從了解應(yīng)用程式數(shù)據(jù)庫(kù)的需求開始,介紹如何建立數(shù)據(jù)庫(kù)與表格,在應(yīng)用程式運(yùn)作的過程中,如何執(zhí)行數(shù)據(jù)庫(kù)的新增、修改、刪除與查詢的工作。
## 11-1 設(shè)計(jì)數(shù)據(jù)庫(kù)表格
在數(shù)據(jù)庫(kù)的技術(shù)中,一個(gè)數(shù)據(jù)庫(kù)(Database)表示應(yīng)用程式儲(chǔ)存與管理資料的單位,應(yīng)用程式可能需要儲(chǔ)存很多不同的資料,例如一個(gè)購(gòu)物網(wǎng)站的數(shù)據(jù)庫(kù),就需要儲(chǔ)存與管理會(huì)員、商品和訂單資料。每一種在數(shù)據(jù)庫(kù)中的資料稱為表格(Table),例如會(huì)員表格可以儲(chǔ)存所有的會(huì)員資料。
SQLite 數(shù)據(jù)庫(kù)的架構(gòu)也跟一般數(shù)據(jù)庫(kù)的概念類似,所以應(yīng)用程式需要先建立好需要的數(shù)據(jù)庫(kù)與表格后,才可以執(zhí)行儲(chǔ)存與管理資料的工作。建立表格是在Android應(yīng)用程式中,唯一需要使用SQL執(zhí)行的工作。其它執(zhí)行數(shù)據(jù)庫(kù)管理與查詢的工作,Android都提供執(zhí)行各種功能的API,使用這些API就不需要了解太多SQL這種數(shù)據(jù)庫(kù)語言。
建立數(shù)據(jù)庫(kù)表格使用SQL的“CREATE TABLE”指令,這個(gè)指令需要指定表格的名稱,還有這個(gè)表格用來儲(chǔ)存每一筆資料的字段(Column)。這些需要的表格字段可以對(duì)應(yīng)到主要類別中的字段變量,不過SQLite數(shù)據(jù)庫(kù)的資料型態(tài)只有下面這幾種,使用它們來決定表格字段可以儲(chǔ)存的資料型態(tài):
* INTEGER – 整數(shù),對(duì)應(yīng)Java 的byte、short、int 和long。
* REAL – 小數(shù),對(duì)應(yīng)Java 的float 和double。
* TEXT – 字串,對(duì)應(yīng)Java 的String。
在設(shè)計(jì)表格字段的時(shí)候,需要設(shè)定字段名稱和型態(tài),表格字段的名稱建議就使用主要類別中的字段變量名稱。表格字段的型態(tài)依照字段變量的型態(tài),把它們轉(zhuǎn)換為SQLite提供的資料型態(tài)。通常在表格字段中還會(huì)加入“NOT NULL”的指令,表示這個(gè)表格字段不允許空值,可以避免資料發(fā)生問題。
表格的名稱可以使用主要類別的類別名稱,一個(gè)SQLite表格建議一定要包含一個(gè)可以自動(dòng)為資料編號(hào)的字段,字段名稱固定為“_id”,型態(tài)為“INTEGER”,后面加上“PRIMARY KEY AUTOINCREMENT”的設(shè)定,就可以讓SQLite自動(dòng)為每一筆資料編號(hào)以后儲(chǔ)存在這個(gè)字段。
## 11-2 建立SQLiteOpenHelper類別
Android 提供許多方便與簡(jiǎn)單的數(shù)據(jù)庫(kù)API,可以簡(jiǎn)化應(yīng)用程式處理數(shù)據(jù)庫(kù)的工作。這些API都在“android.database.sqlite”套件,它們可以用來執(zhí)行數(shù)據(jù)庫(kù)的管理和查詢的工作。在這個(gè)套件中的“SQLiteOpenHelper”類別,可以在應(yīng)用程式中執(zhí)行建立數(shù)據(jù)庫(kù)與表格的工作,應(yīng)用程式第一次在裝置執(zhí)行的時(shí)候,由它負(fù)責(zé)建立應(yīng)用程式需要的數(shù)據(jù)庫(kù)與表格,后續(xù)執(zhí)行的時(shí)候開啟已經(jīng)建立好的數(shù)據(jù)庫(kù)讓應(yīng)用程式使用。還有應(yīng)用程式在運(yùn)作一段時(shí)間以后,如果增加或修改功能,數(shù)據(jù)庫(kù)的表格也增加或修改了,它也可以為應(yīng)用程式執(zhí)行數(shù)據(jù)庫(kù)的修改工作,讓新的應(yīng)用程式可以正常的運(yùn)作。
接下來設(shè)計(jì)建立數(shù)據(jù)庫(kù)與表格的類別,在“net.macdidi.myandroidtutorial”套件按鼠標(biāo)右鍵,選擇“New -> Java CLass”,在Name輸入“MyDBHelper”后選擇“OK”。參考下列的內(nèi)容先完成部份的程式碼:
~~~
package net.macdidi.myandroidtutorial;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
public class MyDBHelper extends SQLiteOpenHelper {
// 數(shù)據(jù)庫(kù)名稱
public static final String DATABASE_NAME = "mydata.db";
// 數(shù)據(jù)庫(kù)版本,資料結(jié)構(gòu)改變的時(shí)候要更改這個(gè)數(shù)字,通常是加一
public static final int VERSION = 1;
// 數(shù)據(jù)庫(kù)物件,固定的字段變量
private static SQLiteDatabase database;
// 建構(gòu)子,在一般的應(yīng)用都不需要修改
public MyDBHelper(Context context, String name, CursorFactory factory,
int version) {
super(context, name, factory, version);
}
// 需要數(shù)據(jù)庫(kù)的元件呼叫這個(gè)方法,這個(gè)方法在一般的應(yīng)用都不需要修改
public static SQLiteDatabase getDatabase(Context context) {
if (database == null || !database.isOpen()) {
database = new MyDBHelper(context, DATABASE_NAME,
null, VERSION).getWritableDatabase();
}
return database;
}
@Override
public void onCreate(SQLiteDatabase db) {
// 建立應(yīng)用程式需要的表格
// 待會(huì)再回來完成它
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 刪除原有的表格
// 待會(huì)再回來完成它
// 呼叫onCreate建立新版的表格
onCreate(db);
}
}
~~~
## 11-3 數(shù)據(jù)庫(kù)功能類別
在Android應(yīng)用程式中使用數(shù)據(jù)庫(kù)功能通常會(huì)有一種狀況,就是Activity或其它元件的程式碼,會(huì)因?yàn)榧尤胩幚頂?shù)據(jù)庫(kù)的工作,程式碼變得又多、又復(fù)雜。一般程式設(shè)計(jì)的概念,一個(gè)元件中的程式碼如果很多的話,在撰寫或修改的時(shí)候,都會(huì)比較容易出錯(cuò)。所以這里說明的作法,會(huì)采用在一般應(yīng)用程式中執(zhí)行數(shù)據(jù)庫(kù)工作的設(shè)計(jì)方式,把執(zhí)行數(shù)據(jù)庫(kù)工作的部份寫在一個(gè)獨(dú)立的Java類別中。
接下來設(shè)計(jì)應(yīng)用程式需要的數(shù)據(jù)庫(kù)功能類別,提供應(yīng)用程式與數(shù)據(jù)庫(kù)相關(guān)功能。在“net.macdidi.myandroidtutorial”套件按鼠標(biāo)右鍵,選擇“New -> Java CLass”,在Name輸入“ItemDAO”后選擇“OK”。參考下列的內(nèi)容先完成部份的程式碼:
~~~
package net.macdidi.myandroidtutorial;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
// 資料功能類別
public class ItemDAO {
// 表格名稱
public static final String TABLE_NAME = "item";
// 編號(hào)表格字段名稱,固定不變
public static final String KEY_ID = "_id";
// 其它表格字段名稱
public static final String DATETIME_COLUMN = "datetime";
public static final String COLOR_COLUMN = "color";
public static final String TITLE_COLUMN = "title";
public static final String CONTENT_COLUMN = "content";
public static final String FILENAME_COLUMN = "filename";
public static final String LATITUDE_COLUMN = "latitude";
public static final String LONGITUDE_COLUMN = "longitude";
public static final String LASTMODIFY_COLUMN = "lastmodify";
// 使用上面宣告的變量建立表格的SQL指令
public static final String CREATE_TABLE =
"CREATE TABLE " + TABLE_NAME + " (" +
KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
DATETIME_COLUMN + " INTEGER NOT NULL, " +
COLOR_COLUMN + " INTEGER NOT NULL, " +
TITLE_COLUMN + " TEXT NOT NULL, " +
CONTENT_COLUMN + " TEXT NOT NULL, " +
FILENAME_COLUMN + " TEXT, " +
LATITUDE_COLUMN + " REAL, " +
LONGITUDE_COLUMN + " REAL, " +
LASTMODIFY_COLUMN + " INTEGER)";
// 數(shù)據(jù)庫(kù)物件
private SQLiteDatabase db;
// 建構(gòu)子,一般的應(yīng)用都不需要修改
public ItemDAO(Context context) {
db = MyDBHelper.getDatabase(context);
}
// 關(guān)閉數(shù)據(jù)庫(kù),一般的應(yīng)用都不需要修改
public void close() {
db.close();
}
// 新增參數(shù)指定的物件
public Item insert(Item item) {
// 建立準(zhǔn)備新增資料的ContentValues物件
ContentValues cv = new ContentValues();
// 加入ContentValues物件包裝的新增資料
// 第一個(gè)參數(shù)是字段名稱, 第二個(gè)參數(shù)是字段的資料
cv.put(DATETIME_COLUMN, item.getDatetime());
cv.put(COLOR_COLUMN, item.getColor().parseColor());
cv.put(TITLE_COLUMN, item.getTitle());
cv.put(CONTENT_COLUMN, item.getContent());
cv.put(FILENAME_COLUMN, item.getFileName());
cv.put(LATITUDE_COLUMN, item.getLatitude());
cv.put(LONGITUDE_COLUMN, item.getLongitude());
cv.put(LASTMODIFY_COLUMN, item.getLastModify());
// 新增一筆資料并取得編號(hào)
// 第一個(gè)參數(shù)是表格名稱
// 第二個(gè)參數(shù)是沒有指定字段值的默認(rèn)值
// 第三個(gè)參數(shù)是包裝新增資料的ContentValues物件
long id = db.insert(TABLE_NAME, null, cv);
// 設(shè)定編號(hào)
item.setId(id);
// 回傳結(jié)果
return item;
}
// 修改參數(shù)指定的物件
public boolean update(Item item) {
// 建立準(zhǔn)備修改資料的ContentValues物件
ContentValues cv = new ContentValues();
// 加入ContentValues物件包裝的修改資料
// 第一個(gè)參數(shù)是字段名稱, 第二個(gè)參數(shù)是字段的資料
cv.put(DATETIME_COLUMN, item.getDatetime());
cv.put(COLOR_COLUMN, item.getColor().parseColor());
cv.put(TITLE_COLUMN, item.getTitle());
cv.put(CONTENT_COLUMN, item.getContent());
cv.put(FILENAME_COLUMN, item.getFileName());
cv.put(LATITUDE_COLUMN, item.getLatitude());
cv.put(LONGITUDE_COLUMN, item.getLongitude());
cv.put(LASTMODIFY_COLUMN, item.getLastModify());
// 設(shè)定修改資料的條件為編號(hào)
// 格式為“字段名稱=資料”
String where = KEY_ID + "=" + item.getId();
// 執(zhí)行修改資料并回傳修改的資料數(shù)量是否成功
return db.update(TABLE_NAME, cv, where, null) > 0;
}
// 刪除參數(shù)指定編號(hào)的資料
public boolean delete(long id){
// 設(shè)定條件為編號(hào),格式為“字段名稱=資料”
String where = KEY_ID + "=" + id;
// 刪除指定編號(hào)資料并回傳刪除是否成功
return db.delete(TABLE_NAME, where , null) > 0;
}
// 讀取所有記事資料
public List getAll() {
List result = new ArrayList<>();
Cursor cursor = db.query(
TABLE_NAME, null, null, null, null, null, null, null);
while (cursor.moveToNext()) {
result.add(getRecord(cursor));
}
cursor.close();
return result;
}
// 取得指定編號(hào)的資料物件
public Item get(long id) {
// 準(zhǔn)備回傳結(jié)果用的物件
Item item = null;
// 使用編號(hào)為查詢條件
String where = KEY_ID + "=" + id;
// 執(zhí)行查詢
Cursor result = db.query(
TABLE_NAME, null, where, null, null, null, null, null);
// 如果有查詢結(jié)果
if (result.moveToFirst()) {
// 讀取包裝一筆資料的物件
item = getRecord(result);
}
// 關(guān)閉Cursor物件
result.close();
// 回傳結(jié)果
return item;
}
// 把Cursor目前的資料包裝為物件
public Item getRecord(Cursor cursor) {
// 準(zhǔn)備回傳結(jié)果用的物件
Item result = new Item();
result.setId(cursor.getLong(0));
result.setDatetime(cursor.getLong(1));
result.setColor(ItemActivity.getColors(cursor.getInt(2)));
result.setTitle(cursor.getString(3));
result.setContent(cursor.getString(4));
result.setFileName(cursor.getString(5));
result.setLatitude(cursor.getDouble(6));
result.setLongitude(cursor.getDouble(7));
result.setLastModify(cursor.getLong(8));
// 回傳結(jié)果
return result;
}
// 取得資料數(shù)量
public int getCount() {
int result = 0;
Cursor cursor = db.rawQuery("SELECT COUNT(*) FROM " + TABLE_NAME, null);
if (cursor.moveToNext()) {
result = cursor.getInt(0);
}
return result;
}
// 建立范例資料
public void sample() {
Item item = new Item(0, new Date().getTime(), Colors.RED, "關(guān)于Android Tutorial的事情.", "Hello content", "", 0, 0, 0);
Item item2 = new Item(0, new Date().getTime(), Colors.BLUE, "一只非??蓯鄣男」饭?", "她的名字叫“大熱狗”,又叫\(zhòng)n作“奶嘴”,是一只非??蓯踈n的小狗。", "", 25.04719, 121.516981, 0);
Item item3 = new Item(0, new Date().getTime(), Colors.GREEN, "一首非常好聽的音樂!", "Hello content", "", 0, 0, 0);
Item item4 = new Item(0, new Date().getTime(), Colors.ORANGE, "儲(chǔ)存在數(shù)據(jù)庫(kù)的資料", "Hello content", "", 0, 0, 0);
insert(item);
insert(item2);
insert(item3);
insert(item4);
}
}
~~~
完成數(shù)據(jù)庫(kù)功能類別以后,里面也宣告了一些SQLiteOpenHelper類別會(huì)使用到的資料,開啟“MyDBHelper”類別,完成之前還沒有完成的工作:
~~~
@Override
public void onCreate(SQLiteDatabase db) {
// 建立應(yīng)用程式需要的表格
db.execSQL(ItemDAO.CREATE_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 刪除原有的表格
db.execSQL("DROP TABLE IF EXISTS " + ItemDAO.TABLE_NAME);
// 呼叫onCreate建立新版的表格
onCreate(db);
}
~~~
## 11-4 使用數(shù)據(jù)庫(kù)中的記事資料
完成與數(shù)據(jù)庫(kù)相關(guān)的類別以后,其它的部份就簡(jiǎn)單多了,Activity元件也可以保持比較簡(jiǎn)潔的程式架構(gòu)。開啟在“net.macdidi.myandroidtutorial”套件下的“MainActivity”類別,修改原來自己建立資料的作法,改由數(shù)據(jù)庫(kù)提供記事資料并顯示在畫面。由于所有執(zhí)行數(shù)據(jù)庫(kù)工作的程式碼都寫在“ItemDAO”類別,所以要宣告一個(gè)ItemDAO的字段變量,“onCreate”方法也要執(zhí)行相關(guān)的修改:
~~~
// 宣告數(shù)據(jù)庫(kù)功能類別字段變量
private ItemDAO itemDAO;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
processViews();
processControllers();
// 建立數(shù)據(jù)庫(kù)物件
itemDAO = new ItemDAO(getApplicationContext());
// 如果數(shù)據(jù)庫(kù)是空的,就建立一些范例資料
// 這是為了方便測(cè)試用的,完成應(yīng)用程式以后可以拿掉
if (itemDAO.getCount() == 0) {
itemDAO.sample();
}
// 取得所有記事資料
items = itemDAO.getAll();
itemAdapter = new ItemAdapter(this, R.layout.single_item, items);
item_list.setAdapter(itemAdapter);
}
~~~
完成這個(gè)部份的修改以后,執(zhí)行應(yīng)用程式,如果畫面上顯示像這樣的畫面,數(shù)據(jù)庫(kù)的部份應(yīng)該就沒有問題了。
[](http://www.codedata.com.tw/wp-content/uploads/2015/02/AndroidTutorial5_03_03_01.png)
接下來需要處理新增與修改的部份,同樣在“MainActivity”類別,找到“onActivityResult”方法,參考下列的內(nèi)容修改程式碼:
~~~
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK) {
Item item = (Item) data.getExtras().getSerializable(
"net.macdidi.myandroidtutorial.Item");
if (requestCode == 0) {
// 新增記事資料到數(shù)據(jù)庫(kù)
item = itemDAO.insert(item);
items.add(item);
itemAdapter.notifyDataSetChanged();
}
else if (requestCode == 1) {
int position = data.getIntExtra("position", -1);
if (position != -1) {
// 修改數(shù)據(jù)庫(kù)中的記事資料
itemDAO.update(item);
items.set(position, item);
itemAdapter.notifyDataSetChanged();
}
}
}
}
~~~
最后是刪除記事資料的部份,同樣在“MainActivity”類別,找到“clickMenuItem”方法,參考下列的內(nèi)容修改程式碼:
~~~
public void clickMenuItem(MenuItem item) {
int itemId = item.getItemId();
switch (itemId) {
...
case R.id.delete_item:
if (selectedCount == 0) {
break;
}
AlertDialog.Builder d = new AlertDialog.Builder(this);
String message = getString(R.string.delete_item);
d.setTitle(R.string.delete)
.setMessage(String.format(message, selectedCount));
d.setPositiveButton(android.R.string.yes,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 取得最后一個(gè)元素的編號(hào)
int index = itemAdapter.getCount() - 1;
while (index > -1) {
Item item = itemAdapter.get(index);
if (item.isSelected()) {
itemAdapter.remove(item);
// 刪除數(shù)據(jù)庫(kù)中的記事資料
itemDAO.delete(item.getId());
}
index--;
}
itemAdapter.notifyDataSetChanged();
}
});
d.setNegativeButton(android.R.string.no, null);
d.show();
break;
case R.id.googleplus_item:
break;
case R.id.facebook_item:
break;
}
}
~~~
完成這一章所有的工作了,執(zhí)行應(yīng)用程式,試試看新增、修改和刪除記事資料的功能。因?yàn)橛浭沦Y料都保存在數(shù)據(jù)庫(kù),完成測(cè)試以后,關(guān)閉應(yīng)用程式再重新啟動(dòng),記事資料還是會(huì)顯示在畫面。
- 第一堂
- 第一堂(1)西游記里的那只猴子
- 第一堂(2)準(zhǔn)備 Android Studio 開發(fā)環(huán)境
- 第一堂(3)開始設(shè)計(jì) Android 應(yīng)用程式
- 第一堂(4)開發(fā) Android 應(yīng)用程式的準(zhǔn)備工作
- 第二堂
- 第二堂(1)規(guī)劃與建立應(yīng)用程式需要的資源
- 第二堂(2)設(shè)計(jì)應(yīng)用程式使用者界面
- 第二堂(3)應(yīng)用程式與使用者的互動(dòng)
- 第二堂(4)建立與使用 Activity 元件
- 第三堂
- 第三堂(1)為L(zhǎng)istView元件建立自定畫面
- 第三堂(2)儲(chǔ)存與讀取應(yīng)用程式資訊
- 第三堂(3)Android 內(nèi)建的 SQLite 數(shù)據(jù)庫(kù)
- 第四堂
- 第四堂(1)使用照相機(jī)與麥克風(fēng)
- 第四堂(2)設(shè)計(jì)地圖應(yīng)用程式 - Google Maps Android API v2
- 第四堂(3)讀取裝置目前的位置 - Google Services Location
- 第五堂
- 第五堂(1)建立廣播接收元件 - BroadcastReceiver
- 第五堂(2)系統(tǒng)通知服務(wù) - Notification
- 第五堂(3)設(shè)計(jì)小工具元件 - AppWidget
- 第六堂
- 第六堂(1)Material Design - Theme與Transition
- 第六堂(2)Material Design - RecylerView
- 第六堂(3)Material Design - Shared Element與自定動(dòng)畫效果
