程式扎記: [Android 精華文章] SQLite 使用簡單範例

標籤

2011年9月18日 星期日

[Android 精華文章] SQLite 使用簡單範例


轉載自 這裡
Preface :
SQLite 是一個開源的數據庫並在 Android 的 native library 與 application framework 中進行支援. 你可以使用標準的 SQL 語法對 SQLite 進行操作並支援 Prepared statements 與 Transactions. 除此之外, 它還有一個優點也是為什麼它會被手持裝置選擇為內建的數據庫, 就是在 runtime 時它使用的記憶體非常少, 約 250 kbytes.

在 Android 使用 SQLite 並不需要額外安裝或設置. 你只需要透過 SQL 來操作它即可. 透過 Application 提供的幾個有用的類別並可以完成操作, 後面會繼續討論這些類別. 另外在 Android 上使用數據庫操作需要對 I/O 進行操作因而會 slow down 程式的進行, 因此這裡建議使用 AsyncTask 方式來對數據庫進行操作. 相關主題可以參考這裡(Android Background Processing Tutorial for details).

SQLite 支援數據類型如 TEXT (similar to String in Java), INTEGER (similar to long in Java) 與 REAL (similar to double in Java). 所以在插進去數據庫並須做對應的類型轉換. 要注意的是 SQLite 本身並不會進行類型檢查, 因此你可能插入 integer 到 string 的欄位裡.

當你使用 SQLite 建立數據庫表格時, 所有的資料都會被儲存到目錄 "DATA/data/APP_NAME/databases/FILENAME". "DATA" 你可以使用 Environment.getDataDirectory() 來獲取. 而 "APP_NAME" 是你應用程序的名稱, "FILENAME" 則是你數據庫表的名稱. 通常你呼叫 Environment.getDataDirectory() 會返回 SD 卡的位置. 最後 SQLite 數據庫對所有的程序都是 private 也就是說你不能在程序間分享 SQLite 的資料. 但是有替代方案, 就是透過 Content Provider.

SQLiteOpenHelper :
要在 Android 中建立或是更新數據庫, 你可以透過類別 SQLiteOpenHelper. 在這個類別你可以 override 方法 onCreate() 來建立數據庫, 與方法 onUpgrade() 來更新數據庫. 而這兩個方法都會傳入一個類別 SQLiteDatabase.

SQLiteOpenHelper 提供方法 getReadableDatabase() 與 getWriteableDatabase() 來獲取 SQLiteDatabase 物件並進行數據庫 "讀" 與 "寫" 的操作. 而最後在處理 SQLite 數據庫時, 會以 "_id" 當作 primary key 的欄位使用.

SQLiteDatabase and Cursor :
SQLiteDatabase 提供方法 insert())]update() ,)]delete() 與 )]execSQL() 來執行 SQL 與操作數據庫. 詳細的操作使用說明, 請見 API.

查詢的建立可以透過方法 rowQuery(). 該方法接受傳入 SQL 語句 或是透過 query() 來提供介面使用動態的資料建立 SQL 語句. 最後是 SQLiteQueryBuilder.SQLiteQueryBuilder 通常使用於 ContentProviders. 而查詢後返回 Cursor 類別來操作結果數據.

類別 Cursor 代表查詢後的結果數據. 如果你要獲得查詢結果的總數可以使用方法 getCount(). 如果要在查詢數據列表中移動, 你可以使用方法 moveToFirst() 或 moveToNext() 並透過方法 isAfterLast() 知道是否已經走尋完畢. 另外你可以透過類別 SimpleCursorAdapter 來使用 ListView 操作查詢數據 Cursor. 關於 ListView 的操作範例, 你可以參考這裡.

Todo application :
接下來我們會建立一個 Android app "Todo" 來示範 SQLite 的使用. 該 App 允許用戶建立 待處理項目, 並用 SQLite 數據庫儲存這些項目. 而這個 App 將會由兩個 Activities 組成. 一個是用來檢視 待處理項目 清單, 另一個則是用來建立 待處理項目. 而這兩個 Activities 溝通則是透過 Intents.

在開始這個 App 之前, 假設你已經有了對 Android App 開發有了一定的基礎. 如果你是新手上路, 你可以閱讀 Android development tutorial 來對 Android App 開發有初步認識. 另外再開始下面的 App 前, 你可能也需要知道 Android Intents 與 Android ListView 是什麼. 而這個 App 執行後的 GUI 示圖如下 :


首先使用 Eclipse 建立專案 "TodosOverview", 並使用 Activity "TodoDetails". 接著我們來撰寫類別處理 database :
- TodoDatabaseHelper.java :
  1. package de.vogella.android.todos.database;  
  2.   
  3. import android.content.Context;  
  4. import android.database.sqlite.SQLiteDatabase;  
  5. import android.database.sqlite.SQLiteOpenHelper;  
  6. import android.util.Log;  
  7.   
  8. public class TodoDatabaseHelper extends SQLiteOpenHelper {  
  9.     private static final String DATABASE_NAME = "applicationdata";  
  10.   
  11.     private static final int DATABASE_VERSION = 1;  
  12.   
  13.     // Database creation sql statement  
  14.     private static final String DATABASE_CREATE = "create table todo (_id integer primary key autoincrement, "  
  15.             + "category text not null, summary text not null, description text not null);";  
  16.   
  17.     public TodoDatabaseHelper(Context context) {  
  18.         super(context, DATABASE_NAME, null, DATABASE_VERSION);  
  19.     }  
  20.   
  21.     // Method is called during creation of the database  
  22.     @Override  
  23.     public void onCreate(SQLiteDatabase database) {  
  24.         database.execSQL(DATABASE_CREATE);  
  25.     }  
  26.   
  27.     // Method is called during an upgrade of the database, e.g. if you increase  
  28.     // the database version  
  29.     @Override  
  30.     public void onUpgrade(SQLiteDatabase database, int oldVersion,  
  31.             int newVersion) {  
  32.         Log.w(TodoDatabaseHelper.class.getName(),  
  33.                 "Upgrading database from version " + oldVersion + " to "  
  34.                         + newVersion + ", which will destroy all old data");  
  35.         database.execSQL("DROP TABLE IF EXISTS todo");  
  36.         onCreate(database);  
  37.     }  
  38. }  

Based on this helper class we can write the class "TodoDbAdapter" which will provide the functionality to query, create and update todos. The method open() will open the database via the helper class. For updating and creating values we use the "android.content.ContentValues" class. This class allows to store key/values. You use the column names as the key in ContentValues and pass the object to the insert or update method of your database.
接著我們撰寫 "TodoDbAdapter" 來提供數據庫操作. 使用方法 open() 來建立數據庫, 並透過類別 android.content.ContentValues 來封裝數據庫表格的 key/values pairs. 接著我們使用這個類別儲存數據來對數據庫進行 insert 與 update 我們的數據庫 :
- TodoDbAdapter.java :
  1. package de.vogella.android.todos.database;  
  2.   
  3. import android.content.ContentValues;  
  4. import android.content.Context;  
  5. import android.database.Cursor;  
  6. import android.database.SQLException;  
  7. import android.database.sqlite.SQLiteDatabase;  
  8.   
  9. public class TodoDbAdapter {  
  10.   
  11.     // Database fields  
  12.     public static final String KEY_ROWID = "_id";  
  13.     public static final String KEY_CATEGORY = "category";  
  14.     public static final String KEY_SUMMARY = "summary";  
  15.     public static final String KEY_DESCRIPTION = "description";  
  16.     private static final String DATABASE_TABLE = "todo";  
  17.     private Context context;  
  18.     private SQLiteDatabase database;  
  19.     private TodoDatabaseHelper dbHelper;  
  20.   
  21.     public TodoDbAdapter(Context context) {  
  22.         this.context = context;  
  23.     }  
  24.   
  25.     public TodoDbAdapter open() throws SQLException {  
  26.         dbHelper = new TodoDatabaseHelper(context);  
  27.         database = dbHelper.getWritableDatabase();  
  28.         return this;  
  29.     }  
  30.   
  31.     public void close() {  
  32.         dbHelper.close();  
  33.     }  
  34.   
  35.     /** 
  36.      * Create a new todo If the todo is successfully created return the new 
  37.      * rowId for that note, otherwise return a -1 to indicate failure. 
  38.      */  
  39.     public long createTodo(String category, String summary, String description) {  
  40.         ContentValues initialValues = createContentValues(category, summary,  
  41.                 description);  
  42.   
  43.         return database.insert(DATABASE_TABLE, null, initialValues);  
  44.     }  
  45.   
  46.     /** 
  47.      * Update the todo 
  48.      */  
  49.     public boolean updateTodo(long rowId, String category, String summary,  
  50.             String description) {  
  51.         ContentValues updateValues = createContentValues(category, summary,  
  52.                 description);  
  53.   
  54.         return database.update(DATABASE_TABLE, updateValues, KEY_ROWID + "="  
  55.                 + rowId, null) > 0;  
  56.     }  
  57.   
  58.     /** 
  59.      * Deletes todo 
  60.      */  
  61.     public boolean deleteTodo(long rowId) {  
  62.         return database.delete(DATABASE_TABLE, KEY_ROWID + "=" + rowId, null) > 0;  
  63.     }  
  64.   
  65.     /** 
  66.      * Return a Cursor over the list of all todo in the database 
  67.      *  
  68.      * @return Cursor over all notes 
  69.      */  
  70.     public Cursor fetchAllTodos() {  
  71.         return database.query(DATABASE_TABLE, new String[] { KEY_ROWID,  
  72.                 KEY_CATEGORY, KEY_SUMMARY, KEY_DESCRIPTION }, nullnullnull,  
  73.                 nullnull);  
  74.     }  
  75.   
  76.     /** 
  77.      * Return a Cursor positioned at the defined todo 
  78.      */  
  79.     public Cursor fetchTodo(long rowId) throws SQLException {  
  80.         Cursor mCursor = database.query(true, DATABASE_TABLE, new String[] {  
  81.                 KEY_ROWID, KEY_CATEGORY, KEY_SUMMARY, KEY_DESCRIPTION },  
  82.                 KEY_ROWID + "=" + rowId, nullnullnullnullnull);  
  83.         if (mCursor != null) {  
  84.             mCursor.moveToFirst();  
  85.         }  
  86.         return mCursor;  
  87.     }  
  88.   
  89.     private ContentValues createContentValues(String category, String summary,  
  90.             String description) {  
  91.         ContentValues values = new ContentValues();  
  92.         values.put(KEY_CATEGORY, category);  
  93.         values.put(KEY_SUMMARY, summary);  
  94.         values.put(KEY_DESCRIPTION, description);  
  95.         return values;  
  96.     }  
  97. }  

接著我們要往 MVC 的 V 移動, 我們知道 Android 透過 XML 對 GUI 的 Layout 與 上面的元件進行描述. 首先請建立 "listmenu.xml" :
接著我們在 Todo 有分兩種類型 "Reminder" 與 "Urgent", 我們將這兩個字串存在資源檔 "priority.xml" (在 "res/values") 裡 :

接著我們將在 App 有使用的 label 字串或標題存放於資源檔 "strings.xml" :

接著是 "todo_list.xml" 用來描述顯示 todo 清單的 GUI :

接著當我們點擊單一個 todo 項目時, 會有一個 GUI 來表示該項目, 該 GUI 的描述檔 todo_row.xml 如下 :

接著是編輯 todo 項目 GUI 的描述檔 :

接著是兩個 Activities 的代碼 :
- TodoOverview.java :
  1. package de.vogella.android.todos;  
  2.   
  3. import android.app.ListActivity;  
  4. import android.content.Intent;  
  5. import android.database.Cursor;  
  6. import android.os.Bundle;  
  7. import android.view.ContextMenu;  
  8. import android.view.ContextMenu.ContextMenuInfo;  
  9. import android.view.Menu;  
  10. import android.view.MenuInflater;  
  11. import android.view.MenuItem;  
  12. import android.view.View;  
  13. import android.widget.AdapterView.AdapterContextMenuInfo;  
  14. import android.widget.ListView;  
  15. import android.widget.SimpleCursorAdapter;  
  16. import de.vogella.android.todos.database.TodoDbAdapter;  
  17.   
  18. public class TodosOverview extends ListActivity {  
  19.     private TodoDbAdapter dbHelper;  
  20.     private static final int ACTIVITY_CREATE = 0;  
  21.     private static final int ACTIVITY_EDIT = 1;  
  22.     private static final int DELETE_ID = Menu.FIRST + 1;  
  23.     private Cursor cursor;  
  24.   
  25.     /** Called when the activity is first created. */  
  26.     @Override  
  27.     public void onCreate(Bundle savedInstanceState) {  
  28.         super.onCreate(savedInstanceState);  
  29.         setContentView(R.layout.todo_list);  
  30.         this.getListView().setDividerHeight(2);  
  31.         dbHelper = new TodoDbAdapter(this);  
  32.         dbHelper.open();  
  33.         fillData();  
  34.         registerForContextMenu(getListView());  
  35.     }  
  36.   
  37.     // Create the menu based on the XML defintion  
  38.     @Override  
  39.     public boolean onCreateOptionsMenu(Menu menu) {  
  40.         MenuInflater inflater = getMenuInflater();  
  41.         inflater.inflate(R.menu.listmenu, menu);  
  42.         return true;  
  43.     }  
  44.   
  45.     // Reaction to the menu selection  
  46.     @Override  
  47.     public boolean onMenuItemSelected(int featureId, MenuItem item) {  
  48.         switch (item.getItemId()) {  
  49.         case R.id.insert:  
  50.             createTodo();  
  51.             return true;  
  52.         }  
  53.         return super.onMenuItemSelected(featureId, item);  
  54.     }  
  55.   
  56.     @Override  
  57.     public boolean onOptionsItemSelected(MenuItem item) {  
  58.         switch (item.getItemId()) {  
  59.         case R.id.insert:  
  60.             createTodo();  
  61.             return true;  
  62.         }  
  63.         return super.onOptionsItemSelected(item);  
  64.     }  
  65.   
  66.     @Override  
  67.     public boolean onContextItemSelected(MenuItem item) {  
  68.         switch (item.getItemId()) {  
  69.         case DELETE_ID:  
  70.             AdapterContextMenuInfo info = (AdapterContextMenuInfo) item  
  71.                     .getMenuInfo();  
  72.             dbHelper.deleteTodo(info.id);  
  73.             fillData();  
  74.             return true;  
  75.         }  
  76.         return super.onContextItemSelected(item);  
  77.     }  
  78.   
  79.     private void createTodo() {  
  80.         Intent i = new Intent(this, TodoDetails.class);  
  81.         startActivityForResult(i, ACTIVITY_CREATE);  
  82.     }  
  83.   
  84.     // ListView and view (row) on which was clicked, position and  
  85.     @Override  
  86.     protected void onListItemClick(ListView l, View v, int position, long id) {  
  87.         super.onListItemClick(l, v, position, id);  
  88.         Intent i = new Intent(this, TodoDetails.class);  
  89.         i.putExtra(TodoDbAdapter.KEY_ROWID, id);  
  90.         // Activity returns an result if called with startActivityForResult  
  91.           
  92.         startActivityForResult(i, ACTIVITY_EDIT);  
  93.     }  
  94.   
  95.     // Called with the result of the other activity  
  96.     // requestCode was the origin request code send to the activity  
  97.     // resultCode is the return code, 0 is everything is ok  
  98.     // intend can be use to get some data from the caller  
  99.     @Override  
  100.     protected void onActivityResult(int requestCode, int resultCode,  
  101.             Intent intent) {  
  102.         super.onActivityResult(requestCode, resultCode, intent);  
  103.         fillData();  
  104.   
  105.     }  
  106.   
  107.     private void fillData() {  
  108.         cursor = dbHelper.fetchAllTodos();  
  109.         startManagingCursor(cursor);  
  110.   
  111.         String[] from = new String[] { TodoDbAdapter.KEY_SUMMARY };  
  112.         int[] to = new int[] { R.id.label };  
  113.   
  114.         // Now create an array adapter and set it to display using our row  
  115.         SimpleCursorAdapter notes = new SimpleCursorAdapter(this,  
  116.                 R.layout.todo_row, cursor, from, to);  
  117.         setListAdapter(notes);  
  118.     }  
  119.   
  120.     @Override  
  121.     public void onCreateContextMenu(ContextMenu menu, View v,  
  122.             ContextMenuInfo menuInfo) {  
  123.         super.onCreateContextMenu(menu, v, menuInfo);  
  124.         menu.add(0, DELETE_ID, 0, R.string.menu_delete);  
  125.     }  
  126.       
  127.     @Override  
  128.     protected void onDestroy() {  
  129.         super.onDestroy();  
  130.         if (dbHelper != null) {  
  131.             dbHelper.close();  
  132.         }  
  133.     }  
  134. }  

- TodoDetails.java :
  1. package de.vogella.android.todos;  
  2.   
  3. import android.app.Activity;  
  4. import android.database.Cursor;  
  5. import android.os.Bundle;  
  6. import android.util.Log;  
  7. import android.view.View;  
  8. import android.widget.Button;  
  9. import android.widget.EditText;  
  10. import android.widget.Spinner;  
  11. import de.vogella.android.todos.database.TodoDbAdapter;  
  12.   
  13. public class TodoDetails extends Activity {  
  14.     private EditText mTitleText;  
  15.     private EditText mBodyText;  
  16.     private Long mRowId;  
  17.     private TodoDbAdapter mDbHelper;  
  18.     private Spinner mCategory;  
  19.   
  20.     @Override  
  21.     protected void onCreate(Bundle bundle) {  
  22.         super.onCreate(bundle);  
  23.         mDbHelper = new TodoDbAdapter(this);  
  24.         mDbHelper.open();  
  25.         setContentView(R.layout.todo_edit);  
  26.         mCategory = (Spinner) findViewById(R.id.category);  
  27.         mTitleText = (EditText) findViewById(R.id.todo_edit_summary);  
  28.         mBodyText = (EditText) findViewById(R.id.todo_edit_description);  
  29.   
  30.         Button confirmButton = (Button) findViewById(R.id.todo_edit_button);  
  31.         mRowId = null;  
  32.         Bundle extras = getIntent().getExtras();  
  33.         mRowId = (bundle == null) ? null : (Long) bundle  
  34.                 .getSerializable(TodoDbAdapter.KEY_ROWID);  
  35.         if (extras != null) {  
  36.             mRowId = extras.getLong(TodoDbAdapter.KEY_ROWID);  
  37.         }  
  38.         populateFields();  
  39.         confirmButton.setOnClickListener(new View.OnClickListener() {  
  40.             public void onClick(View view) {  
  41.                 setResult(RESULT_OK);  
  42.                 finish();  
  43.             }  
  44.   
  45.         });  
  46.     }  
  47.   
  48.     private void populateFields() {  
  49.         if (mRowId != null) {  
  50.             Cursor todo = mDbHelper.fetchTodo(mRowId);  
  51.             startManagingCursor(todo);  
  52.             String category = todo.getString(todo  
  53.                     .getColumnIndexOrThrow(TodoDbAdapter.KEY_CATEGORY));  
  54.               
  55.             for (int i=0; i
  56.                   
  57.                 String s = (String) mCategory.getItemAtPosition(i);   
  58.                 Log.e(null, s +" " + category);  
  59.                 if (s.equalsIgnoreCase(category)){  
  60.                     mCategory.setSelection(i);  
  61.                 }  
  62.             }  
  63.               
  64.             mTitleText.setText(todo.getString(todo  
  65.                     .getColumnIndexOrThrow(TodoDbAdapter.KEY_SUMMARY)));  
  66.             mBodyText.setText(todo.getString(todo  
  67.                     .getColumnIndexOrThrow(TodoDbAdapter.KEY_DESCRIPTION)));  
  68.         }  
  69.     }  
  70.   
  71.     protected void onSaveInstanceState(Bundle outState) {  
  72.         super.onSaveInstanceState(outState);  
  73.         saveState();  
  74.         outState.putSerializable(TodoDbAdapter.KEY_ROWID, mRowId);  
  75.     }  
  76.   
  77.     @Override  
  78.     protected void onPause() {  
  79.         super.onPause();  
  80.         saveState();  
  81.     }  
  82.   
  83.     @Override  
  84.     protected void onResume() {  
  85.         super.onResume();  
  86.         populateFields();  
  87.     }  
  88.   
  89.     private void saveState() {  
  90.         String category = (String) mCategory.getSelectedItem();  
  91.         String summary = mTitleText.getText().toString();  
  92.         String description = mBodyText.getText().toString();  
  93.           
  94.   
  95.         if (mRowId == null) {  
  96.             long id = mDbHelper.createTodo(category, summary, description);  
  97.             if (id > 0) {  
  98.                 mRowId = id;  
  99.             }  
  100.         } else {  
  101.             mDbHelper.updateTodo(mRowId, category, summary, description);  
  102.         }  
  103.     }  
  104. }  

最後也是該 App 的核心 XML 描述檔 "AndroidManifest.xml" :

3 則留言:

  1. Hi~ 你可以參考: http://www.vogella.de/articles/AndroidSQLite/article.html#todo
    我當初的 Eclipse 專案 已經不在了...Orz

    回覆刪除

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!