Android におけるデータ保存の方法は主に三つ用意されています。それぞれの利用方法をまとめます。
関連する公式ドキュメント
環境設定を簡易 KVS として利用できます。ファイルに保存されるため、アプリケーションが kill されても値は消失しません。
以下のサンプルでは Shared Preferences を直接利用していますが、そうではなくこれを内部的に利用して、アプリケーション設定の UI を構築するための仕組みが存在します。簡易 KVS ではなくアプリケーションの設定をユーザーが管理する UI を作成するためには後者を利用すると簡単です。
package com.example.mycompany.myapp;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private static final String PREF_FILE_NAME = "com.example.mycompany.myapp.PREF_FILE_NAME";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 設定ファイルを開きます。
SharedPreferences sharedPref = getSharedPreferences(PREF_FILE_NAME, Context.MODE_PRIVATE);
// 値の取得
int intVal = sharedPref.getInt("MY_KEY", 123); // 既定値 123 を設定
Log.d(TAG, "MY_VALUE: " + intVal);
// 値の設定 (起動する毎に値が +1 されます)
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt("MY_KEY", intVal + 1);
editor.commit();
}
}
実行例
08-12 11:49:41.905 5893-5893/com.example.mycompany.myapp D/MainActivity: MY_VALUE: 123
↓終了して再度起動
08-12 11:50:13.073 5893-5893/com.example.mycompany.myapp D/MainActivity: MY_VALUE: 124
↓終了して再度起動
08-12 11:50:28.786 5893-5893/com.example.mycompany.myapp D/MainActivity: MY_VALUE: 125
Android ファイルシステムには、常に利用可能であることが保証されておりアプリケーション外からアクセスすることを基本的に想定しない Internal なものと、SD カード等によって提供される常に利用できるとは限らず、他のアプリケーションやユーザーからも参照される可能性のある External なものがあります。以下のサンプルは Internal なファイルシステムに対して読み書きしています。External なファイルシステムに対して操作を行う際は、利用可能かどうかの事前判定や AndroidManifest.xml
におけるユーザーへの Permission 許可依頼が別途必要です。
package com.example.mycompany.myapp;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 書き込み
BufferedWriter writer = null;
try {
writer = new BufferedWriter(new OutputStreamWriter(openFileOutput("my_file.txt", Context.MODE_PRIVATE)));
writer.write("abc\n123");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(writer != null) {
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 読み出し
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(openFileInput("my_file.txt")));
String str = null;
while((str = reader.readLine()) != null) {
Log.d(TAG, str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(reader != null) {
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
// Internal ファイルシステムにおける保存場所
Log.d(TAG, "saved dir: " + getFilesDir());
// 保存されているファイルリスト
String[] files = fileList();
for(int i = 0; i < files.length; ++i) {
Log.d(TAG, "found: " + files[i]);
deleteFile(files[i]); // 削除
}
}
}
Java 7 相当以上の API レベルであれば try-with-resources 構文を利用した記述が可能です。また、Android Studio におけるファイルエクスプローラは「Tools → Android → Android Device Monitor → File Explorer」で利用できます。
実行例
08-12 19:17:03.878 5837-5837/com.example.mycompany.myapp D/MainActivity: abc
08-12 19:17:03.878 5837-5837/com.example.mycompany.myapp D/MainActivity: 123
08-12 19:17:03.879 5837-5837/com.example.mycompany.myapp D/MainActivity: saved dir: /data/user/0/com.example.mycompany.myapp/files
08-12 19:17:03.879 5837-5837/com.example.mycompany.myapp D/MainActivity: found: my_file.txt
Android では Rails でも既定の設定で利用されている SQLite が組み込み DB として利用できます。上述「ファイル保存」における Internal ファイルシステムと同様、保存された情報は他のアプリケーションから参照できません。以下のサンプルでは簡単のため main スレッドで DB 操作していますが、一般に時間のかかる処理であり実際のアプリケーション開発時には IntentService などで別スレッドを用意するようにします。
contract class とよばれるクラスを用意し、そのインナークラスで各テーブルを定義します。
package com.example.mycompany.myapp;
import android.provider.BaseColumns;
public final class MyDbContract {
// コンストラクタを利用できないようにします。
private MyDbContract() {}
// テーブル名、列名の定数定義です。
public static class MyTable implements BaseColumns {
public static final String TABLE_NAME = "my_table";
public static final String COLUMN_NAME_INT_COL = "int_col";
public static final String COLUMN_NAME_STR_COL = "str_col";
}
}
後に MainActivity で DB 操作のために利用するクラスです。先程定義した contract class 内の MyTable を static インポートしています。
package com.example.mycompany.myapp;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import static com.example.mycompany.myapp.MyDbContract.MyTable;
public class MyDbHelper extends SQLiteOpenHelper {
// スキーマに変更があれば VERSION をインクリメントします。
public static final int DATABASE_VERSION = 1;
// SQLite ファイル名を指定します。
public static final String DATABASE_NAME = "MyDb.db";
// SQLite ファイルが存在しない場合や VERSION が変更された際に実行する SQL を定義します。
private static final String SQL_CREATE_TABLE =
"CREATE TABLE " + MyTable.TABLE_NAME + " (" +
MyTable._ID + " INTEGER PRIMARY KEY," +
MyTable.COLUMN_NAME_INT_COL + " INTEGER," +
MyTable.COLUMN_NAME_STR_COL + " TEXT)";
private static final String SQL_DROP_TABLE = "DROP TABLE IF EXISTS " + MyTable.TABLE_NAME;
public MyDbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) { // SQLite ファイルが存在しない場合
db.execSQL(SQL_CREATE_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// VERSION が上がった場合に実行されます。本サンプルでは単純に DROP して CREATE し直します。
db.execSQL(SQL_DROP_TABLE);
onCreate(db);
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// VERSION が下がった場合に実行されます。本サンプルでは単純に DROP して CREATE し直します。
onUpgrade(db, oldVersion, newVersion);
}
}
簡単のためすべて onCreate()
内でクエリを実行しています。実際にはバックグラウンドで別スレッドで処理すべきです。また、コネクションを張る処理は重いため onDestroy()
で閉じるまで使い回しています。
package com.example.mycompany.myapp;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import static com.example.mycompany.myapp.MyDbContract.MyTable;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
// DB を操作するためのインスタンス
private MyDbHelper mDbHelper = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDbHelper = new MyDbHelper(getApplicationContext());
SQLiteDatabase reader = mDbHelper.getReadableDatabase();
SQLiteDatabase writer = mDbHelper.getWritableDatabase();
// INSERT
ContentValues values = new ContentValues();
values.put(MyTable.COLUMN_NAME_INT_COL, 123);
values.put(MyTable.COLUMN_NAME_STR_COL, "aaa");
writer.insert(MyTable.TABLE_NAME, null, values);
// SELECT
String[] projection = { // SELECT する列
MyTable._ID,
MyTable.COLUMN_NAME_INT_COL,
MyTable.COLUMN_NAME_STR_COL
};
String selection = MyTable.COLUMN_NAME_INT_COL + " = ?"; // WHERE 句
String[] selectionArgs = { "123" };
String sortOrder = MyTable.COLUMN_NAME_STR_COL + " DESC"; // ORDER 句
Cursor cursor = reader.query(
MyTable.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
);
while(cursor.moveToNext()) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(MyTable._ID));
String str = cursor.getString(cursor.getColumnIndexOrThrow(MyTable.COLUMN_NAME_STR_COL));
Log.d(TAG, "id: " + String.valueOf(id) + ", str: " + str);
}
cursor.close();
// DELETE
String deleteSelection = MyTable._ID + " > ?"; // WHERE 句
String[] deleteSelectionArgs = { "5" };
writer.delete(MyTable.TABLE_NAME, deleteSelection, deleteSelectionArgs);
// UPDATE
ContentValues updateValues = new ContentValues();
updateValues.put(MyTable.COLUMN_NAME_STR_COL, "bbb");
String updateSelection = MyTable.COLUMN_NAME_STR_COL + " = ?";
String[] updateSelectionArgs = { "aaa" };
writer.update(
MyTable.TABLE_NAME,
updateValues,
updateSelection,
updateSelectionArgs);
}
@Override
protected void onDestroy() {
if(mDbHelper != null) {
mDbHelper.close(); // コネクションを閉じます。
}
super.onDestroy();
}
}
Android Studio で SDK と同封されてインストールされる adb (Android Debug Bridge) コマンドを利用すると Android 端末に shell で接続できます。「Preferences → Appearance & Behavior → System Settings → Android SDK → Android SDK Location」で SDK の場所を確認して PATH を通しておきます。
$ /Users/username/Library/Android/sdk/platform-tools/adb shell
generic_x86:/ $ su
generic_x86:/ #
SQLite DB ファイルの場所を探します。
generic_x86:/ # find . -name 'MyDb.db'
...
./data/data/com.example.mycompany.myapp/databases/MyDb.db
sqlite3 コマンドで接続します。
generic_x86:/ # sqlite3 ./data/data/com.example.mycompany.myapp/databases/MyDb.db
テーブル一覧
sqlite> .tables
android_metadata my_table
スキーマ確認
sqlite> .schema my_table
CREATE TABLE my_table (_id INTEGER PRIMARY KEY,int_col INTEGER,str_col TEXT);
SQL 発行
sqlite> select * from my_table;
1|123|bbb
2|123|bbb
3|123|bbb
4|123|bbb
5|123|bbb