というわけで今回はAndroidの3つ目のデータ永続化法である、「データベース」の扱い方を紹介したいと思う。
Androidでは軽量データベースでおなじみのSQLiteが使われている。
スキーマとコントラクトの定義
まずはスキーマとコントラクトを定義する必要がある。まずはスキーマ。
CREATE TABLE messages ( _ID INTEGER PRIMARY KEY, messages_id TEXT, message TEXT );_ID というカラムはAndroidのフレームワーク側で利用されるカラムなので、暗黙の了解として定義しておこう。ちなみにこのカラムを定義しなくてもなんとなく動いたけど、定義しておくに越したことはないだろう。
次にコントラクト。
MessagesContract.java
package com.example.myfirstapp; import android.provider.BaseColumns; public class MessagesContract { /** * インスタンス化防止 */ private MessagesContract() { } public abstract class Messages implements BaseColumns { // テーブル名 public static final String TABLE_NAME = "messages"; // 「_ID」はandroidアプリのお決まりらしいので変更または削除しないこと。 public static final String _ID = "_ID"; // カラム名1 public static final String COLUMN_NAME_MESSAGES_ID = "messages_id"; // カラム名2 public static final String COLUMN_NAME_MESSAGE = "message"; } }
DbHelper (SQL Helper)
データベースを操作するためのヘルパークラスを作る。MessagesDbHelper.java
package com.example.myfirstapp; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; public class MessagesDbHelper extends SQLiteOpenHelper { private static final String TEXT_TYPE = " TEXT"; private static final String SQL_CREATE_MESSAGES = "CREATE TABLE " + MessagesContract.Messages.TABLE_NAME + " (" + MessagesContract.Messages._ID + " INTEGER PRIMARY KEY," + MessagesContract.Messages.COLUMN_NAME_MESSAGES_ID + TEXT_TYPE + "," + MessagesContract.Messages.COLUMN_NAME_MESSAGE + TEXT_TYPE + " )"; private static final String SQL_DELETE_MESSAGES = "DROP TABLE IF EXISTS " + MessagesContract.Messages.TABLE_NAME; // If you change the database schema, you must increment the database // version. public static final int DATABASE_VERSION = 1; public static final String DATABASE_NAME = "Messages.db"; public MessagesDbHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE_MESSAGES); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // 今回は特にデータエクスポート処理は実装してない // テーブルをDropして作り直してるだけ db.execSQL(SQL_DELETE_MESSAGES); onCreate(db); } @Override public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { // onUpgradeを読んでるだけ onUpgrade(db, oldVersion, newVersion); } }
※DbHelperのonUpgrade(またはonDowngrade)メソッドにはデータベースのスキーマのバージョンアップ(またはバージョンダウン)時にデータエクスポート機能などを実装することを想定している。
次は画面の定義。
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <EditText android:id="@+id/edit_message" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:hint="@string/edit_message" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="sendMessage" android:text="@string/button_send" /> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="saveToSP" android:text="@string/button_save_to_sp" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="readFromSP" android:text="@string/button_read_from_sp" /> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="saveToFile" android:text="@string/button_save_to_file" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="readFromFile" android:text="@string/button_read_from_file" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="deleteFile" android:text="@string/button_delete_file" /> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="saveToDb" android:text="@string/button_save_to_db" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="readFromDb" android:text="@string/button_read_from_db" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="deleteFromDb" android:text="@string/button_delete_from_db" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="updateDb" android:text="@string/button_update_db" /> </LinearLayout> </LinearLayout>
今回使っているのは、入れ子になっている4つ目のLinearLayout。
そしてリソースを定義。
strings.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">My First App</string> <string name="edit_message">Enter a message</string> <string name="button_send">Send</string> <string name="menu_settings">Settings</string> <string name="title_activity_main">MainActivity</string> <string name="action_settings">Settings</string> <string name="hello_world">Hello world!</string> <string name="title_activity_display_message">My Message</string> <!-- SharedPreferences --> <string name="sp_key">my_sp_key</string> <string name="button_save_to_sp">save to SP</string> <string name="button_read_from_sp">read from SP</string> <!-- File --> <string name="button_save_to_file">save to file</string> <string name="button_read_from_file">read from file</string> <string name="button_delete_file">delete file</string> <!-- Db --> <string name="button_save_to_db">DBへ保存</string> <string name="button_read_from_db">DBから読込</string> <string name="button_update_db">DBを更新(LIKE \u0025aaa\u0025)</string> <string name="button_delete_from_db">DBから全削除</string> </resources>
最後はアクティビティ。
MainActivity.java
package com.example.myfirstapp; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.util.Date; import android.annotation.SuppressLint; import android.app.Activity; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.view.Menu; import android.view.View; import android.widget.EditText; import android.widget.Toast; public class MainActivity extends Activity { public final static String EXTRA_MESSAGE = "com.example.myfirstapp.MESSAGE"; // InstanceStateへ状態を保存するためのキー public final static String STATE_ENTERED_MESSAGE = "state.enteredmessage"; private String message = null; private final static String FILENAME = "myfile.txt"; // DbHelper private MessagesDbHelper mDbHelper = new MessagesDbHelper(this); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } /** Called when the user clicks the Send button */ public void sendMessage(View view) { Intent intent = new Intent(this, DisplayMessageActivity.class); EditText editText = (EditText) findViewById(R.id.edit_message); message = editText.getText().toString(); intent.putExtra(EXTRA_MESSAGE, message); startActivity(intent); } /** * メッセージをSharedPreferencesへ保存 */ public void saveToSP(View view) { // 入力ボックスの値を取得 EditText editText = (EditText) findViewById(R.id.edit_message); message = editText.getText().toString(); // SPへ保存 SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE); SharedPreferences.Editor editor = sharedPref.edit(); editor.putString(getString(R.string.sp_key), message); editor.commit(); // トースト表示 Toast.makeText(this, "SPへ保存しました:" + message, Toast.LENGTH_LONG).show(); } /** * SharedPreferencesに保存されたメッセージを読み込み */ @SuppressLint("ShowToast") public void readFromSP(View view) { // SPからデータを取得 SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE); String message = sharedPref.getString(getString(R.string.sp_key), "default message"); // トースト表示 Toast.makeText(this, "SPから読み込みました:" + message, Toast.LENGTH_LONG) .show(); } /** * ファイルへ保存 * * @param view */ @SuppressLint("ShowToast") public void saveToFile(View view) { // 入力ボックスの値を取得 EditText editText = (EditText) findViewById(R.id.edit_message); message = editText.getText().toString(); try { FileOutputStream fos = openFileOutput(FILENAME, Context.MODE_PRIVATE); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(fos)); bw.write(message); bw.flush(); bw.close(); } catch (Exception e) { e.printStackTrace(); } // トースト表示 Toast.makeText(this, "saved to file:" + message, Toast.LENGTH_SHORT) .show(); } /** * ファイルから読み込み * * @param view */ @SuppressLint("ShowToast") public void readFromFile(View view) { FileInputStream file; String str = null; try { file = openFileInput(FILENAME); BufferedReader br = new BufferedReader(new InputStreamReader(file)); str = br.readLine(); br.close(); } catch (Exception e) { e.printStackTrace(); } // トースト表示 Toast.makeText(this, "read from file:" + str, Toast.LENGTH_LONG).show(); } /** * ファイルを削除 * * @param view */ @SuppressLint("ShowToast") public void deleteFile(View view) { // 内部ストレージのファイルを削除 deleteFile(FILENAME); // // 任意のファイルを削除する場合はこっちを使う // File file = new File(Environment.getExternalStorageDirectory(), "/" + // FILENAME); // file.delete(); // トースト表示 Toast.makeText(this, "file deleted:" + FILENAME, Toast.LENGTH_LONG) .show(); } @Override protected void onSaveInstanceState(Bundle outState) { // InstanceState へ 入力されたメッセージを保存しておく。 outState.putString(STATE_ENTERED_MESSAGE, message); super.onSaveInstanceState(outState); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); message = savedInstanceState.getString(STATE_ENTERED_MESSAGE); } /** * DBへ保存 * * @param view */ @SuppressLint("ShowToast") public void saveToDb(View view) { // 入力ボックスの値を取得 EditText editText = (EditText) findViewById(R.id.edit_message); message = editText.getText().toString(); SQLiteDatabase db = mDbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(MessagesContract.Messages.COLUMN_NAME_MESSAGES_ID, String.valueOf(new Date().getTime())); values.put(MessagesContract.Messages.COLUMN_NAME_MESSAGE, message); long newRowId; newRowId = db .insert(MessagesContract.Messages.TABLE_NAME, null, values); // 新規作成された message_id をトースト表示 Toast.makeText(this, "saveToDb:" + newRowId, Toast.LENGTH_LONG).show(); } /** * DBから全件読み込み * * @param view */ @SuppressLint("ShowToast") public void readFromDb(View view) { SQLiteDatabase db = mDbHelper.getReadableDatabase(); String[] projection = { MessagesContract.Messages._ID, MessagesContract.Messages.COLUMN_NAME_MESSAGES_ID, MessagesContract.Messages.COLUMN_NAME_MESSAGE }; // 検索条件:カラム名 String selection = MessagesContract.Messages.COLUMN_NAME_MESSAGE + " LIKE ?"; // 検索条件:カラム値 String[] selectionArgs = { "%" };// 全件検索 // Sort条件 String sortOrder = MessagesContract.Messages.COLUMN_NAME_MESSAGE + " DESC"; Cursor c = db.query(MessagesContract.Messages.TABLE_NAME, // The table // to query projection, // The columns to return selection, // The columns for the WHERE clause selectionArgs, // The values for the WHERE clause null, // don't group the rows null, // don't filter by row groups sortOrder // The sort order ); StringBuilder text = new StringBuilder(); while (c.moveToNext()) { text.append("_ID=" + c.getString(0)); text.append(", messages_id=" + c.getString(1)); text.append(", message=" + c.getString(2)); text.append("\n"); } // トースト表示 Toast.makeText(this, "readFromDb:" + text, Toast.LENGTH_LONG).show(); } /** * DBのデータから "aaa" が含まれるデータを適当に更新 * * @param view */ @SuppressLint("ShowToast") public void updateDb(View view) { SQLiteDatabase db = mDbHelper.getReadableDatabase(); // 更新するデータ ContentValues values = new ContentValues(); values.put(MessagesContract.Messages.COLUMN_NAME_MESSAGE, "hoge"); // 検索条件 String selection = MessagesContract.Messages.COLUMN_NAME_MESSAGE + " LIKE ?"; String[] selectionArgs = { "%aaa%" }; // 更新 (戻り値は更新件数) int count = db.update(MessagesContract.Messages.TABLE_NAME, values, selection, selectionArgs); // トースト表示 Toast.makeText(this, "updateDb:" + count, Toast.LENGTH_LONG).show(); } /** * DBから全件削除 * * @param view */ @SuppressLint("ShowToast") public void deleteFromDb(View view) { SQLiteDatabase db = mDbHelper.getWritableDatabase(); // 検索条件:カラム名 String selection = MessagesContract.Messages.COLUMN_NAME_MESSAGE + " LIKE ?"; // 検索条件:カラム値 String[] selectionArgs = { String.valueOf("%") };// 全件検索 // 削除 (戻り値は削除件数) int count = db.delete(MessagesContract.Messages.TABLE_NAME, selection, selectionArgs); // トースト表示 Toast.makeText(this, "deleteFromDb:" + count, Toast.LENGTH_LONG).show(); } }
ポイントは #saveToDb, #readFromDb, #updateDb, #deleteFromDb メソッド。
DbHelper#getWritableDatabase(またはgetReadableDatabase)でSQLiteDatabaseオブジェクトを取得し、挿入(#insert)したり、選択(#query)したり、更新(#update)したり、削除(#delete)したりすればよい。
SQL実行(query | update | delete)の際に条件を指定したい場合は、selection(条件文)とselectionArgs(条件文の?に入れる値)を第三、第四引数に渡すのがミソ。