Content Provider入門
Androidでデータを保存するにはContent Providerを使用するのが基本です。端末内のアプリ専用エリアに作成され、アクセス権限を定義すれば他のアプリからも使用可能と夢が広がる機能です。システムのデータもこれで作成されていて、電話帳などにアクセスするアプリを作ることも可能です。
メリットは上記の通りで、これで作っておけば問題無いと言いたいところなんだけど、端末の容量が数十メガバイトしかなく(少なくともdev phoneは)、あまり消費させたくないというのが実情なので、必要に応じて外部の(SDカードとか)記憶エリアを併用するのが賢いやり方だと思います。
マニュアル
Content Providers | Android DevelopersやSDK付属のサンプル(NotePad)を見ればわかるんだけど、とりあえず動かしてみたい場合には事前準備が多くて大変です。自分自身、一目で理解出来なかったので、ドキュメントとNotePadを見ながら最低限必要な部分だけ動かしつつ試行錯誤しながら理解していこうと思います。
AndroidManifest.xmlの更新
まずはContent Providerを使用するための設定をAndroidManifest.xmlに追加します。
<provider android:name="TestProvider" android:authorities="com.matabii.test.testprovider" />
これで、com.matabii.test.testproviderという名前(URL)を使用して、TestProviderにアクセス出来ます。他のアプリからもアクセス出来るようにするために、パッケージ名を含めたクラス名にするのが良いみたいです。ActivityからはこのURLを使ってProviderにアクセスします。
AndroidManifest.xmlについて、今回はこれだけの編集で終了です。
Providerクラスの作成
AndroidManifest.xmlを保存すると、TestProviderが無いというエラーが出るので作成します。Content Providerを使用するには、ContentProviderのサブクラスを作る必要があります。ContentProviderは抽象クラスで、onCreate()、query()、insert()、delete()などのメソッドをオーバーライドさせるようになってます。何のためのクラスなのか、何となくわかりますね。
TestProviderクラスを作り、ContentProviderを継承、必要なメソッドをeclipseで自動で実装しておきます。
package com.matabii.test; import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; public class TestProvider extends ContentProvider{ @Override public boolean onCreate() { // TODO Auto-generated method stub return false; } @Override public Uri insert(Uri uri, ContentValues values) { // TODO Auto-generated method stub return null; ・・・略
Helperクラスの作成
TestProviderに最低限の実装をしていきます。今回は、テーブルの作成とデータの追加の動きを見るだけにします。ContentProvider自身にはデータベースをどうこうする手段は無いので、別にSQLiteOpenHelperの実装クラスを作る必要があります。NotePadでもそうしてるので、ここではTestProviderのインナークラスとして定義します。
テーブルを作成する処理はここに書きます。プライマリーキーとして、_IDが必要みたいです。後はnameとdescriptionだけのシンプルなテーブルを作ることにします。
private static class DatabaseHelper extends SQLiteOpenHelper { DatabaseHelper(Context context) { super(context, "test.db", null, 1); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE test (" + BaseColumns._ID + " INTEGER PRIMARY KEY," + "name TEXT," + "description TEXT" + ");"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // TODO Auto-generated method stub db.execSQL("DROP TABLE IF EXISTS test"); onCreate(db); } }
SQLiteOpenHelperについて→http://d.hatena.ne.jp/isher/20091108
DatabaseHelperへのアクセス変数を作成します。
DatabaseHelper databaseHelper;
OnCreate()の実装
重要なメソッドですが、Helperに振るだけなのでこれだけです。最初にアクセスした時に呼ばれるので、DatabaseHelperのインスタンスを作ります。上で作ったDatabaseHelperクラスのonCreate()が呼び出され、データベースが無ければ作成して、インスタンスが返されます。
@Override public boolean onCreate() { databaseHelper = new DatabaseHelper(getContext()); return true; }
insert()の実装
データ登録用のメソッドです。DatabaseHelperのインスタンスから更新用のSQLiteDatabaseインスタンスを取得して、insertメソッドを呼び出すだけです。実際はテーブル名で分岐させたり、登録日なんてカラムがあるならここでセットしてしまえばいいですね。
@Override public Uri insert(Uri uri, ContentValues values) { SQLiteDatabase db = databaseHelper.getWritableDatabase(); db.insert("test", null, values); //testがテーブル名 return null; }
query()の実装
データ検索用のメソッドです。DatabaseHelperのインスタンスから参照用のSQLiteDatabaseインスタンスを取得してCursorを返すことになります。ここもテーブル名で分岐させたりするんだろうけど、固定でいっちゃいます。
@Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteDatabase db = databaseHelper.getReadableDatabase(); SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setTables("test"); //テーブル名 Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, null); return c; }
Provider作成完了
updateやdeleteが作成されてないし実際使い物にならないけど、動作を見るくらいならこれで動きます。
package com.matabii.test; import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import android.provider.BaseColumns; public class TestProvider extends ContentProvider { private static class DatabaseHelper extends SQLiteOpenHelper { DatabaseHelper(Context context) { super(context, "test.db", null, 1); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE test (" + BaseColumns._ID + " INTEGER PRIMARY KEY," + "name TEXT," + "description TEXT" + ");"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // TODO Auto-generated method stub db.execSQL("DROP TABLE IF EXISTS test"); onCreate(db); } } DatabaseHelper databaseHelper; @Override public boolean onCreate() { // TODO Auto-generated method stub databaseHelper = new DatabaseHelper(getContext()); return true; } @Override public Uri insert(Uri uri, ContentValues values) { SQLiteDatabase db = databaseHelper.getWritableDatabase(); db.insert("test", null, values); return null; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteDatabase db = databaseHelper.getReadableDatabase(); SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setTables("test"); Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, null); return c; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // TODO Auto-generated method stub return 0; } @Override public String getType(Uri uri) { // TODO Auto-generated method stub return null; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // TODO Auto-generated method stub return 0; } }
Activityからのテスト
いよいよActivityから呼び出してみますが、簡単な動作確認だけにしてしまいます。
content://com.matabii.test.testproviderがAndroidManifest.xmlに定義したURLです。電話帳を使いたい時はcontent://contacts/peopleを使います。直接クラスを使わずにURLをIntentにセットすることでアプリケーション間で連携させるわけです。
package com.matabii.test; import android.app.Activity; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.util.Log; public class ProviderTest extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getIntent().setData(Uri.parse("content://com.matabii.test.testprovider")); ContentValues values = new ContentValues(); values.put("name", "Pen"); values.put("description", "This is a pen"); getContentResolver().insert(getIntent().getData(), values); Cursor cur = managedQuery(getIntent().getData(), null, null, null, null); while (cur.moveToNext()) { Log.d(cur.getString(1), cur.getString(2)); } setContentView(R.layout.main); } }
結果確認
エミュレーターを実行して、Activityが表示されれば成功です。結果はログを見ないとわかりませんが。
データベースは/data/data/パッケージ名、上の例だと/data/data/com.matabii.testの下にdatabasesディレクトリが作られて、その中にデータベースが作成されてます。
エミュレータを立ち上げてadb shellしてのぞいてみましょう。
# cd /data/data/com.matabii.test/ # ls databases lib # cd databases # ls test.db
test.dbが作られてます。sqlite3で中身を見てみます。
# sqlite3 test.db SQLite version 3.5.9 Enter ".help" for instructions sqlite> .schema CREATE TABLE android_metadata (locale TEXT); CREATE TABLE test (_id INTEGER PRIMARY KEY,name TEXT,description TEXT); sqlite> select * from test; 1|Pen|This is a pen 2|Pen|This is a pen
testテーブルが作成されていて、内容も登録されてました。2回実行したので、2件です。_idは自動採番されてます。