SQLite Database
SQLite is an embedded relational database engine. Its developers call it a self-contained, serverless, zero-configuration and transactional SQL database engine. 其實只需要記住SQLite不需要數據庫服務器,不需要額外配置,其數據庫本身就是一個文件的一個小型數據庫。 通常情況下在android中創建數據庫,系統會在"DATA/data/APP_NAME/databases".目錄爲我們創建數據庫文件。
SQLite BasicDataTypes
SQLite支持的數據類型有很多,但他們總共可以分爲以下幾類:
-
desc <table> //查看錶結構
-
select * from <table> //查詢所有更
-
select , from table ;//查看指定列
-
select distinct , from table ;//非重複查詢
-
insert into users(_id,username,password) select * from users;//複製
-
select username from users where username like 'S%' ;//非重名字首字母爲大寫S的用戶
-
select username from users where username like '__S%' ;//非重名字第三個字母爲大寫S的用戶
-
select * from users where _id in(001,220,230);
-
select * from user order by _id;//以id的順序排列
-
select * from user order by _id desc;//以id反的順序排
複製代碼
常用Select語句
-
desc <table> //查看錶結構
-
select * from <table> //查詢所有更
-
select , from table ;//查看指定列
-
select distinct , from table ;//非重複查詢
-
insert into users(_id,username,password) select * from users;//複製
-
select username from users where username like 'S%' ;//非重名字首字母爲大寫S的用戶
-
select username from users where username like '__S%' ;//非重名字第三個字母爲大寫S的用戶
-
select * from users where _id in(001,220,230);
-
select * from user order by _id;//以id的順序排列
-
select * from user order by _id desc;//以id反的順序排
複製代碼
圖形環境
很少有人直接在程序當中直接編寫SQL語句,麻煩且容易出錯。所以最好應該先在一個圖形化的環境把SQL語句寫好,測試好再加入到代碼當中。 而且,SQLite數據庫本身就是一個獨立的文件,很容易從模擬器上抓取出來。可以使用圖形化界面進行分析,和修改 etc.
SQLite expert 是個簡單小巧的SQLite數據庫管理軟件,有免費版和,收費版兩種,一般來講免費版足以應付我們需求。
sqliteexpert官方網址
軟件界面截圖
使用SQLite expert 創建一張table,並讓它爲我們生成所需的sql語句
下面的例子,我們創建一張表用來存儲用戶的用戶名和密碼;數據庫名"users",表名"user_accounts",有三個列:row_id INTEGER自增類型的主鍵,usernameTEXT類型 用來保存用戶名,passwordTEXT類型,用來保存用戶密碼;
創建表格
爲了創建一張table 首先要有一個數據庫文件,點擊"File"->""New Database然後選擇文件的路徑,點擊OK便創建了一個數據庫文件 創建完數據庫之後創建表格,右擊數據庫文件名選擇"New Table"如下圖所示:
這時我們便進入了"設計狀態",填寫表名(user_accounts),點擊"Add",在Name中填寫"row_id",Type選擇Integer,如下圖所示:
因爲row_id這個列比較特殊是我們的索引的主鍵,所以還要點擊"Index",之後會看到下圖:
左邊的Available Fields 中有我們剛剛創建的row_id,點擊"Add"把row_id加入右邊的Index Fields當中。這時上面的Primary和AutoIncrement會變成可先狀態 ,勾選Primary和AutoIncrement點擊OK;
再回到Field當中創建其它兩列,分別爲username類型爲TEXT,password類型也爲TEXT,但不需要創建Index; 完成之後點擊Apply,這樣我們便使用SQLite expert創建一張表格,點擊DDL,我們可以看到SQLite expert已經爲我們生成好了生成這張表格所需的SQL語句:
在android中需要代碼生成表格時,只要把這段代碼複製過去,就可以了。
增加用戶
點擊SQL,執行以下SQL語句,爲表格增加一個用戶:
-
INSERT INTO user_accounts(row_id,username,password) VALUES(001,'John','abcdef');
複製代碼
點擊Data會發現數據庫裏邊增加了一個用戶名爲John的用戶。爲了練習,我們不妨再增加兩個用戶,David和Sarah
-
INSERT INTO user_accounts(row_id,username,password) VALUES(002,'David','123456');
-
-
INSERT INTO user_accounts(row_id,username,password) VALUES(003,'Sarah','00000000');
複製代碼
刪除用戶
執行下面的語句刪除用戶David:
-
DELETE FROM user_accounts WHERE username = 'David';
複製代碼
修改密碼
執行以下語句修改Sarah的密碼:
-
update user_accounts SET password='666666' WHERE username = 'Sarah';
複製代碼
查看所用戶信息可以使用如下語句查看錶內所有用戶的信息:
-
SELECT * FROMM user_accounts;
複製代碼
一般來講select * 的語句只在測試的時候時候,在正式代碼中不推薦使用。
查看指定列的內容執行以下語句查看所有用戶的用戶名和密碼:
-
SELECT username,password FROM user_accounts;
複製代碼
這時個就發現row_id列沒有顯示出來。
查詢特定條件的信息
SQL可以通過給定查詢條件進行精確查找,比如我們只需要John的密碼。就可以使用這樣的語句
-
SELECT password FROM user_accounts WHERE username = 'John';
複製代碼
以下流程圖,來源於SQLite官方文檔
Create Table Statement
column-def
type-name
column-constraint
table-constraint
foriegn-key-clause
Insert Statement
Delete Statement
qualified-table-name
Update Statement
qualified-table-name
Select Statement
select-core
result-column
join-source
single-source
join-op
join-constraint
ordering-term
compound-operator
Dealing with large amount of data分頁當數據庫數據量很大時,分頁顯示是個很簡單且符合人們習慣的一種處理方法。
獲取數據行總數:
最簡單的方法是:SELECT所有的行,再調用curosr.getCount() ;取得行數,但這樣效率會很低。 SQLite爲我們提供了一個函數很容易查出總共有多少行。有個名爲cet6_table的表格,我們想知道總共有多少行的word;可以使用如下 語句來完成:
-
SELECT count(word) AS number FROM cet6_table;
複製代碼
count()函數爲我們返回一個Int整形,代表有多少行數據。返回的列的名字叫count(word),爲了方便閱讀和處理用as number給這個列取個 別名number;
-
SELECT [word],[explanation] FROM cet6_table ORDER BY word LIMIT 100 OFFSET 200"
複製代碼
上語句,返回自第200行的最多100條數據。分頁時我們只要修改offset 後面的數據即可取得對應頁面的數據。
詳情見示例程序!
多線程自帶數據庫
有些程序比如,字典,輸入法等程序會在apk中帶有數據庫。思路很簡單,把預先準備好的數據庫放入asset或者raw目錄當中 使用時複製到對應的手機目錄上;再打開。以下代碼會把raw目錄中的名爲cet6的數據庫文件寫入sd卡根目錄並打開。 對於一些常用或者比較重要的數據建議放在程序的私有目錄當中。
-
public void init(Context context) {
-
try {
-
String databasePath = Environment.getExternalStorageDirectory().getAbsolutePath();
-
String databaseFilename = databasePath + "/" + DATABASE_FILENAME;
-
File dir = new File(databasePath);
-
if (!dir.exists()){
-
dir.mkdir();
-
}
-
if (!(new File(databaseFilename)).exists()) {
-
InputStream is = context.getResources().openRawResource(
-
R.raw.cet6);
-
FileOutputStream fos = new FileOutputStream(databaseFilename);
-
byte[] buffer = new byte[1024];
-
int count = 0;
-
while ((count = is.read(buffer)) > 0) {
-
fos.write(buffer, 0, count);
-
}
-
fos.flush();
-
fos.close();
-
is.close();
-
}
-
mCet6Db = SQLiteDatabase.openOrCreateDatabase(databaseFilename,
-
null);
-
} catch (Exception e) {
-
e.printStackTrace();
-
}
-
}
複製代碼
單詞提示,IME使用AutoCompleteText + SQLite很容易實現類似於baidu ,Google等搜索時出現的提示文字。 輸入法也是同樣的原理,以拼音或五筆等作爲索引,非常容易實現。可以加一個列標明一個字被選擇的次數, 在select的時候可以按照這個列來排序,這樣可以保存用戶的習慣;作出來的輸入法將更人性化。因爲輸入法的查詢量比較大
可以考慮把數據庫文件放入內存當中,SQLite本身是支持內存數據庫的。但android中封裝上沒有提供這一功能,而且SQLiteDataBase類 很多核心功能都是通過JNI實現的,所以要把數據庫搬入內存還需編寫c/c++的代碼。也可以考慮使用Lucene的全文搜索功能來實現。Date
SQLite有很多函數支持日期的方便處理。
-
%d day of month: 00
-
%f fractional seconds: SS.SSS
-
%H hour: 00-24
-
%j day of year: 001-366
-
%J Julian day number
-
%m month: 01-12
-
%M minute: 00-59
-
%s seconds since 1970-01-01
-
%S seconds: 00-59
-
%w day of week 0-6 with Sunday==0
-
%W week of year: 00-53
-
%Y year: 0000-9999
-
%% %
複製代碼
SQLite支持以下格式的日期:
-
YYYY-MM-DD
-
YYYY-MM-DD HH:MM
-
YYYY-MM-DD HH:MM:SS
-
YYYY-MM-DD HH:MM:SS.SSS
-
YYYY-MM-DDTHH:MM //T 就是字母T,用於分開日期和時間
-
YYYY-MM-DDTHH:MM:SS
-
YYYY-MM-DDTHH:MM:SS.SSS
-
HH:MM
-
HH:MM:SS
-
HH:MM:SS.SSS
-
now
-
DDDDDDDDDD //is the Julian day number expressed as a floating point value.
複製代碼
strftime('%Y-%m-%d', column_name)方法可以很方便的對日期進行格式化,而不用Java對String進行再處理。
ContentValues 不支持直接把datetime('now')之類函數作爲值插入數據庫。 比如想把當前系統時間插件數據庫使用ContentValues:
-
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-
Date date = new Date();
-
ContentValues initialValues = new ContentValues();
-
initialValues.put("date_created", dateFormat.format(date));
-
long rowId = mDb.insert(DATABASE_TABLE, null, initialValues);
複製代碼
或者如附帶程序記事本程序一樣使用exeSQL(sql);
SqliteOpenHelper
上流程圖來自O'REILLY 視頻公開課"Developing Android Applications with Java"中的視頻截圖
SqliteOpenHelper是android框架爲我們提供的一個數據庫助手類。對於數據庫的操作一般都是從這個類繼承。它主要負責數據庫的創建,打開,更新 等一些操作。
Singleton DAO
很多有j2ee背景的開發者都喜歡建立一個單例模式的數據庫操作類,專門處理數據庫之間的交流。也有很多人把這個習慣帶到的Android的開發當中。 而且我們也經常需要在幾個Activity,Service同時訪問一個數據庫文件,所以經常能看到類似於這樣的代碼:
-
public class DbAccess {
-
-
class DbHelper extends SQLiteOpenHelper {
-
//此處代碼被省略
-
}
-
private static DB_NAME = "users";
-
private mDbHelper = null;
-
private static mDbAccess;//to make sure there is only one instance exist;
-
-
private DbAccess(Context context){
-
//private constructer;
-
if(mDbHelper == null){
-
mDbHelper = new DbHelper(context,DB_NAME);
-
}
-
}
-
-
public DbAccess getInstance(Context context){
-
if(mDbAccess == null){
-
mDbAccess = new DbAccess(context);
-
}
-
return mDbAccess;
-
}
-
-
//部分代碼被省略
-
//insert
-
//delete
-
//update
-
//query
-
}
-
-
public class ActivityA extends Activity{
-
@Override
-
protected void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
dao = DbAccess.getInstance(this);
-
////部分代碼被省略
-
}
-
////部分代碼被省略
-
}
複製代碼
程序的流程應該是這樣的:
假設ActivityA創建了Dao的對象,又開啓了ServiceA,A使用了一段時間再跳轉到了ActivityB與B再接着使用同一個Dao對象。ActivityA創建Dao對象的時候 把自己的引用向上轉型成一個Context對象,Dao又用這個context創建了SQLiteOpenHelper,所以SQLiteOpenHelper就持有了ActivityA的引用。當ActivityA被銷燬 之後因爲單例的Dao保留有ActivityA的引用,所以ActivityA不能被GC所回收,造成整個Acitivity內存的泄漏。
解決方案
1,最簡單的,不使用單例模式,因爲SQLiteOpenHelper保證了數據庫的單例,所以實際上我們只是創建了多個SQLiteOpenHelper的對象,數據庫 對象本身還是單例的。缺點:會有一些重複的代碼,並生成多個SQLiteOpenHelper的子類對象。
2,把Dao對象放入Application當中,在Application的onCreate()中使用Application的Context進行初始化。優點,Application對象的創建在Activity和Service之前 所以保證了,Activity和Service等等使用時數據庫已經準備好了。缺點:Application的生命週期是最長的,並且Android會盡量不讓程序關閉,也就是儘量不
去銷燬Application對象,所以數據庫一直保持打開狀態,依靠Application的onDestroy()關閉數據庫,不確定的因素比較大,並且因爲打開數據庫的時候直接 影響程序啓動時間。
3,先看一個問題,爲什麼SQLiteOpenHelper需要一個Context對象?查看SQLiteOpenHelper的源碼發現如下代碼:
-
SQLiteDatabase db = null;
-
try {
-
mIsInitializing = true;
-
String path = mContext.getDatabasePath(mName).getPath();
-
db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY);
-
if (db.getVersion() != mNewVersion) {
-
throw new SQLiteException("Can't upgrade read-only database from version " +
-
db.getVersion() + " to " + mNewVersion + ": " + path);
-
}
-
onOpen(db);
-
Log.w(TAG, "Opened " + mName + " in read-only mode");
-
mDatabase = db;
複製代碼
所以我們可以自己仿照SQLiteOpenHelper寫一個不需要Context的助手類,大constructer當中把路徑傳進行,而不是一個context對象。缺點:需要考慮多線程同步等 很多因素,有一定的複雜度。
4,在創建Dao對象時,不使用Activity的引用作爲Context,而是把Application作爲Context傳入Dao的構造函數中進行創建。優點,不會影響程序的啓動時間。 缺點:對於數據庫的關閉還是不確定。
5,在第四種方案的基礎上加入計數器。第調用getInstance計數器自增,再加入一個release()方法,計數器自減,當計數器爲0時,關閉數據庫(或者當計數器爲零且 過一段時間沒有連接時關閉)。