Android - 認識SQLite與資料移動至SD Card

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的系統架構說明課程講義

Android SQLite應用教學

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

 

Dotblogs Tags: ,