Androidアイキャッチ5

Androidアプリの4大要素 ContentProvider 【Android Tips】

Posted 4月 26 2013 by ozw  , ,

1
Androidアプリの4大要素とインテント
ContentProvider
この節では、Androidアプリケーションの4大要素のうちの1つ”ContentProvider”を紹介します。ContentProviderは異なるアプリケーション間でデータ共有を行うためのコンポーネントです。同様の機能を持つアプリケーションでそれぞれユーザーデータを管理するのではなく、ユーザーデータをただ1つのアプリケーションに集約することができるという利点があります。
ContentProvider概要
アプリケーションは記録したい情報をファイルシステム(内部保存域)やSQLiteデータベースを用いて保存することができます。これらの情報はアプリケーション毎に独立しているため、他のアプリケーションからは参照する事ができません。
開発するアプリケーションの用途や目的によっては、他のアプリケーションが公開している情報を利用したり、逆に他のアプリケーションにデータを公開することもあるでしょう。その際に用いるのがコンテントプロバイダという仕組みです。

Androidでは標準でいくつかのコンテントプロバイダが公開されています。代表的なものは発信者履歴(CallLog)やコンタクトリスト(Contacts)です。

 

コンテントプロバイダへの問い合わせ(この問い合わせのことをクエリと呼びます。)は、リレーショナルデータベース(RDB)と同じように行えるよう設計されており、カーソルやデータセットといったRDBへアクセスする一般的な考え方でデータを参照することができます。

 

この節では、ContentProvideを用いてデータを参照する方法を紹介します。

ContentProviderを用いたデータ参照
以下にContentProviderを用いて他のアプリケーションからデータを受け取る際の注意点をピックアップしました。

それぞれのContentProviderはユニークに識別するためのURIを公開しています。
1つのアプリケーションがデータセットを複数公開している場合は、それぞれの別のURIを定義・公開しています。
たとえば、コンタクトリストではそれぞれ別のURIでEmailアドレス帳や電話番号リストを公開しています。

 

このURIは「content://」という文字列から始まり、その後に完全修飾クラス名が続きます。
データ利用側(クライアント)のプログラムコードをシンプルにするために定数化されていることが多いようです。

以下は、E-mailアドレス帳を表す定数の例です。

android.provider.ContactsContract.CommonDataKinds.Email.CONTENT_URI

 

ContentProviderでデータを参照したい場合はContentResolverオブジェクトを使い、クライアントとしてデータ提供側のアプリケーションと通信します。
ContentProviderはデータの問い合わせだけでなく、データの追加・変更・削除も行うことができ、それらは全てこのContentResolverオブジェクトを介して行います。
ContentResolverオブジェクトの取得方法は以下の通りです。

ContentResolver resolver = getContentResolver();

 

ContentProviderへの問い合わせはContentResolverクラスに定義されているquery()メソッドもしくはActivityクラスに定義されているmanagedQuery()メソッドのいずれかを使用します。どちらのメソッドも同じ引数、同じ戻り値(Cursorオブジェクト)を返すようになっています。

この2つのメソッドの違いは、managedQuery()メソッドはCursorオブジェクトのライフサイクルをアクティビティが管理するようになり、アクティビティが一時停止したときにはCursorをアンロードし、アクティビティが再開したときに再問合わせするといった細かい挙動を制御してくれるという点です。
このため、query()メソッドを実行したときに必要となるような、クライアント側で細かくCursorオブジェクトの再読み込みを行うプログラムコードを記述する必要がなくなります。

 

query()メソッドおよびmanagedQuery()メソッドの引数は以下の通りです。

データ型 引数名 概要
Uri uri ContentProviderを識別するURI
String[] projection 取得するカラムのカラム名のリスト
String selection 取得する行を識別するフィルタ(SQLのWHERE句に相当)
String[] selectionArgs クエリパラメータのリスト
String sortOrder 取得したデータのソート順(SQLのORDER BY句に相当)

※SQLについては後述します。

 

では実際の使用例を見てみましょう。
これはCursorオブジェクトを取得するまでの例で、コンタクトリストから名前と電話番号を取得しようとしています。

import android.database.Cursor;
import android.provider.Contacts.People;
//中略
ContentResolver resolver = getContentResolver();
String[] projection = new String[] {
        People._ID,
        People._COUNT,
        People.NAME,
        People.NUMBER };
Uri uri =  People.CONTENT_URI;

Cursor cur = managedQuery(uri,
        projection,
        null,
        null,
        People.NAME + " ASC");

4行目でContentResolverオブジェクトの取得を行っています。
12行目から16行目でCursorオブジェクトを取得しています。ここでは、managedQuery()メソッドを使用していますね。

Cursorオブジェクトの操作
カーソルはデータベースから取得したデータを表すときに一般的よく使われる考え方です。
プログラム言語によってはデータセットなどと呼ばれることもありますが、基本的な考え方は同じです。
※データベースについては後述します。

 

Cursorはデータベースと同じようにデータが2次元の表形式となっています。縦軸をカラム(列)、横軸をロウ(行)といいます。
各行は1つの関連したデータを表し、各列には必ずカラム名とデータ型が定義されています。

 

query()メソッドやmanagedQuery()メソッドの戻り値としての取得したCursorオブジェクトのカラム名、デフォルト順序、データタイプはそれぞれのContentProviderで別個ものとなります。
ただし、すべてのContentProviderは_IDおよび_COUNTカラムを持っており、_IDカラムには各レコードの一意な数値(データベースでいうところの主キー)を、_COUNTカラムには返されたレコードの件数(データベースでいうところのグループ関数COUNTの結果)を保持しています。

 

Cursorオブジェクトからデータを取得するには以下のように行います。

Cursorオブジェクトには各データ型専用のgetString()、getInt()および、getFloat()、getBlob()といった読み込みメソッドが定義されています。つまり、Cursorオブジェクトからデータを取得するためには、そのカラムのデータ型を知っておく必要があります。

ただし、どのようなデータ型であったとしても、getString()メソッドを使用すればそのデータの文字列表現を取得することができます。
たとえば、「1.23」といったfloat型のデータだったとしても、「”1.23”」といったString型に変換したものを取得することができるようになっています。

 

実際の使用例は以下のようになります。

if (cur.moveToFirst()) {
    String name; 
    String phoneNumber; 

    int nameColumn = cur.getColumnIndex(People.NAME); 
    int phoneColumn = cur.getColumnIndex(People.NUMBER);
 
    do {
        name = cur.getString(nameColumn);
        phoneNumber = cur.getString(phoneColumn);
    } while (cur.moveToNext());
}

1行目で、moveToFirst()メソッドを使用してCursorオブジェクトの先頭行に移動しています。
仮にクエリ結果が1件もない場合は、このメソッドの実行結果がfalseとなるため、以降の処理は行いません。

 

5行目、6行目で取得するカラムの列インデックスを取得しています。これは、getColumnIndex()メソッドで取得することができ、引数には列インデックスを取得したいカラム名を指定します。このカラム名は一般的に定数化されていることが多いようです。

 

8行目から11行目でdo-while文を用いてループしています。
先ほど取得した列インデックスを使用して、データの取得を行っているのが9行目、10行目です。
Cursorオブジェクトの1行をすべて読み込んだら、moveToNext()メソッドを実行しCursorオブジェクトの次の行に移動します。
このメソッドはCursorオブジェクトの末尾行まで読み込みが終わるとfalseを返すため、do-while文のループを抜けます。

 

データを変更する場合は、Cursorオブジェクトのデータを操作するのではなく、以下のメソッドを用いて行います。
これらのメソッドはContentResolverクラスに定義されているメソッドです。

目的 ContentResolverクラスのメソッド
新たなレコードを追加する insert()
既存のレコードに新たな値を追加する
既存のレコードを更新する update()
既存のレコードを削除する delete()


ContentProviderを利用したE-mailアドレス一覧取得アプリ
では、ContentProviderを利用して、コンタクトリストに登録されているE-mailアドレスを一覧表示してみましょう。

ListViewのアイテムをクリックすると、Toastが表示されます。

 

まずは、MainActivityのレイアウトXMLファイルです。
“res/layout/activity_main.xml”

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal" >
 
    <ListView
        android:id="@+id/listView1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">
    </ListView>
 
</LinearLayout>

ListViewを使用するため、LinearLayout内に定義しました。

 

次に、ListVIew内のアイテムに使用するレイアウトです。
“res/layout/list_item.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" >
 
    <TextView
        android:id="@+id/text1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:padding="10dp" />
 
</LinearLayout>

こちらもLinearLayoutの中に定義されているのはTextViewだけです。

 

次に、MainActivityのソースプログラムです。
“src/com.example.service/MainActivity.java”

package com.example.contentprovider;
// 中略
public class MainActivity extends Activity implements OnItemClickListener{
    private List<String> emails = new ArrayList<String>();
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
 
    @Override
    protected void onResume() {
        super.onResume();
        disp();
    }

    private void disp(){
        emails = new ArrayList<String>();
        ContentResolver resolver = getContentResolver();
 
        Uri uri = Email.CONTENT_URI;
        String[] projection = new String[] { Email.DATA1 };
        String order = Email.DATA1 + " ASC";
        Cursor cur = resolver.query(uri, projection, null, null, order);
 
        if (cur.moveToFirst()) {
            int emailColumn = cur.getColumnIndex(Email.DATA1);
 
            do {
                emails.add(cur.getString(emailColumn));
            } while (cur.moveToNext());
        }
 
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
                R.layout.list_item, R.id.text1, emails);
 
        ListView listView1 = (ListView) findViewById(R.id.listView1);
        listView1.setAdapter(adapter);
        listView1.setOnItemClickListener(this);
    }
 
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        Toast.makeText(this, emails.get(position), Toast.LENGTH_SHORT).show();
    }
}

13行目でonResume()メソッドをオーバーライドしています。
このメソッドはActivityがフォアグラウンドになるときに実行されるメソッドで、この中でdisp()メソッドを呼び出しています。

18行目からはdisp()メソッドを定義しています。
インスタンス変数である“emails”を初期化した後、ContentProviderを用いてデータ取得を行います。
20行目から33行目までのプログラムコードはこの節で紹介した使用例とほとんど同じですね。
違うポイントとしては、31行目でContentProviderから取得したデータをdisp()メソッドの最初で初期化した“emails”に追加しているところです。
このArrayListがListViewに表示するデータとして使われます。

 

35行目から40行目まではlist_item.xmlと上で作成したArrayListを用いてAdapterを作成し、activity_main.xml上のListViewと紐付を行っています。

 

40行目ではListViewオブジェクトにOnItemClickListenerオブジェクトをリスナーとして設定しています。
MainActivityに対してOnItemClickListenerインタフェースが実装されているところにも注目してください。

43行目から46行目では、onItemClick()メソッドをオーバーライドし、ListViewのアイテムをクリックしたときの挙動を定義しています。
ここでは、クリックしたItemをToastで出力しているだけです。

 

最後にマニフェストファイルです。
“AndroidManifest.xml”

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.emailcontentprovider"
    android:versionCode="1"
    android:versionName="1.0" >
 
    <uses-sdk
        android:minSdkVersion="7"
        android:targetSdkVersion="15" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
 
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/title_activity_main" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
 
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
 
</manifest>

追加しなくてはいけないのは9行目です。
コンタクトリストからデータを取得するには、パーミッションの設定が必要になります。

 

このアプリのポイントはコンタクトリストからデータを取得し、表示するロジック「disp()メソッド」がonResume()メソッドで呼び出されているという点です。

では、disp()メソッドをonCreate()メソッドで呼び出したらどうなるのでしょうか?
この場合、MainActivityが表示しているアドレス帳が最新状態であるという保証がなくなってしまいます。

 

disp()メソッドをonCreate()メソッド内に移動し、実行してみましょう。
そのあと、いったんアプリを終了し、“コンタクトリスト“アプリを起動させ、E-Mailデータの修正をします。
そして、このアプリに戻ってきてください。

 

コンタクトリストで修正した内容が反映していないですよね。
これは、Activityのライフサイクルによるものです。
Activityがフォアグラウンドに遷移した際に呼び出されるのはonResume()メソッドですから、修正したアプリではアプリに戻ってきたときにdisp()メソッドが再実行されません。
その結果として、コンタクトリストで修正した内容が反映していないという状態になってしまいます。
修正前のアプリのようにonResume()メソッド内でdisp()メソッドを実行することで、フォアグラウンドに戻った際にdisp()メソッドが再実行され、Cursorオブジェクトが最新化、Adapterに反映されることになります。

 

別の方法として、Contextクラスのquery()メソッドではなく、ActivityクラスのmanagedQuery()メソッドを利用するという方法もあります。
managedQuery()メソッドを使用すれば、自動的にCursorのアンロードと再読み込みを行ってくれます。
ですので、onCreate()メソッド内でdisp()メソッドを実行したとしてもCursorオブジェクトは常に最新状態となることが保障されます。
(この方法を用いればたしかにCursorは最新状態となりますが、Adapterは最新化されません。表示されるデータが常に最新となることを保証するには、ArrayAdapterではなくCursorAdapterを使うようにしなくてはなりません。)


Android TIPS



  未経験OKの仕事 |  上場企業の仕事 |  高待遇の仕事 |  外資系の仕事 |  社内SEで検索 |  自社サービスで検索





メールアドレス
ご質問・問い合わせ等、ご自由にお書きください。