Content Provider入門

Androidでデータを保存するにはContent Providerを使用するのが基本です。端末内のアプリ専用エリアに作成され、アクセス権限を定義すれば他のアプリからも使用可能と夢が広がる機能です。システムのデータもこれで作成されていて、電話帳などにアクセスするアプリを作ることも可能です。
メリットは上記の通りで、これで作っておけば問題無いと言いたいところなんだけど、端末の容量が数十メガバイトしかなく(少なくともdev phoneは)、あまり消費させたくないというのが実情なので、必要に応じて外部の(SDカードとか)記憶エリアを併用するのが賢いやり方だと思います。

マニュアル

Content Providers | Android DevelopersSDK付属のサンプル(NotePad)を見ればわかるんだけど、とりあえず動かしてみたい場合には事前準備が多くて大変です。自分自身、一目で理解出来なかったので、ドキュメントとNotePadを見ながら最低限必要な部分だけ動かしつつ試行錯誤しながら理解していこうと思います。

テストプロジェクト作成

開発環境はeclipseSDKは1.5です。ProviderTestという名前でActivityも作りました。

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は自動採番されてます。