2012-12-13

【Android】SQLite - 資料庫的應用

SQLite 是 Android 採用的資料庫,因其小巧,所以適合單機使用。

因為 app 要儲存資料在 SQLite 之前,必須先建立 app 用的資料庫檔案,故 Android 提供了 SQLiteOpenHelper 這個類別幫您管理您 app 的資料庫檔案。

首先,建立自己的 app 的 SQLiteOpenHelper:

 
public class DictionaryOpenHelper extends SQLiteOpenHelper
{
    // 若您變更了資料庫的 schema, 就必須增加 DATABASE_VERSION 的值.
    public static final int DATABASE_VERSION = 1;
    public static final String DATABASE_NAME = "whalintest.db";
    
    SQLite(Context context)
    {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db)
    {
        db.execSQL("CREATE TABLE table1 (field1 VARCHAR(5), field2 VARCHAR(9));");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
    {
        // This database is only a cache for online data, so its upgrade policy is
        // to simply to discard the data and start over
        db.execSQL("DROP TABLE IF EXISTS table1");
        onCreate(db);
    }
 
    @Override
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion)
    {
        onUpgrade(db, oldVersion, newVersion);
    }
}
 


在這類別中,我們 Override 了 3 個 method -- onCreateonUpgrade、和 onDowngrade,onCreate 就是建立您的 app 要使用的資料庫;而當您的 app 增加了新的功能導致資料庫結構也要跟著調整時,要調整的程式碼就寫在 onUpgrade,並且 DATABASE_VERSION 這個變數的值,要比前一版的數字大,Android 在偵測到版本變大時,就會進行資料庫版本升級的動作,在本例是相當偷懶的 -- 直接砍掉重練!! :-P

相對地,Android 偵測到 DATABASE_VERSION 數字較原有的值小時,就會執行 onDowngrade。

然後在程式中進行資料的存取:
 
@SuppressLint("NewApi")
public class MainActivity extends Activity
{
    Button button1;
    public static final boolean DEVELOPER_MODE = BuildConfig.DEBUG;
 
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        if(DEVELOPER_MODE)
        {
            StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectDiskReads()
                                                                            .detectDiskWrites()
                                                                            .detectNetwork()   // or .detectAll() for all detectable problems
                                                                            .penaltyLog()
                                                                            .build());
  
            StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectLeakedSqlLiteObjects()
                                                                    .detectLeakedClosableObjects()
                                                                    .penaltyLog()
                                                                    .penaltyDeath()
                                                                    .build());
        }
      
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
  
        button1 = (Button) findViewById(R.id.button1);
    }

 
    public void onButton1Click(View v)
    {
        Thread thread = new Thread()
                        {
                            @Override
                            public void run()
                            { 
                                super.run();
       
                                SQLite mDbHelper = new SQLite(getBaseContext());
                                // 以寫入模式開啟資料庫
                                SQLiteDatabase db = mDbHelper.getWritableDatabase();

                                // 將要存入的欄位-值 以 ContentValues 做準備
                                ContentValues values = new ContentValues();
                                values.put("field1", "ann");
                                values.put("field2", "huang");
  
                                // 以 insert 存入一筆記錄
                                long newRowId;
                                newRowId = db.insert("table1", null, values);
        
                                // 記得使用完畢後要關閉
                                db.close();
                                mDbHelper.close();
        
                                // --------------------
        
                                mDbHelper = new SQLite(getBaseContext());
                                // 以讀取模式開啟資料庫
                                db = mDbHelper.getReadableDatabase();

                                // db.query(table, columns, selection, selectionArgs, groupBy, having, orderBy);
                                // 不用到的功能或選項就填入 null
                                Cursor rs = db.query("table1", null, "field1=\"ann\" ", null, null, null, null);

                                // 或是您也可以用 rawQuery, 輸入完整的 SQL 語法
                                // 這在查詢 2 個以上的資料表時很好用
                                // db.rawQuery("SELECT a.field1, b.field3, b.field4 FROM table1 a, table2 b \n" + 
                                //             "    WHERE a.field1=b.field2" , null);
        
                                // 記得使用完畢後要關閉
                                rs.close();
                                db.close();
                                mDbHelper.close();
                            }
                        };
        thread.start();
    }
 
}
 


db.query() 中 columns 若為 null 則表全部的欄位,等於一般的 SELECT * FROMselection selectionArgs 就是 SQL 指令中的 WHERE 述句,selection 是欄位,selectionArgs 是要符合的條件,偷懶的寫法是 -- 直接在 selection 填入全部的 WHERE 條件,selectionArgs 則為 null。

另外,還要注意每當下了 db.query() 指令後,就等於開啟了一個查詢結果資料集(query data set) ,要記得將它關閉。如下:
 
// 這裡開啟了一個 query data set
// 我們以 Cursor rs 去 reference 指向這個 data set
Cursor rs = db.query("table1", null, "field1=\"ann\" ", null, null, null, null);
rs.close();

// 重新 query,又開啟了一個 query data set
// 再將 rs 指向這個 data set
rs = db.query("table1", null, "field1=\"bryan\" ", null, null, null, null);
// 注意這裡我沒有執行 rs.close();

// 第三次 query,又開啟了一個 query data set
// 再將 rs 指向這個 data set 
rs = db.query("table1", null, "field1=\"john\" ", null, null, null, null);
rs.close();

// 因為第二次的 query 沒有 close(),所以它一直處在開啟的狀態
// 而我們又將 rs 指向了第三次的 query data set
// 如果您有啟動 StrictMode 抓 bug,就會被 StrictMode 偵測到沒有關閉 query 的警告了
 



要連線資料庫查看資料的儲存狀況:

1. 先開啟一個 Android 模擬器,您會看到模擬器視窗左上角有個數字,我的是 5554

2. 開啟一個 "命令提示字元"視窗(DOS視窗) 以 cd 指令切換到您安裝的 sdk 資料夾的 platform-tools 下,舉例來說,我的 SDK 安裝在 D:\,則為

cd \ android\android-sdk\platform-tools

3. 下達連線模擬器指令

adb -s emulator-5554 shell

4. 連線您應用程式所使用資料庫,則下指令

sqlite3   /data/data/您的 package name/databases/資料庫名稱

這時您會看到多列文字顯示連線的過程


同時它也提示您, 下指令 .help 可取得輔助說明,並且下達的 SQL 指令要以 分號 做結尾

試著下 .dump,倒出資料庫所有資料


沒錯,我的確先前試寫時,執行了 db.insert(),在 table1 這個資料表存了三筆一樣的記錄。

退出 sqlite3 指令為 .exit,或按 CTRL+D

要記得舉凡 "磁碟機(含記憶卡)" 的存取,網線連線...不論管您在執行這些動作的程式碼多簡單,這些 Android 都認為是屬於耗時的工作,為有良好品質的 app,請將資料庫的存取視為耗時工作,將這段指令以 Thread 執行,並請用 StrictMode 來檢驗您的 app 指令有哪些是違反 StrictMode 的政策。