ContentProviderでアプリケーションのデータを共有する その1
今回は題名にある通り、ContentProviderを使ったデータの共有方法について説明してみたいと思います。
アプリケーションのデータを保存する手段としては、SQLiteを利用するのが一般的だと思いますが、場合によってはその蓄積したデータを他のアプリケーションからも利用したいと思うことが多々あると思います。
そんな場合はContentProviderをアプリに実装することで、そのデータを外部に公開することが可能です。
ContentProviderの詳細な説明については、他サイトを参考にして頂ければ良いと思います。
ContentProviderを使って、不特定多数のアプリにデータを公開する
図で説明すると、次のような感じになります。
例として、プロジェクトAとプロジェクトBがあるとします。
プロジェクトAのデータに対して、プロジェクトBがプロジェクトAのContentProviderを介して、
データベースを操作するイメージです。
それでは上記の通りデータがやり取りされていることを確認するために、Eclipseから新たにプロジェクトを2つ立ち上げてください。
プロジェクト名は、プロジェクトAとプロジェクトBとします。
プロジェクトAでは次のようなクラスを実装します。
- Activityクラス
- データベース操作クラス
- クエリー定義クラス
- ContentProviderを拡張したクラス
- ArrayAdapterを拡張したクラス(リストビューのカスタマイズ)
Activityクラス Hello
アプリの起動と同時にデータベースを作成し、データを一件インサート後、
その直後にInsertしたデータをSelectして、リストビューに表示します。
package hoge.hoge.hatena.A; import android.app.Activity; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.widget.ListView; /** * Hello Activity * 概要:インサート処理とセレクト処理行い * リストビューにセレクトした内容を詰め込む * @author korsakov * */ public class Hello extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //インサート処理 DBHelper mDb = new DBHelper(this); SQLiteDatabase db = mDb.getWritableDatabase(); try { db.beginTransaction(); HelloTable q = new HelloTable(); q.mMs = "Hello A"; db.execSQL(q.getInsertQuery(), q.getInsertBindItem()); db.setTransactionSuccessful(); } finally { db.endTransaction(); db.close(); } //セレクト処理 db = mDb.getReadableDatabase(); HelloTable []bean = null; try { db.beginTransaction(); HelloTable q = new HelloTable(); //セレクトした結果のカーソルオブジェクトを変換する bean = q.makeData(db.rawQuery(q.getSelectQuery(), null)); db.endTransaction(); } finally { db.close(); } ListView list = (ListView)this.findViewById(R.id.ListView01); list.setAdapter(new HelloListAdapter(this, 0, bean)); } }
データベース操作クラス DBHelper
SQLiteOpenHelperを拡張したクラスです。
このクラスを使うポイントとしては予めデータベース名を定義しておき、onCreateメソッドが走った時にクエリー発行して必要なテーブルの作成を行うことです。
データベース名は HELLO_DATABASE としておきます
package hoge.hoge.hatena.A; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; public class DBHelper extends SQLiteOpenHelper { private static final String DB_NAME = "HELLO_DATABASE"; private static final int DB_VERSION = 1; public DBHelper(Context context) { //データベース名と、データベースバージョンを設定 super(context, DB_NAME, null, DB_VERSION); } @Override public void onCreate(SQLiteDatabase db) { // 初回DB作成時は、こちらのメソッドが実行される db.beginTransaction(); try { HelloTable q = new HelloTable(); db.execSQL(q.getCreateQuery()); db.setTransactionSuccessful(); } finally { //トランザクションの終了 db.endTransaction(); } } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // TODO Auto-generated method stub } }
クエリー定義クラス HelloTable
AndroidでSQLiteの使い方を紹介しているサイトは幾つも有りますが、各サイトで紹介されている方法の多くはクエリーを発行するクラス内で、直接クエリーの文字列リテラルをソースに記述するやり方が紹介されています。
そのやり方だとtypoする事もあるし、自分的には何となく纏まりがない思うので、定義するテーブルごとにクラスを作成する事にしました。
次のようなコードよりも・・・
String table = “foo, bar”;
String[] columns = {“foo.a”, “bar.b”};
String selection = “foo.a = bar.a”;
Cursor c = db.query(table, columns, selection, null, null, null, null);
このほうがスマートだと思う。
HelloTable []bean = null;
HelloTable q = new HelloTable();
bean = q.makeData(db.rawQuery(q.getSelectQuery(), null));
package hoge.hoge.hatena.A; import android.database.Cursor; public class HelloTable { private static final String CREATE = "create table hello(_id integer primary key autoincrement not null, ms text not null)"; private static final String DROP = "drop table hello"; private static final String INSERT = "insert into hello(ms) values ( ? )"; private static final String UPDATE = "update hello set ms = ? where _id = ?"; private static final String DELETE = "delete from hello"; private static final String SELECT = "select * from hello"; public int mId; //Column 1 public String mMs; //Column 2 public String getCreateQuery() { return CREATE; } public String getInsertQuery() { return INSERT; } public String[] getInsertBindItem() { return new String[]{mMs}; } public String getSelectQuery() { return SELECT; } public void setData(Cursor c) { // TODO Auto-generated method stub mId = c.getInt(0); mMs = c.getString(1); } public HelloTable[] makeData(Cursor c) { //最初のデータを参照する c.moveToFirst(); int cnt = c.getCount(); if (cnt == 0) { c.close(); return null; } HelloTable []q = new HelloTable[cnt]; for (int i = 0; i < cnt; i++) { q[i] = new HelloTable(); q[i].setData(c); c.moveToNext(); } c.close(); return q; } }
ContentProviderクラス HelloProvider
ContentProviderを拡張したクラスです。AUTHORITYはプロジェクトAの完全なデータベース名になります。
この記述はAndroidManifestファイルにも同様に記述します。
package hoge.hoge.hatena.A; import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.util.Log; public class HelloProvider extends ContentProvider { private DBHelper mDb; public static final String AUTHORITY = "hoge.hoge.hatena.A.HELLO_DATABASE"; public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/"); private static final String TAG = "HelloProvider"; //ディレクトリのMIMEタイプ private static final String CONTENT_TYPE = "vnd.android.cursor.dir/" + AUTHORITY; @Override public int delete(Uri arg0, String arg1, String[] arg2) { // TODO Auto-generated method stub return 0; } @Override public String getType(Uri arg0) { // TODO Auto-generated method stub //ディレクトリのみアクセス return CONTENT_TYPE; } @Override public Uri insert(Uri arg0, ContentValues arg1) { // TODO Auto-generated method stub return null; } @Override public boolean onCreate() { Log.d(TAG, "onCreate"); mDb = new DBHelper(getContext()); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Log.d(TAG, "query"); Log.d(TAG, uri.toString()); HelloTable hello = new HelloTable(); final SQLiteDatabase db = mDb.getReadableDatabase(); Cursor c = null; c = db.rawQuery(hello.getSelectQuery(), null); if (c == null) { Log.d(TAG, "Cursor is null"); } c.setNotificationUri(getContext().getContentResolver(), uri); return c; } @Override public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) { // TODO Auto-generated method stub return 0; } }
ArrayAdapterクラス HelloListAdapter
ListViewをカスタマイズする為に作成しました。各行のレイアウトについてはInflaterを使ってXMLレイアウトから読み込むのが定石のようですが、Inflaterは結構遅いらしいので今回は使っていません。
package hoge.hoge.hatena.A; import android.content.Context; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.widget.ArrayAdapter; import android.widget.LinearLayout; import android.widget.TableRow; import android.widget.TextView; public class HelloListAdapter extends ArrayAdapter<HelloTable> { private Context context; public HelloListAdapter(Context context, int textViewResourceId, HelloTable []items) { super(context, textViewResourceId); this.context = context; for (HelloTable h : items) { this.add(h); } } @Override public View getView(int position, View convertView, ViewGroup parent) { LinearLayout mLayout = null; TextView mText1 = null; TextView mText2 = null; View view = convertView; final HelloTable h = this.getItem(position); if (view == null) { //リストビューのレイアウトを作成 //inflaterは遅いので・・・ mLayout = new LinearLayout(context); mLayout.setId(1); TableRow.LayoutParams textParams = new TableRow.LayoutParams( LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); textParams.weight = 1; mText1 = new TextView(context); mText1.setLayoutParams(textParams); mText1.setGravity(Gravity.CENTER); mText1.setPadding(4, 2, 2, 2); mText1.setTextSize(20); mText1.setId(2); mText2 = new TextView(context); mText2.setLayoutParams(textParams); mText2.setGravity(Gravity.CENTER); mText1.setPadding(2, 2, 2, 4); mText2.setTextSize(20); mText2.setId(3); mLayout.addView(mText1); mLayout.addView(mText2); view = mLayout; } else { mLayout = (LinearLayout)view.findViewById(1); mText1 = (TextView)view.findViewById(2); mText2 = (TextView)view.findViewById(3); } if (mText1 != null) { mText1.setText("id " + h.mId); } if (mText2 != null) { mText2.setText(h.mMs); } return view; } }
AndroidManifest
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="hoge.hoge.hatena.A"> <application android:icon="@drawable/icon" android:label="@string/app_name" android:debuggable="true"> <activity android:name=".Hello" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <provider android:name=".HelloProvider" android:authorities="hoge.hoge.hatena.A.HELLO_DATABASE"> </provider> </application> <uses-sdk android:minSdkVersion="4" /> </manifest>
main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <ListView android:id="@+id/ListView01" android:layout_width="fill_parent" android:layout_height="fill_parent"> </ListView> </LinearLayout>
上記のコードをビルドして実行すると、次のうような画面になると思います。
続いて、プロジェクトAのデータベースを読み取るべく、プロジェクトBを作成します。
プロジェクトBでは、次のような機能を実装します。
Activityクラス Hello
package hoge.hoge.hatena.B; import android.app.Activity; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.widget.ArrayAdapter; import android.widget.ListView; public class Hello extends Activity { /** Called when the activity is first created. */ private String TAG = "Hello B"; //アプリAのパッケージ名 + データベース名 private static final String CONTENT = "content://hoge.hoge.hatena.A.HELLO_DATABASE/"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Cursor c = null; ListView list = (ListView)this.findViewById(R.id.ListView01); //アプリAのContentProviderを使って、データを取得 getIntent().setData(Uri.parse(CONTENT)); c = managedQuery(getIntent().getData(), null, null,null,null); if (c != null) { HelloTable q = new HelloTable(); HelloTable []bean = q.makeData(c); if (bean != null) { list.setAdapter(new HelloListAdapter(this, 0, bean)); } } else { //データが存在しない場合 Log.d(TAG, "result 0items"); ArrayAdapter<String> ad = new ArrayAdapter<String>( this, android.R.layout.simple_list_item_1); ad.add("No Data"); list.setAdapter(ad); } } }
AndroidManifest
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="hoge.hoge.hatena.B" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name" android:debuggable="true"> <activity android:name=".Hello" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> <uses-sdk android:minSdkVersion="4" /> </manifest>
main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <ListView android:id="@+id/ListView01" android:layout_width="fill_parent" android:layout_height="fill_parent"> </ListView> </LinearLayout>
プロジェクトBを実行すると、次のようにプロジェクトAのデータベースの中身を一覧表示します。
随分とソースコードが長くなってしまいましたが、ContentProviderを使う上で参考になれば幸いです。
次回は、ContentProviderを使って特定のアプリケーションにのみ、データベースを公開する方法を紹介したいと思います。