概述
在項(xiàng)目的開發(fā)過程中,有很多功能都需要我們將數(shù)據(jù)保存到我們的手機(jī)本地來提升app的性能,常見的有圖片緩存、消息的離線緩存等;
Android端常見的本地?cái)?shù)據(jù)緩存方式有:
- SharedPreference:緩存輕量級的 key-value 類型的數(shù)據(jù),例如:賬號、密碼等;
- File:用來存儲文件,例如:圖片,音樂等;
- SQLite:用來存儲大量復(fù)雜的關(guān)系型數(shù)據(jù);
我們知道現(xiàn)在的app都是數(shù)據(jù)量比較大的,例如我們每天使用的qq,少的來說也有幾百條消息記錄,多的有可能是上萬條的消息數(shù)據(jù),如果我們使用 SharedPreference 或者 File 來存儲顯然是不合適的,這時(shí)候就應(yīng)該輪到SQLite數(shù)據(jù)庫上場了;

本片文章著重講的是如何使用,關(guān)于 SQLite 的概念大家可以自己去找資料;
關(guān)于SQLiteOpenHelper的使用
Android 官方幫我們封裝了一個(gè)SQLiteOpenHelper類來幫助我們操作數(shù)據(jù)庫;我們只需要寫一個(gè)類繼承它,重寫抽象方法即可;
public class DbOpenHelper extends SQLiteOpenHelper {
public static DbOpenHelper mInstance;
private DbOpenHelper() {
super(AppApplication.mContext, getUserDatabaseName(), null, Constant.DATABASE_VERSION);
}
/**
* @param context 我們可以使用ApplicationContext,因此我們在設(shè)計(jì)單例的時(shí)候可以設(shè)置成無參
* @param name 創(chuàng)建的數(shù)據(jù)庫的名稱
* @param factory 游標(biāo)工廠,直接傳入null就好
* @param version 創(chuàng)建的數(shù)據(jù)庫版本 >= 1
*/
private DbOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
public static DbOpenHelper getInstance() {
if (mInstance == null) {
synchronized (DbOpenHelper.class) {
if (mInstance == null) {
mInstance = new DbOpenHelper();
}
}
}
return mInstance;
}
/**
* 當(dāng)數(shù)據(jù)庫創(chuàng)建(db文件)的時(shí)候調(diào)用(此處我們執(zhí)行當(dāng)前版本所有的表的創(chuàng)建語句)
*
* @param db 數(shù)據(jù)庫對象
*/
@Override
public void onCreate(SQLiteDatabase db) {
Log.e("TAG", "-----onCreate-----");
}
/**
* 當(dāng)數(shù)據(jù)庫有版本更新的時(shí)候調(diào)用,在這里我們根據(jù)不同的版本對數(shù)據(jù)庫進(jìn)行相關(guān)修改
*
* @param db 數(shù)據(jù)庫對象
* @param oldVersion 數(shù)據(jù)庫舊版本
* @param newVersion 數(shù)據(jù)庫更新
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.e("TAG", "-----onUpgrade-----");
}
/**
* 拼接生成不同的db文件(如果是IM項(xiàng)目,不同的賬號登錄應(yīng)該對應(yīng)不同的db文件)
*
* @return
*/
private static String getUserDatabaseName() {
return Constant.DATABASE_NAME + "_demo.db";
}
}
相關(guān)方法的解析大家可以查看注釋,還是比較容易理解的,這里我們需要注意的是默認(rèn)的數(shù)據(jù)庫db文件保存的位置為 /data/data/'項(xiàng)目包名'/databases/***.db,如果使用模擬器測試,最好將模擬器版本控制在5.0及以下,這樣可以直接查看文件,否則需要自己修改權(quán)限;
DbManager 管理/執(zhí)行 sql 語句
在打開了數(shù)據(jù)庫后,通常我們會再建立一個(gè)數(shù)據(jù)庫管理工具類 DbManager,來管理和執(zhí)行我們的業(yè)務(wù)相關(guān)的SQL,這樣有助于我們管理項(xiàng)目,同樣我們可以將其設(shè)置為單例模式,我們在DbManager類中,通過我們自己定義的DbOpenHelper類,調(diào)用getWritableDatabase()或者getReadableDatabase()方法,拿到數(shù)據(jù)庫讀寫操作的SQLiteDatabase對象來執(zhí)行SQL語句,個(gè)人比較推薦大家使用sql語句,DbManager 類里面就是一些數(shù)據(jù)的增刪改查的方法,這里就不貼代碼了,在文章的末尾會給出項(xiàng)目的github地址;
還有一點(diǎn)就是數(shù)據(jù)庫的名稱、字段名稱在sql語句中出現(xiàn)的頻率非常的高,這里推薦大家給不同的表都建立一個(gè) Dao 類,將 數(shù)據(jù)庫的表名稱,字段名稱寫成靜態(tài)成員變量的形式,這樣可以避免我們在寫 sql 語句的時(shí)候發(fā)生數(shù)據(jù)庫表名稱、字段名稱拼寫錯(cuò)誤的問題,同時(shí)我們可以在此類里面寫入對應(yīng)表的增刪改查操作(實(shí)際還是引用DbManager類里面的方法),在具體的業(yè)務(wù)的地方,直接調(diào)用此對象,這樣也可以讓我們的代碼更加明確,例如:
public class UserDao {
// 將數(shù)據(jù)庫表中的相關(guān)字段名稱定義到這里,防止拼寫錯(cuò)誤
public static final String TABLE_NAME = "users";
public static final String COLUMN_NAME_ID = "id";
public static final String COLUMN_NAME_USER = "user_name";
public static final String COLUMN_NAME_PASSWORD = "password";
public static final String COLUMN_NAME_EMAIL = "email";
public void insert(User user) {
DbManager.getInstance().insertUser(user);
}
public ArrayList<User> selectAll() {
return DbManager.getInstance().selectAllUsers();
}
public void updateByName(User user) {
DbManager.getInstance().updateUserByName(user);
}
public void deleteByUserName(String userName) {
DbManager.getInstance().deleteUserByName(userName);
}
}
關(guān)于db文件
類似IM之類的app,通常表的結(jié)構(gòu)是已經(jīng)擬定好了的,用于區(qū)分不同賬號的最好的方式就是根據(jù)登錄的用戶,創(chuàng)建單獨(dú)的db文件,因?yàn)?code>SQLiteOpenHelper 類打開的是一個(gè)db文件,在用戶登錄的時(shí)候判斷創(chuàng)建/打開具體的db文件:

具體的寫法可以參考上面類中的
getUserDatabaseName() 方法;
關(guān)于數(shù)據(jù)庫的版本更新
隨著業(yè)務(wù)改變,應(yīng)用的版本也在更新,如果在業(yè)務(wù)更新的同時(shí),數(shù)據(jù)庫表的數(shù)據(jù)結(jié)構(gòu)發(fā)生了修改,或者增加了新表(通常只有增加表或字段,基本不會刪除表和字段),我們需要將我們的數(shù)據(jù)庫的版本往上增加,此時(shí):
- 新用戶:調(diào)用 onCreate 方法,在此方法內(nèi)完成數(shù)據(jù)庫表的創(chuàng)建;
-
舊版本用戶: 調(diào)用 onUpgrade 方法,在此方法內(nèi)判斷舊版的數(shù)據(jù)庫版本,然后逐版本的更新數(shù)據(jù)庫中的表,直至更新到當(dāng)前新安裝的數(shù)據(jù)庫的版本;
@A@
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.e("TAG", "-----onUpgrade-----");
if (oldVersion < 2) {
// 刪除原來的表,新建數(shù)據(jù)庫表(基本不會有此操作)
String sql = "drop table user";
db.execSQL(sql);
db.execSQL(USER_CREATE_TABLE);
}
if (oldVersion < 3) {
// 增加列
Log.e("TAG", "-----------onUpgrade--------- 增加 email 列");
String sql = "alter table " + UserDao.TABLE_NAME + " add " + UserDao.COLUMN_NAME_EMAIL + " text";
db.execSQL(sql);
}
if (oldVersion < 4) {
// 刪除列(基本不會有此操作)
// SQLite不支持刪除列操作,需要進(jìn)行以下操作來完成
Log.e("TAG", "-----------onUpgrade--------- 刪除 password 列");
// 1.創(chuàng)建一張臨時(shí)表
String sql = "create table temp as select " + UserDao.COLUMN_NAME_ID + ", " + UserDao.COLUMN_NAME_USER + ", " + UserDao.COLUMN_NAME_EMAIL + " from " + UserDao.TABLE_NAME + " where 1 = 1";
db.execSQL(sql);
// 2.刪除原來的表
sql = "drop table " + UserDao.TABLE_NAME;
db.execSQL(sql);
// 3.修改臨時(shí)表的名稱
sql = "alter table temp rename to " + UserDao.TABLE_NAME;
db.execSQL(sql);
}
if (oldVersion < 5) {
// 增加表
Log.e("TAG", "-----------onUpgrade--------- 增加表 performance");
db.execSQL(PERFORMANCE_CREATE_TABLE);
String sql = "insert into " + PerformanceDao.TABLE_NAME + "(" + PerformanceDao.COLUMN_NAME_USER_ID + ", " + PerformanceDao.COLUMN_NAME_SALARY + ") values(1, 20.00)";
db.execSQL(sql);
}
}
總結(jié)
本案例主要還是偏向于SQLite數(shù)據(jù)庫的使用而不是原理解析,主要是想先知其然,然后知其所以然,如果想要深入了解的,大家可以google下,這方面的資料還是比較多的;
其實(shí)Android中SQLite用起來還是比較簡單的,不需要向Java中其它數(shù)據(jù)庫連接使用jdbc一樣,直接通過 SQLiteOpenHelper 類就可以幫助我們很快速的進(jìn)行操作數(shù)據(jù)庫;
