Android - 認識SQLite與資料移動至SD Card
學習Android的開發裡,它與iPhone、WP7有一個很不一樣的地方,就是可以選擇性地儲存或移動安裝的程式與資源檔,
到SD Card或固定手機裡面,這是其他二個手機系統沒有的功能。雖然說WP7裡有Isolated Stroage但實際程式安裝進去之後,
*.xap檔被放在那裡我們是不知道的(當然有些工具解開之後就可以看到),也因為這個原因,Android通常就會被拿來把一些下
載到的*.apk放置記憶卡中,直接進行安裝(或是使用HTC sync安裝);或是拿去當隨身碟把一些資料給拿出來進行分析等。
不過這些只是題外話,有關安全性的處理,我相信網路上還有很多值得去了解的文件,此篇主要說明的還是在於
學習Android開發時,一定要知道SQLite的操作與應用,以及如何把資料如何移動到SD Card中。
〉Android針對SQLite的操作類別:
(a) SQLiteOpenHelper:
Android提供操作SQLite的能力,但實際操作的是一個SQLiteDataBase物件,而SQLiteOpenHelper類別的任務是什麼呢?
它主要的任務是負責管理資料庫檔案的建立與版本控制。其中,該類別可以輕鬆的為Content Provider進行實作,讓外部
程式也可以分享到資料庫的內容。以下將簡單介紹實作SQLiteOpenHelper中幾個需要了解的地方:
a-1. SQLiteOpenHelper(context, database name, factory, database version)
SQLiteOpenHelper的建構子,負責建立一個Helper物件來建立、開啟與管理一個SQLiteDatabase。為何要介紹它呢?
因為在實作Helper類別時,name通常使用的是一個資料庫檔案(example.db),也是一個組合過的路徑(/mnt/data/sqlite/example.db),
如果沒有使用過SQLite的話,這是要特別注意的地方。
a-2. onCreate(SQLiteDatabase db)
啟動於資料庫建立時所執行的方法。代表會重新建立一個新的資料庫,通常會在此階段把資料庫基本的Schema、Table或相關
設定一併處理完畢。此方法在執行時,如遇到DB已存在,則進入onOpen()方法;如不存在則會先建立一個DB;
a-3. onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
此方法發生於當資料庫版本不一致的時候,即觸發。那該方法主要任務要做什麼呢?通常知道資料庫版本不同,要進行資料的
覆蓋或重建都是需要好好考量的,因為有可能讓資料遺失或不一致。因此,可以參考<Android開發筆記-建立SQLite實作類別>的作法。
a-4. onOpen(SQLiteDatabase db)
當資料庫被開啟時則進入此方法。會再配合SQLiteOpenHelper提供的二個方法有相呼應:getReadableDatabase()與getWritableDatabase();
二個方法分別取得唯讀(Read-only)與可編輯(Write)的資料庫實體。然而,這二個方法即時觸發onCreate、onUpgrade或onOpen的來源者,
所以上述介紹的幾個重要的方法,則真正被呼叫時是在getWritableDatabase()/getReadableDatabase()被使用時。
(b) SQLiteDataBase:
Android程式實際操作的真正實體(db),提供所有操作SQLite的功能,包括:新增、修改、刪除、查詢等,協助操作裡面的資料,
至於裡面的資料要怎麼操作,SQLiteDataBase已提供一些已經包裝好的方法,當然也包括了指定SQL指令來操作資料的內容,
除此之外,也支援Transcation的使用。beginTransaction()、setTransactionSuccessful()、endTransaction();以下將一一介紹:
b-1. execSQL(String sql)
專門執行單一非Select的SQL指令或是其他操作資料庫的指令,例如:建立資料表的指令、或是操作權限等相關的管理類的指令。
b-2. rawQuery(String sql, String[] selectionArgs)
如果今天針對要操作的資料,不單單只有一個Table而已時,即可以透過這個方法就像過去操作.NET的SQLCommand物件方式,
把SQL指令先撰寫好,交由SQLiteDatabase來操作資料。回傳值為:Cursor(其中也可以使用SQLiteCursor,它是Cursor的實作類別)。
b-3. query/insert(String table, String nullColumnHack, ContentValues values)/update/delete
這是包裝好針對一個Table進行基本的新增、修改、刪除與查詢的固定方法,其使方法非常容易,透過指定的Table與相關的參數,
包括:select的欄位、where的參數(使用[?] 做為參數取代值)、Sort等,讓開發人員可以快速取得資料,但如果需要比較複雜的查詢,
那就建議使用上述的rawQuery()來執行。它一樣回傳一個Cursor物件。至於新增、修改與刪除的部分呢,將配合下方的物件來使用。
b-4. ContentValues/Cursor
在新增、修改、刪除的方法使用時,如果是使用上述已包裝好的方法,例如:insert(String table, String nullColumnHack, ContentValues values),
則一定要了解ContentValues與ContenResolver的使用方法。並且,這三個方法的使用,都會回傳成功影響的比數,如失敗則為:-1。
b-4-1. ContentValues專門用於欲操作的資料內容。透過put的方式操作內容值,這與過去直接操作data table或data row是比較不同的。
不過它相似於key/value的操作方式,因此,當在使用put(String key, object value)時,key則需要對應到table的column name才行。
b-4-2. Cursor則是操作查詢出來的結果,提供了相當豐富的方法與屬性,協助開發人員擷取資料的內容與移動索引值。
(c) SQLiteCursor:
它是實作Cursor的一個類別,主要承接從SQLiteDatabase查詢出來的資料,它被擷取出來之後,則與資料庫內的資料採不同步的方式,
這也就是說,當query出來的結果被放入SQLiteCursor之後,它就像是一個Cache,後來如果其他程式段改了DB中的資料,則SQLiteCursor
並不會被更新,需要重新查詢(requery())才有辦法取得。
然而,SQLiteCursor類別提供很多便利的方法,包括:getCount()取得欄位數、getDatabase()取得來源資料庫等方法,操作起來如同
使用.NET中的TableRow與TableColumn的使用概念。
(d) SQLiteStatement:
該類別提供一個事先編譯(pre-compiled)SQL指令,讓該SQL指令可以重覆針對SQLiteDatabase使用時,不需要每次都重新編譯,
常見的例子:連續性新增資料。但要特別注意的:"它無法回傳多筆資料(rows),只能1對1的資料集合被回傳"。另外,在使用它時,
不需要new,只需直接使用compileStatement(String)語法即可。它也是非同步的物件,要同步都需要自己實作。
(e) SQLiteQueryBuilder:
如果透過String.format來組合SQL指令覺得不方便的話,可以使用SQLiteQueryBuiler類別來進行查詢指定的建立。
它提供透過設定屬性與方法的方式,組合要查詢的相關SQL指令(buildQuery)。也有提供豐富的, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String)" target=_blank>, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String)" target=_blank>, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String)" target=_blank>buildUnionSubQuery等方法,
然而,它也有提供如同SQLiteDatabase所包裝好的query方法。其實使用起來,除非我操作的指令是非常簡單的,
不然我還是習慣使用SQLiteQueryBuilder或自訂SQL指令來達成結果。
以上是介紹有關SQLite在Android上操作的類別,目前只是針對學習到的部分做一些簡單的整理,但還有更多類別沒有介紹到,
可以參考<android.database.sqlite>。接下來往下介紹一些實作時要注意的地方。
〉移動到SD Card:
在Android開發時,使用模擬器開發的時候,專案所產生的資料,包括:apk、資源檔、資料庫,其實內鍵都是被放於手機記憶體中,
例如:「/data/data/<your namespaces>/」下。因此,當今天使用的資料庫夠大時,僅存於手機記憶體中將會造成整個效能相對較差。
為了解決這樣的問題,我們可以把它資料庫先放入專案裡,等到程式安裝完成,啟動時再將資料由記憶體移動到SD Card之中。
[實作方法]
(1) 先將資料庫檔案(example.db)放置於專案的「/raw」資料夾中;(放此資料夾代表資源不需要被編譯)
(2) 透過FileOutputStream將資料從「/data/data/<your namespaces>/」移到SD Card中;
1: //建立一個Copy檔案的方法
2: private void copyDBtoSDCard() {
3: try {
4: //取得SD Card路徑
5: String tSDCardPath = android.os.Environment.getExternalStorageDirectory().getAbsolutePath()
6: //指定SD Card與目標目錄名稱
7: File tDataPath = new File(tSDCardPath+"/mydb/");
8: //指定實際Database路徑
9: String tDBFilePath = tDataPath + "example.db";
10: File tFile = new File(tDBFilePath);
11: //識別指定的目錄是否存在,false則建立;
12: if (tDataPath.exists() == false) {
13: tDataPath.mkdirs();
14: }
15: //識別指定的檔案是否存在,false則建立;
16: if (tFile.exists() == false)
17: {
18: //先使用InputStream從raw中把example.db資料庫給讀進來;
19: InputStream tISStream = getResources().openRawResource(R.raw.exampledb);
20: //new一個OutputStream物件,並且指定寫入的路徑;
21: FileOutputStream tOutStream = new FileOutputStream(tDBFilePath);
22: //指定資料庫檔案的size
23: byte[] tBuffer = new byte[5120];
24: int tCount = 0;
25: while ((tCount = tISStream.read(tBuffer)) > 0)
26: {
27: tOutStream.write(tBuffer, 0, tCount);
28: }
29: tOutStream.close();
30: tISStream.close();
31: }
32: }
33: catch (Exception e)
34: {
35: Log.i("CopyDBException",e.getMessage());
36: }
37: }
============
學習Android的時候,針對SQLite的處理,其實很多時間都是花在對語法的熟悉度,因此,
建議在學習使用SQLite時,要特別注意處理Exception的部分,可以加快你在開發時,更容易找到可能的錯誤。
至於要不要實作SQLiteOpenHelper的話,我的建議是要,主要考量的點有二點:
(1) 可以為特殊需求在實作每一個建立的資料庫實體;
(2) 提供資料庫實體的連線與操作完整控制;(因為可以為資料庫實體在關閉與開啟時都加上自己需要的任務)
以上是我分享一些學習到的心得。
References:
‧[Android 教學] Android的系統架構說明課程講義
‧Unzipping Files with Android (Programmatically)
‧How to download file/image from url to your device
‧Android開發筆記-建立SQLite實作類別 (必看)
‧ContentResolver (實作SQLite與ContentProvider時,要看的元件)
‧Android How to check network status(Both Wifi and Mobile 3G) (必要)
‧Android Programming Tutorials