View on GitHub
mixi-inc/AndroidTraining
データベース
この章では、Android でのデータベース、特に SQLite3 と ContentProvider について解説します。

参考:Using Databases | Android Developers
参考:Saving Data in SQL Databases | Android Developers
参考:Content Providers | Android Developers

目次

SQLite3Database

Android では、SQLiteを使用します。
SQLiteはサーバー処理を必要としない軽量なトランザクションデーターベースです。
SQLiteでは以下のデータ型がサポートされています。

型 説明
INTEGER 符号付整数
REAL 浮動小数点数
TEXT テキスト
BLOB Binary Large OBject、バイナリ

データベースを作成する(SQLiteOpenHelper)

SQLiteOpenHelper クラスは、データベースの作成とバージョン管理を行います。
使用するには SQLiteOpenHelper クラスを継承した独自クラスを作成し、onCreate() メソッドと onUpgrade() メソッドを override します。

onCreate() メソッドはテーブル生成コマンドを実行する役割となっており、アプリケーションの初回起動時に自動的に呼び出されます。
但し、この時点ではsqliteファイルの実態は存在しません。データベースに対してinsertといった処理を実行したときにsqliteファイルは生成されます。
(sqlieファイルは data/data/packagename/databases 配下に生成されます)
onUpgrade() メソッドはデーターベースのマイグレーション処理などを行なう際に使用されます。 データーベースのバージョン が既に保持しているデーターベースのものと異なる場合に呼び出されます。

今回のサンプルでは以下のテーブル構成を実装していきます。

DB名:sample
テーブル名:book

カラム名 内容
_id プライマリーキー
title テキスト型 null禁止
publisher テキスト型
price テキスト型
_id title publisher price
1 TITLE1 PUBLISHER1 PRICE1
2 TITLE2 PUBLISHER2 PRICE2
3 TITLE3 PUBLISHER3 PRICE3

BookOpenHelper.java

public class BookOpenHelper extends SQLiteOpenHelper {

    // データーベースのバージョン
    // データベーススキーマを変える場合は、バージョンを上げること
    private static final int DATABASE_VERSION = 1;

    public static final String DATABASE_NAME = "Sample.db";

    private static final String BOOK_TABLE_CREATE =
            "CREATE TABLE " + Book.BOOK_TABLE_NAME + " (" +
                    Book._ID + " INTEGER PRIMARY KEY," +
                    Book.COLUMN_NAME_BOOK_TITLE + " TEXT NOT NULL, " +
                    Book.COLUMN_NAME_BOOK_PUBLISHER + " TEXT, " +
                    Book.COLUMN_NAME_BOOK_PRICE + " TEXT);";

    private static final String BOOK_TABLE_DELETE =
            "DROP TABLE IF EXISTS " + Book.BOOK_TABLE_NAME;

    public BookOpenHelper(Context context) {
        // データベース名、バージョンを指定する
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        // テーブル作成
        db.execSQL(BOOK_TABLE_CREATE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // ここでアップデート条件を判定する
        db.execSQL(BOOK_TABLE_DELETE);
        onCreate(db);
    }

}

Book.java

public class Book implements BaseColumns {
    public static final String BOOK_TABLE_NAME = "book";

    public static final String COLUMN_NAME_BOOK_TITLE = "title";
    public static final String COLUMN_NAME_BOOK_PUBLISHER = "publisher";
    public static final String COLUMN_NAME_BOOK_PRICE = "price";

    private String mTitle;
    private String mPublisher;
    private int mPrice;

    ・・・以下アクセッサのため省略
}

上記例ではテーブル名とカラム名をエンティティクラスに定義しています。
その際に BaseColumns クラスを implements しています。
BaseColumns クラスには 基本的な Column 名があらかじめ定義されているためです。

また、Androidではユニークな自動インクリメントIDを使用することを推奨しています。これは、プライベートデータには必要ありませんが、ListView 、 CursorAdapter 、 ContentProvider を使用する場合には含める必要があります。そのため実質必須のものと考えて良いです。
ユニークIDは先ほどのBaseColumns クラスに _ID として用意されています。

データベースの操作(SQLiteDatabase)  

SQLiteDatabaseは読み込み、書き込みといったデータベースの操作をおこないます。
SQLiteDatabase オブジェクトは SQLiteOpenHelper クラスの getWritableDatabase() と getReadableDatabase() メソッドから取得することができます。

Insert

SQLiteDatabase オブジェクトの insert() メソッドを使用します。 ContentValues オブジェクトを介してデーターベースに insert を行います。
ContentValues は key(列名)value(列の値)形式でマッピングしてデータを保持します。

        // 書き込み用のSQLiteDatabaseを取得
        SQLiteDatabase db = bookOpenHelper.getWritableDatabase();
        
        ContentValues values = new ContentValues();
        values.put(Book.COLUMN_NAME_BOOK_TITLE, "TITLE1");
        values.put(Book.COLUMN_NAME_BOOK_PUBLISHER, "PUBLISHER1");
        values.put(Book.COLUMN_NAME_BOOK_PRICE, "PRICE1");

        // 戻り値はRowID(_ID)
        // エラーの場合は-1になる
        long rowId = db.insert(Book.BOOK_TABLE_NAME, null, values);
Read

SQLiteDatabase オブジェクトの query() メソッドを使用します。

        // 読み込み用のSQLiteDatabaseを取得
        SQLiteDatabase db = bookOpenHelper.getReadableDatabase();

        // 取得する情報を指定
        String[] projection = {
                Book._ID,
                Book.COLUMN_NAME_BOOK_TITLE,
                Book.COLUMN_NAME_BOOK_PUBLISHER,
                Book.COLUMN_NAME_BOOK_PRICE
        };

        // 条件を指定
        String selection = Book.COLUMN_NAME_BOOK_PRICE + " = ?";
        String[] selectionArgs = {
                "PRICE1"
        };

        Cursor cursor = db.query(Book.BOOK_TABLE_NAME, projection, selection, selectionArgs, null, null, null);
        boolean moveToFirst = cursor.moveToFirst();
        long itemId = cursor.getLong(cursor.getColumnIndexOrThrow(Book._ID));

query メソッドの戻り値は Cursor クラスとして返却されます。
Cursor クラスは検索結果のインタフェースであり、カーソルを移動させるには move メソッド群を使用します。
大抵の場合、検索結果の1番目データを読み込み開始位置とするため moveToFirst ()メソッドを呼びます。これにより読み込み開始位置が1件目のデータとなります。 cursor がからの場合、moveToFirst ()メソッドの戻り値は false となります。

cursor からデータを取得するには、カラムのindexを引数としたgetメソッド群を使用します。 上記例では getColumnIndexOrThrow メソッドを使用し、カラム名(Book._ID)に該当するindexの値を取得し、getLong メソッドからBook._IDの値を取得してます。
必要な処理が終了したら、cursor の close メソッドを呼び出し、 cursor を閉じます。

Update
        SQLiteDatabase db = bookOpenHelper.getWritableDatabase();

        // update情報を設定する
        ContentValues values = new ContentValues();
        values.put(Book.COLUMN_NAME_BOOK_TITLE, "NEW_TITLE");

        // 条件を指定
        String selection = Book.COLUMN_NAME_BOOK_TITLE + " LIKE ?";
        String[] selectionArgs = {
                "TITLE%"
        };

        int updatedCount = db.update(Book.BOOK_TABLE_NAME, values, selection, selectionArgs);
Delete
        SQLiteDatabase db = bookOpenHelper.getWritableDatabase();

        // 条件を指定
        String selection = Book.COLUMN_NAME_BOOK_PRICE + " = ?";
        String[] selectionArgs = {
                "PRICE1"
        };
        int deletedCount = db.delete(Book.BOOK_TABLE_NAME, selection, selectionArgs);
Transaction

SQLiteDatabase は Transaction 管理もサポートしています。

        SQLiteDatabase db = bookOpenHelper.getWritableDatabase();
        db.beginTransaction();
        try {
            for (int i = 0; i < 10; i++) {
                ContentValues values = new ContentValues();
                values.put(Book.COLUMN_NAME_BOOK_TITLE, "TITLE" + i);
                values.put(Book.COLUMN_NAME_BOOK_PUBLISHER, "PUBLISHER" + i);
                values.put(Book.COLUMN_NAME_BOOK_PRICE, "PRICE" + i);
                db.insert(Book.BOOK_TABLE_NAME, null, values);
            }
            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }

setTransactionSuccessful メソッドは実行しているTransactionを成功したものとしてマークします。実行スレッドのトランザクション内でない場合、または既にマークされている場合は IllegalStateException が発生します。
トランザクションが setTransactionSuccessful メソッドを呼び出してマークされずに終了した場合、変更はロールバックされます。されている場合はコミットされます。

取得したデータを ListView に表示(CursorAdapter)  

CursorAdapter クラスはデータベースから取得したデータとListViewを紐付けるものです。
CursorAdapter を容易に実装できるクラスとして SimpleCursorAdapter が提供されています。
今回は SimpleCursorAdapter を使用してデータの表示を行います。
MainActivity.java

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mBookOpenHelper = new BookOpenHelper(this);
        ListView listView = (ListView) findViewById(R.id.ListView);

        // データを取得
        mCursor = read(mBookOpenHelper);
        // UIにバインドするデータのカラム名
        String[] from = {
                Book.COLUMN_NAME_BOOK_TITLE, Book.COLUMN_NAME_BOOK_PRICE
        };
        // 指定したカラムのデータを表示するViewのIDを指定します。
        int[] to = {
                R.id.Title, R.id.Price

        };
        // 第2引数 リストに表示するレイアウトファイル
        // 第3引数 データベースから取得してきたCursorを指定します
        // 第4引数 UIにバインドするデータのカラム名を指定します
        // 第5引数 第4引数で指定したカラムのデータを表示するViewのIDを指定します。
        // また、第4引数の配列の並び順とViewIDの並び順は対応させる必要があります。
        // 第6引数 Adapterの振る舞いを指定します。
        mSimpleCursorAdapter = new SimpleCursorAdapter(this, R.layout.list_item_book, mCursor, from, to, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);

        listView.setAdapter(mSimpleCursorAdapter);

        findViewById(R.id.Add).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                insert(mBookOpenHelper);
                // データを再読み込みしてListの表示を最新のものにします
                mSimpleCursorAdapter.getCursor().requery();
            }
        });

    }

    private void insert(BookOpenHelper bookOpenHelper) {

        SQLiteDatabase db = bookOpenHelper.getWritableDatabase();

        ContentValues values = new ContentValues();
        values.put(Book.COLUMN_NAME_BOOK_TITLE, "TITLE1");
        values.put(Book.COLUMN_NAME_BOOK_PUBLISHER, "PUBLISHER1");
        values.put(Book.COLUMN_NAME_BOOK_PRICE, "PRICE1");

        db.insert(Book.BOOK_TABLE_NAME, null, values);
    }


    private Cursor read(BookOpenHelper bookOpenHelper) {

        SQLiteDatabase db = bookOpenHelper.getReadableDatabase();

        String[] projection = {
                Book._ID,
                Book.COLUMN_NAME_BOOK_TITLE,
                Book.COLUMN_NAME_BOOK_PUBLISHER,
                Book.COLUMN_NAME_BOOK_PRICE
        };

        String selection = Book.COLUMN_NAME_BOOK_PRICE + " = ?";
        String[] selectionArgs = {
                "PRICE1"
        };

        Cursor cursor = db.query(Book.BOOK_TABLE_NAME, projection, selection, selectionArgs, null, null, null);
        return cursor;
    }

list_item_book.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/Title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />

    <TextView
        android:id="@+id/Price"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />

</LinearLayout>

simple_cusoradapter

ContentProvider

ContentProvider は他のアプリケーションに対して、自身のアプリケーションが持つデータを公開する機能を提供します。
Contacts Provider、Calendar Provider として提供されており、連絡先情報やカレンダー情報をどのアプリからでもアクセスできるようになっています。また、ContentProvider は独自に作成することができます。
参考:Calendar Provider | Android Developers
参考:Contacts Provider | Android Developers

ContentProvider はデータ公開といった側面以外でも以下のような利点が考えられるため、利用する価値があります。

  • ContentProvider で決められた interface が提供されているため利用側は一定の手順でデータの取得、操作をすることができます。
  • 内部は隠蔽されているため、内部実装を変更しても利用側はそのことを意識する必要はありあません。
  • データの変更の監視が容易にできます。

公式ドキュメントでは自身のアプリ内でのみ使用するデータベースの場合は、プロバイダーは必要ないとありますが、適切に公開範囲を指定して運用すれば上記のような利点を得ることができます。

You don’t need a provider to use an SQLite database if the use is entirely within your own application.

[BeforeYouStart Android Developers](http://developer.android.com/guide/topics/providers/content-provider-creating.html#BeforeYouStart)

ただし、自身のアプリ内でのみで使用するプライベートな ContentProvider の設定は Android 2.2 以前では有効ではないため注意が必要です。

ContentProviderの作成

ContentProviderを作成するには以下の手順が必要となります。

  1. ContentURIの定義
  2. 独自ContentProviderの作成

ContentURIの定義

ContentURI はプロバイダ内のデータを識別するためのURIです。データ取得対象となるプロバイダを特定するのに使用します。
ContentURIは以下のもので構成されます。

Authority : 一意となる文字列を指定します。ここではコンテンツプロバイダを使用するクラスの完全修飾名を設定します。
Path : アクセスするデータの構造体です。ここではテーブル名を指定します。
Scheme : “content://” 文字列です。コンテンツURIとして識別するために指定します。

Authorityはこのようになります。
Book.java

    private static final String AUTHORITY = "jp.mixi.sample.contentprovider.Book";

ContentURI は Uri 型で定義します。
Book.java

    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book");

独自ContentProviderの作成

独自 ContentProvider を作成するには ContentProvider クラスを継承し、以下のメソッドを実装する必要があります。

メソッド名 説明
query プロバイダからデータを取得します。戻り値はCursorです。
insert プロバイダに新しく行を追加します。戻り値は新たに追加された行のContentURIです。
update プロバイダの各行を更新します。戻り値は更新した行の数です。
delete プロバイダから行を削除します。戻り値は削除した行の数です。
getType ContentURIに対応するMIMEタイプを返します。
onCreate プロバイダを初期化します。

各メソッド内でそれぞれの永続化の処理を実装していきます。

BookContentProvider.java

public class BookContentProvider extends ContentProvider {

    private BookOpenHelper mBookOpenHelper;

    // 利用者がメソッドを呼び出したURIに対応する処理を判定処理に使用します
    private static final UriMatcher URI_MATCHER;
    static {
        URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
        URI_MATCHER.addURI(Book.AUTHORITY, Book.BOOK_TABLE_NAME, Book.BOOK);
    }
    @SuppressWarnings("unused")
    private static final String TAG = BookContentProvider.class.getSimpleName();

    // アプリケーション起動時にメインスレッド上で呼ばれます。そのため、時間がかかる処理は行うのは禁止されています。
    // ここで必要な初期化処理を行います。
    @Override
    public boolean onCreate() {
        mBookOpenHelper = new BookOpenHelper(getContext());
        return true;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        isValidUri(uri);

        SQLiteDatabase db = mBookOpenHelper.getWritableDatabase();
        int deletedCount = db.delete(Book.BOOK_TABLE_NAME, selection, selectionArgs);
        // 設定したURIのデータに変更があったことを通知します
        getContext().getContentResolver().notifyChange(uri, null);
        return deletedCount;
    }

    @Override
    public String getType(Uri uri) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        isValidUri(uri);

        SQLiteDatabase db = mBookOpenHelper.getWritableDatabase();
        long rowId = db.insert(Book.BOOK_TABLE_NAME, null, values);
        // 追加された行のURIを生成。content://jp.mixi.sample.contentprovider.Book/book/1
        Uri insertedUri = ContentUris.withAppendedId(uri, rowId);
        // 設定したURIのデータに変更があったことを通知します
        getContext().getContentResolver().notifyChange(insertedUri, null);
        return insertedUri;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        isValidUri(uri);

        SQLiteDatabase db = mBookOpenHelper.getReadableDatabase();
        // URIからテーブル名を取得
        String tableName = uri.getPathSegments().get(0);
        Cursor cursor = db.query(tableName, projection, selection, selectionArgs, null, null, sortOrder);
        // 設定したURIの変更を監視するように設定
        cursor.setNotificationUri(getContext().getContentResolver(), uri);
        return cursor;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        isValidUri(uri);

        SQLiteDatabase db = mBookOpenHelper.getWritableDatabase();
        // URIからテーブル名を取得
        String tableName = uri.getPathSegments().get(0);
        int updatedCount = db.update(tableName, values, selection, selectionArgs);
        getContext().getContentResolver().notifyChange(uri, null);
        return updatedCount;
    }

    // このContentProviderで使用可能なURIかを判定します。
    // 使用不可の場合はIllegalArgumentExceptionを投げます。
    private void isValidUri(Uri uri) {
        if (URI_MATCHER.match(uri) != Book.BOOK) {
            throw new IllegalArgumentException("Unknown URI : " + uri);
        }
    }

UriMatcher クラスは、コンテンツプロバイダのURIのマッチングをサポートするユーティリティクラスです。
isValidUri では UriMatcher を使用して正しいURIであるかを判定しています。

ContentProviderの使用

ContentProvider を使用するには以下の手順が必要となります。

  1. AndroidManifest.xml の修正
  2. ContentProvider へのアクセス処理の実装

AndroidManifest.xml の修正

ContentProvider は AndroidManifest.xml に定義しないと使用することができません。
以下のようにapplicationタグ内に定義します。

        <provider
            android:name="jp.mixi.sample.contentprovider.BookContentProvider"
            android:authorities="jp.mixi.sample.contentprovider.Book"
            android:exported="false" />

android:nameは ContentProvider を継承したサブクラスを指定します。
android:authoritiesはContentURIの定義で定義したものを使用します。
android:exportedは provider の公開非公開を指定します。"false"と指定すると、非公開の ContentProvider となり他のアプリから参照できなくなります。デフォルト値は "true" となっているので、非公開の ContentProvider を作成するときは注意しましょう。

ContentProvider へのアクセス処理の実装

ContentProvider へのアクセスは直接行わず ContentResolver クラスを通じて行います。
ContentResolver クラスにはデータへの基本的なCRUDメソッドが提供されており、ContentProvider を実装したサブクラスオブジェクトとやり取りをします。
各メソッドで指定したコンテントURIに関連したContentProviderの各メソッドが適切に呼び出されます。

Insert
        ContentValues values = new ContentValues();
        for (int i = 0; i < 3; i++) {
            values.clear();
            values.put(Book.COLUMN_NAME_BOOK_TITLE, "TITLE" + i);
            values.put(Book.COLUMN_NAME_BOOK_PUBLISHER, "PUBLISHER" + i);
            values.put(Book.COLUMN_NAME_BOOK_PRICE, "PRICE" + i);

            getContentResolver().insert(Book.CONTENT_URI, values);
        }
Read
        Cursor cursor = getContentResolver().query(Book.CONTENT_URI, null, null, null, null);
        while (cursor.moveToNext()) {
            Log.d(TAG, cursor.getString(cursor.getColumnIndexOrThrow(Book.COLUMN_NAME_BOOK_TITLE)));
        }
        // 処理が完了したらCursorを閉じます
        cursor.close();
Update
        ContentValues values = new ContentValues();
        values.put(Book.COLUMN_NAME_BOOK_PRICE, "PRICE100000");
        int update = getContentResolver().update(Book.CONTENT_URI, values, null, null);
        Log.d(TAG, "update count:" + update);
Delete
        int delete = getContentResolver().delete(Book.CONTENT_URI, null, null);
        Log.d(TAG, "delete count:" + delete);

ContentProviderの外部アプリからの使用

外部アプリから ContentProvider からデータを取得するには以下のようになります。
ContentProviderCallSample MainActivity#onCreate

        Uri uri = Uri.parse("content://" + "jp.mixi.sample.contentprovider.Book" + "/book");
        Cursor cursor = getContentResolver().query(uri, null, null, null, null);
        while (cursor.moveToNext()) {
            Log.d(TAG, "call:" + cursor.getString(cursor.getColumnIndexOrThrow("title")));
        }
        // 処理が完了したらCursorを閉じます
        cursor.close();

呼び出したいContentProvider のURIと取得するデータのパラメータ名(この場合はデータベースのカラム名)がわかっていれば、上記のコードのみで取得することができます。

公開されている ContentProvider を呼び出す側は、AndroidManifest.xml に providerを記述する必要はありません。

取得したデータを ListView に表示(ContentProvider と CursorAdapter)

取得したデータを ListView に表示(CursorAdapter)ほどんど同じなため全体のコードは割愛します。
異なる点としてContentProvider の場合、registerContentObserver でデータ更新時のコールバックを取得することができます。
ContentProviderLoaderSample MainActivity.java

        final Cursor cursor = getContentResolver().query(Book.CONTENT_URI, null, null, null, null);
        // Cursorのデータが更新されたことを通知する
        // ContentProviderでsetNotificationUri()をしないと動作しない
        cursor.registerContentObserver(new ContentObserver(new Handler()) {
            @Override
            public void onChange(boolean selfChange) {
                cursor.requery();
            }
        });

CursorLoader

データベースからのデータの読み出しに特化した Loader です。
CursorLoader は AsyncTaskLoader を拡張したもので、バックグラウンドスレッドでCursorを取得する機能を提供しています。
基本的な実装方法は2.08. 非同期処理の Loader の呼び出しとコールバックと同様です。
異なる点は、LoaderCallbacks のジェネリックに Cursor 型を指定すること、onCreateLoader メソッドの戻り値に CursorLoader を返却すること。コールバック(onLoadFinished メソッド、onLoaderReset メソッド)で Adapter を更新することです。

ContentProviderLoaderSample MainLoaderActivity.java

/**
 * Loaderを使用したListViewを表示するサンプルです。 <br>
 * AdapterにはSimpleCursorAdapterを使用。
 */
public class MainLoaderActivity extends FragmentActivity implements LoaderCallbacks<Cursor> {

    private SimpleCursorAdapter mSimpleCursorAdapter;

    private ListView mListView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mListView = (ListView) findViewById(R.id.ListView);
        // UIにバインドするデータのカラム名
        String[] from = {
                Book.COLUMN_NAME_BOOK_TITLE, Book.COLUMN_NAME_BOOK_PRICE
        };
        // 指定したカラムのデータを表示するViewのIDを指定します。
        int[] to = {
                R.id.Title, R.id.Price
        };

        // 第3引数のCursorはコールバックで設定されるのでnullを渡しています
        mSimpleCursorAdapter = new SimpleCursorAdapter(this, R.layout.list_item_book, null, from, to, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
        mListView.setAdapter(mSimpleCursorAdapter);

        // ボタンクリックでインサート処理を実行
        findViewById(R.id.ADD).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                insert();
            }
        });

        // ローダの管理をするオブジェクト
        LoaderManager loaderManager = getSupportLoaderManager();
        // ローダを初期化して非同期処理を開始する
        loaderManager.initLoader(0, null, this);

    }

    private void insert() {
        ContentValues values = new ContentValues();
        for (int i = 0; i < 3; i++) {
            values.clear();
            values.put(Book.COLUMN_NAME_BOOK_TITLE, "TITLE" + i);
            values.put(Book.COLUMN_NAME_BOOK_PUBLISHER, "PUBLISHER" + i);
            values.put(Book.COLUMN_NAME_BOOK_PRICE, "PRICE" + i);

            getContentResolver().insert(Book.CONTENT_URI, values);
        }
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        // ここでデータの取得条件の指定が可能です。
        // CursorLoader (Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
        return new CursorLoader(this, Book.CONTENT_URI, null, null, null, null);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
        // 古いCursorと新しいCursorを入れ替えます。そのため最新のデータが表示されます。
        mSimpleCursorAdapter.swapCursor(c);
    }

    @Override
    public void onLoaderReset(Loader<Cursor> cursor) {
        // 古いCursorと新しいCursorを入れ替えます。そのため最新のデータが表示されます。
        mSimpleCursorAdapter.swapCursor(null);
    }

}

CursorLoader と ContentProvider を使用することにより、上記のようにデータ取得の非同期化と更新の反映を独自で実装する必要がなくなります。

実習と課題

プロジェクトの開き方は課題プロジェクトの開き方を参照してください。

実習

この実習では 1-10-DatabasePracticeを使用します。

  1. SQLiteOpenHelper クラス(BookOpenHelper)が実装されています。SQLiteActivity に insert,delete,update,qureyメソッドを使用する機能を実装し実行結果をログで確認してください。
  2. BookContentProviderのTODO部分を実装し、データベース処理を ContentProvider で提供するようにしてください。
  3. 新しくプロジェクトを作成し、上の実習で実装したBookContentProviderをそのアプリでContentProviderからデータの取得を行ってください。
    その後1-10-DatabasePracticeの AndroidManifest.xml を修正して非公開の ContentProvider に変更し、データの取得ができないことを確認してください。

課題

この課題では 1-10-DatabaseAssignmentを使用します。

  1. 以下のデータベースを作成し、 SQLiteOpenHelper クラスを作成し SQLiteActivity で insert , qurey を実行してください。
  2. SQLiteListActivityのListViewに 1で作成したデータベースを表示してください
    Adapter には SimpleCursorAdapter を使用してください。
  3. SQLite3Database 2で実装したSQLiteOpenHelperを使用し ContentProvider を作成してください
    さらに CursorLoader 作成し、ContentProvider を呼び出してください。

DB名 : plactice
テーブル名 : android_code_name

カラム名 内容
_id プライマリーキー
name テキスト型 null禁止
version テキスト型 null許容