web-dev-qa-db-ja.com

Androidでコンテンツプロバイダーを使用して複数のテーブルを公開するためのベストプラクティス

イベント用のテーブルと会場用のテーブルがあるアプリを作成しています。他のアプリケーションにこのデータへのアクセスを許可したい。この種の問題のベストプラクティスに関する質問がいくつかあります。

  1. データベースクラスをどのように構成する必要がありますか現在、EventsDbAdapterおよびVenuesDbAdapterのクラスがあり、各テーブルを照会するためのロジックを提供しますが、データベースバージョンの作成、作成/アップグレードのための個別のDbManager(SQLiteOpenHelperを拡張)を持っていますデータベース(getWriteable/ReadeableDatabase)へのアクセスを許可します。これは推奨される解決策ですか、それともすべてを1つのクラス(つまりDbManager)に統合するか、すべてを分離して各アダプターにSQLiteOpenHelperを拡張させる方がよいでしょうか?

  2. 複数のテーブルのコンテンツプロバイダーをどのように設計する必要がありますか?前の質問を拡張し、アプリ全体に1つのコンテンツプロバイダーを使用する必要がありますか、またはイベントと会場に個別のプロバイダーを作成する必要がありますか?

私が見つけたほとんどの例は、単一テーブルのアプリのみを扱っているので、ここでのポインタに感謝します。

89
Gunnar Lium

おそらくあなたには少し遅れますが、他の人はこれが便利だと思うかもしれません。

まず、複数のCONTENT_URIを作成する必要があります

_public static final Uri CONTENT_URI1 = 
    Uri.parse("content://"+ PROVIDER_NAME + "/sampleuri1");
public static final Uri CONTENT_URI2 = 
    Uri.parse("content://"+ PROVIDER_NAME + "/sampleuri2");
_

次に、URI Matcherを展開します

_private static final UriMatcher uriMatcher;
static {
    uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    uriMatcher.addURI(PROVIDER_NAME, "sampleuri1", SAMPLE1);
    uriMatcher.addURI(PROVIDER_NAME, "sampleuri1/#", SAMPLE1_ID);      
    uriMatcher.addURI(PROVIDER_NAME, "sampleuri2", SAMPLE2);
    uriMatcher.addURI(PROVIDER_NAME, "sampleuri2/#", SAMPLE2_ID);      
}
_

次に、テーブルを作成します

_private static final String DATABASE_NAME = "sample.db";
private static final String DATABASE_TABLE1 = "sample1";
private static final String DATABASE_TABLE2 = "sample2";
private static final int DATABASE_VERSION = 1;
private static final String DATABASE_CREATE1 =
    "CREATE TABLE IF NOT EXISTS " + DATABASE_TABLE1 + 
    " (" + _ID1 + " INTEGER PRIMARY KEY AUTOINCREMENT," + 
    "data text, stuff text);";
private static final String DATABASE_CREATE2 =
    "CREATE TABLE IF NOT EXISTS " + DATABASE_TABLE2 + 
    " (" + _ID2 + " INTEGER PRIMARY KEY AUTOINCREMENT," + 
    "data text, stuff text);";
_

2番目の_DATABASE_CREATE_をonCreate()に追加することを忘れないでください

switch-caseブロックを使用して、使用するテーブルを決定します。これは私の挿入コードです

_@Override
public Uri insert(Uri uri, ContentValues values) {
    Uri _uri = null;
    switch (uriMatcher.match(uri)){
    case SAMPLE1:
        long _ID1 = db.insert(DATABASE_TABLE1, "", values);
        //---if added successfully---
        if (_ID1 > 0) {
            _uri = ContentUris.withAppendedId(CONTENT_URI1, _ID1);
            getContext().getContentResolver().notifyChange(_uri, null);    
        }
        break;
    case SAMPLE2:
        long _ID2 = db.insert(DATABASE_TABLE2, "", values);
        //---if added successfully---
        if (_ID2 > 0) {
            _uri = ContentUris.withAppendedId(CONTENT_URI2, _ID2);
            getContext().getContentResolver().notifyChange(_uri, null);    
        }
        break;
    default: throw new SQLException("Failed to insert row into " + uri);
    }
    return _uri;                
}
_

deleteupdategetTypeなどを作成する必要があります。プロバイダーがDATABASE_TABLEまたはCONTENT_URIを呼び出す場合は常に、ケースを追加し、DATABASE_TABLE1またはCONTENT_URI1を1つに入れます。そして、次の#2など、必要な数だけ。

114
Opy

Android 2.x ContactProvider。(オンラインで見つけることができます)。フロントエンドでは、単一のコンテンツプロバイダーを介してさまざまなURIを介して呼び出し元にアクセスできますテーブルフィールド名とURI文字列の定数を保持するクラスまたは2つを提供することもできます。 APIインクルードとして、またはクラスのドロップとして提供され、消費アプリケーションの使用をはるかに簡単にします。

少し複雑なので、カレンダーの仕組みも確認して、自分が何をして何が必要でないのかを考えてみてください。

ほとんどの作業を行うには、データベースごと(テーブルごとではなく)に1つのDBアダプターと1つのコンテンツプロバイダーのみが必要ですが、本当に必要な場合は複数のアダプター/プロバイダーを使用できます。物事が少し複雑になります。

10
Charles B

1つのContentProviderで複数のテーブルを提供できますが、それらはある程度関連している必要があります。プロバイダーを同期する場合、違いが生じます。連絡先、メール、カレンダーなどの個別の同期が必要な場合は、同期アダプターが直接関連付けられているため、それらが同じデータベース内にあるか、同じサービスと同期されている場合でも、それぞれに異なるプロバイダーが必要になります特定のプロバイダー。

ただし、私が知る限り、データベース内のテーブルにメタ情報を保存するため、データベースごとに使用できるSQLiteOpenHelperは1つだけです。したがって、ContentProvidersが同じデータベースにアクセスする場合、なんとかしてヘルパーを共有する必要があります。

7
Timo Ohr

注:これは、Opyが提供する回答の明確化/変更です。

このアプローチは、個々のテーブルを処理するために、insertdeleteupdate、およびgetTypeの各メソッドをswitchステートメントで細分化します。 CASEを使用して、参照する各テーブル(またはuri)を識別します。各CASEは、テーブルまたはURIのいずれかにマップされます。たとえば、TABLE1またはRI1は、ケース#1などで、アプリが使用するすべてのテーブルに対して選択されます。

これがアプローチの例です。これはinsertメソッド用です。 Opyの実装とは少し異なりますが、同じ機能を実行します。好みのスタイルを選択できます。また、テーブルの挿入が失敗した場合でも、挿入が値を返すことを確認したかったその場合、-1を返します。

  @Override
  public Uri insert(Uri uri, ContentValues values) {
    int uriType = sURIMatcher.match(uri);
    SQLiteDatabase sqlDB; 

    long id = 0;
    switch (uriType){ 
        case TABLE1: 
            sqlDB = Table1Database.getWritableDatabase();
            id = sqlDB.insert(Table1.TABLE_NAME, null, values); 
            getContext().getContentResolver().notifyChange(uri, null);
            return Uri.parse(BASE_PATH1 + "/" + id);
        case TABLE2: 
            sqlDB = Table2Database.getWritableDatabase();
            id = sqlDB.insert(Table2.TABLE_NAME, null, values); 
            getContext().getContentResolver().notifyChange(uri, null);
            return Uri.parse(BASE_PATH2 + "/" + id);
        default: 
            throw new SQLException("Failed to insert row into " + uri); 
            return -1;
    }       
  }  // [END insert]
7
PeteH

ContentProviderの最良のデモと説明を見つけたので、Android Standards。

契約クラス

 /**
   * The Content Authority is a name for the entire content provider, similar to the relationship
   * between a domain name and its website. A convenient string to use for content authority is
   * the package name for the app, since it is guaranteed to be unique on the device.
   */
  public static final String CONTENT_AUTHORITY = "com.androidessence.moviedatabase";

  /**
   * The content authority is used to create the base of all URIs which apps will use to
   * contact this content provider.
   */
  private static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);

  /**
   * A list of possible paths that will be appended to the base URI for each of the different
   * tables.
   */
  public static final String PATH_MOVIE = "movie";
  public static final String PATH_GENRE = "genre";

および内部クラス:

 /**
   * Create one class for each table that handles all information regarding the table schema and
   * the URIs related to it.
   */
  public static final class MovieEntry implements BaseColumns {
      // Content URI represents the base location for the table
      public static final Uri CONTENT_URI =
              BASE_CONTENT_URI.buildUpon().appendPath(PATH_MOVIE).build();

      // These are special type prefixes that specify if a URI returns a list or a specific item
      public static final String CONTENT_TYPE =
              "vnd.Android.cursor.dir/" + CONTENT_URI  + "/" + PATH_MOVIE;
      public static final String CONTENT_ITEM_TYPE =
              "vnd.Android.cursor.item/" + CONTENT_URI + "/" + PATH_MOVIE;

      // Define the table schema
      public static final String TABLE_NAME = "movieTable";
      public static final String COLUMN_NAME = "movieName";
      public static final String COLUMN_RELEASE_DATE = "movieReleaseDate";
      public static final String COLUMN_GENRE = "movieGenre";

      // Define a function to build a URI to find a specific movie by it's identifier
      public static Uri buildMovieUri(long id){
          return ContentUris.withAppendedId(CONTENT_URI, id);
      }
  }

  public static final class GenreEntry implements BaseColumns{
      public static final Uri CONTENT_URI =
              BASE_CONTENT_URI.buildUpon().appendPath(PATH_GENRE).build();

      public static final String CONTENT_TYPE =
              "vnd.Android.cursor.dir/" + CONTENT_URI + "/" + PATH_GENRE;
      public static final String CONTENT_ITEM_TYPE =
              "vnd.Android.cursor.item/" + CONTENT_URI + "/" + PATH_GENRE;

      public static final String TABLE_NAME = "genreTable";
      public static final String COLUMN_NAME = "genreName";

      public static Uri buildGenreUri(long id){
          return ContentUris.withAppendedId(CONTENT_URI, id);
      }
  }

SQLiteOpenHelperを使用してデータベースを作成しています:

public class MovieDBHelper extends SQLiteOpenHelper{
    /**
     * Defines the database version. This variable must be incremented in order for onUpdate to
     * be called when necessary.
     */
    private static final int DATABASE_VERSION = 1;
    /**
     * The name of the database on the device.
     */
    private static final String DATABASE_NAME = "movieList.db";

    /**
     * Default constructor.
     * @param context The application context using this database.
     */
    public MovieDBHelper(Context context){
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    /**
     * Called when the database is first created.
     * @param db The database being created, which all SQL statements will be executed on.
     */
    @Override
    public void onCreate(SQLiteDatabase db) {
        addGenreTable(db);
        addMovieTable(db);
    }

    /**
     * Called whenever DATABASE_VERSION is incremented. This is used whenever schema changes need
     * to be made or new tables are added.
     * @param db The database being updated.
     * @param oldVersion The previous version of the database. Used to determine whether or not
     *                   certain updates should be run.
     * @param newVersion The new version of the database.
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

    /**
     * Inserts the genre table into the database.
     * @param db The SQLiteDatabase the table is being inserted into.
     */
    private void addGenreTable(SQLiteDatabase db){
        db.execSQL(
                "CREATE TABLE " + MovieContract.GenreEntry.TABLE_NAME + " (" +
                        MovieContract.GenreEntry._ID + " INTEGER PRIMARY KEY, " +
                        MovieContract.GenreEntry.COLUMN_NAME + " TEXT UNIQUE NOT NULL);"
        );
    }

    /**
     * Inserts the movie table into the database.
     * @param db The SQLiteDatabase the table is being inserted into.
     */
    private void addMovieTable(SQLiteDatabase db){
        db.execSQL(
                "CREATE TABLE " + MovieContract.MovieEntry.TABLE_NAME + " (" +
                        MovieContract.MovieEntry._ID + " INTEGER PRIMARY KEY, " +
                        MovieContract.MovieEntry.COLUMN_NAME + " TEXT NOT NULL, " +
                        MovieContract.MovieEntry.COLUMN_RELEASE_DATE + " TEXT NOT NULL, " +
                        MovieContract.MovieEntry.COLUMN_GENRE + " INTEGER NOT NULL, " +
                        "FOREIGN KEY (" + MovieContract.MovieEntry.COLUMN_GENRE + ") " +
                        "REFERENCES " + MovieContract.GenreEntry.TABLE_NAME + " (" + MovieContract.GenreEntry._ID + "));"
        );
    }
}

コンテンツプロバイダー:

public class MovieProvider extends ContentProvider {
    // Use an int for each URI we will run, this represents the different queries
    private static final int GENRE = 100;
    private static final int GENRE_ID = 101;
    private static final int MOVIE = 200;
    private static final int MOVIE_ID = 201;

    private static final UriMatcher sUriMatcher = buildUriMatcher();
    private MovieDBHelper mOpenHelper;

    @Override
    public boolean onCreate() {
        mOpenHelper = new MovieDBHelper(getContext());
        return true;
    }

    /**
     * Builds a UriMatcher that is used to determine witch database request is being made.
     */
    public static UriMatcher buildUriMatcher(){
        String content = MovieContract.CONTENT_AUTHORITY;

        // All paths to the UriMatcher have a corresponding code to return
        // when a match is found (the ints above).
        UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
        matcher.addURI(content, MovieContract.PATH_GENRE, GENRE);
        matcher.addURI(content, MovieContract.PATH_GENRE + "/#", GENRE_ID);
        matcher.addURI(content, MovieContract.PATH_MOVIE, MOVIE);
        matcher.addURI(content, MovieContract.PATH_MOVIE + "/#", MOVIE_ID);

        return matcher;
    }

    @Override
    public String getType(Uri uri) {
        switch(sUriMatcher.match(uri)){
            case GENRE:
                return MovieContract.GenreEntry.CONTENT_TYPE;
            case GENRE_ID:
                return MovieContract.GenreEntry.CONTENT_ITEM_TYPE;
            case MOVIE:
                return MovieContract.MovieEntry.CONTENT_TYPE;
            case MOVIE_ID:
                return MovieContract.MovieEntry.CONTENT_ITEM_TYPE;
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        Cursor retCursor;
        switch(sUriMatcher.match(uri)){
            case GENRE:
                retCursor = db.query(
                        MovieContract.GenreEntry.TABLE_NAME,
                        projection,
                        selection,
                        selectionArgs,
                        null,
                        null,
                        sortOrder
                );
                break;
            case GENRE_ID:
                long _id = ContentUris.parseId(uri);
                retCursor = db.query(
                        MovieContract.GenreEntry.TABLE_NAME,
                        projection,
                        MovieContract.GenreEntry._ID + " = ?",
                        new String[]{String.valueOf(_id)},
                        null,
                        null,
                        sortOrder
                );
                break;
            case MOVIE:
                retCursor = db.query(
                        MovieContract.MovieEntry.TABLE_NAME,
                        projection,
                        selection,
                        selectionArgs,
                        null,
                        null,
                        sortOrder
                );
                break;
            case MOVIE_ID:
                _id = ContentUris.parseId(uri);
                retCursor = db.query(
                        MovieContract.MovieEntry.TABLE_NAME,
                        projection,
                        MovieContract.MovieEntry._ID + " = ?",
                        new String[]{String.valueOf(_id)},
                        null,
                        null,
                        sortOrder
                );
                break;
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }

        // Set the notification URI for the cursor to the one passed into the function. This
        // causes the cursor to register a content observer to watch for changes that happen to
        // this URI and any of it's descendants. By descendants, we mean any URI that begins
        // with this path.
        retCursor.setNotificationUri(getContext().getContentResolver(), uri);
        return retCursor;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        long _id;
        Uri returnUri;

        switch(sUriMatcher.match(uri)){
            case GENRE:
                _id = db.insert(MovieContract.GenreEntry.TABLE_NAME, null, values);
                if(_id > 0){
                    returnUri =  MovieContract.GenreEntry.buildGenreUri(_id);
                } else{
                    throw new UnsupportedOperationException("Unable to insert rows into: " + uri);
                }
                break;
            case MOVIE:
                _id = db.insert(MovieContract.MovieEntry.TABLE_NAME, null, values);
                if(_id > 0){
                    returnUri = MovieContract.MovieEntry.buildMovieUri(_id);
                } else{
                    throw new UnsupportedOperationException("Unable to insert rows into: " + uri);
                }
                break;
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }

        // Use this on the URI passed into the function to notify any observers that the uri has
        // changed.
        getContext().getContentResolver().notifyChange(uri, null);
        return returnUri;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        int rows; // Number of rows effected

        switch(sUriMatcher.match(uri)){
            case GENRE:
                rows = db.delete(MovieContract.GenreEntry.TABLE_NAME, selection, selectionArgs);
                break;
            case MOVIE:
                rows = db.delete(MovieContract.MovieEntry.TABLE_NAME, selection, selectionArgs);
                break;
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }

        // Because null could delete all rows:
        if(selection == null || rows != 0){
            getContext().getContentResolver().notifyChange(uri, null);
        }

        return rows;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        int rows;

        switch(sUriMatcher.match(uri)){
            case GENRE:
                rows = db.update(MovieContract.GenreEntry.TABLE_NAME, values, selection, selectionArgs);
                break;
            case MOVIE:
                rows = db.update(MovieContract.MovieEntry.TABLE_NAME, values, selection, selectionArgs);
                break;
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }

        if(rows != 0){
            getContext().getContentResolver().notifyChange(uri, null);
        }

        return rows;
    }
}

それがあなたのお役に立てば幸いです。

GitHubのデモ: https://github.com/androidessence/MovieDatabase

記事全体: https://guides.codepath.com/Android/creating-content-providers

参照:

注:デモまたは記事のリンクが将来削除される可能性があるため、コードをコピーしました。