Лучшие практики для демонстрации нескольких таблиц с помощью контент-провайдеров в Android

87

Я создаю приложение, где у меня есть таблица для событий и таблица для мест. Я хочу, чтобы предоставить другим приложениям доступ к этим данным. У меня есть несколько вопросов, связанных с передовыми методами для такого рода проблем.

  • Как структурировать классы базы данных? В настоящее время у меня есть классы для EventsDbAdapter и VenuesDbAdapter, которые предоставляют логику для запроса каждой таблицы, имея отдельный DbManager (расширяет SQLiteOpenHelper) для управления версиями баз данных, создания/обновления баз данных, предоставления доступа к базе данных (getWriteable/ReadeableDatabase). Является ли это рекомендуемым решением, или мне лучше или консолидировать все в один класс (т.е. DbManager) или разделить все и позволить каждому адаптеру расширять SQLiteOpenHelper?

  • Как мне создавать контент-провайдеры для нескольких таблиц? Расширение предыдущего вопроса, следует ли использовать один контент-провайдер для всего приложения или я должен создавать отдельные поставщики для событий и мест проведения?

Большинство примеров, которые я нахожу, касается только приложений с одной таблицей, поэтому я был бы признателен за любые указатели здесь.

Теги:
android-contentprovider

4 ответа

117

Возможно, вам немного поздно, но другие могут найти это полезным.

Сначала вам нужно создать несколько CONTENT_URIs

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-счетчик

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);";

Не забудьте добавить второй DATABASE_CREATE в onCreate()

Для определения используемой таблицы вы будете использовать блок блокировки коммутатора. Это мой код вставки

@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;                
}

Вам нужно будет разделить delete, update, getType и т.д. Везде, где ваш провайдер требует DATABASE_TABLE или CONTENT_URI, вы добавите случай и будете иметь DATABASE_TABLE1 или CONTENT_URI1 в одном и в # 2 в следующем и так далее, сколько хотите.

  • 1
    Спасибо за ваш ответ, это было довольно близко к решению, которое я в конечном итоге использовал. Я нахожу, что сложные провайдеры, работающие с несколькими таблицами, получают много операторов switch, что выглядит не так уж и элегантно. Но я понимаю, что так делают большинство людей.
  • 0
    Действительно ли notifyChange должен использовать _uri, а не оригинальный uri?
Показать ещё 5 комментариев
10

Я рекомендую проверить исходный код для Android 2.x ContactProvider. (Которые можно найти в Интернете). Они обрабатывают запросы кросс-таблиц, предоставляя специализированные представления, которые затем запускают запросы с обратной стороны. На лицевой стороне они доступны для вызывающего абонента через различные URI через одного поставщика контента. Вероятно, вы также захотите предоставить класс или два для хранения констант для имен полей таблицы и строк URI. Эти классы могут быть предоставлены либо в виде API, либо в виде снижения класса, и это значительно облегчит использование приложения-потребителя.

Это немного сложнее, поэтому вы также можете проверить, как календарь также получить представление о том, что вы делаете и не нуждаетесь.

Чтобы выполнить большую часть работы, вам нужен только один адаптер базы данных и один поставщик контента на каждую базу данных (не для таблицы), но вы можете использовать несколько адаптеров/поставщиков, если хотите. Это просто усложняет ситуацию.

  • 5
    com.android.providers.contacts.ContactsProvider2.java github.com/android/platform_packages_providers_contactsprovider/…
  • 0
    @ Марлок Спасибо. Хорошо, я понимаю, что даже команда Android использует решение switch , но эту часть вы упомянули: They handle cross table queries by providing specialized views that you then run queries against on the back end. On the front end they are accessible to the caller via various different URIs through a single content provider . Как вы думаете, вы могли бы объяснить это немного подробнее?
7

Один ContentProvider может обслуживать несколько таблиц, но они должны быть несколько взаимосвязаны. Это будет иметь значение, если вы собираетесь синхронизировать своих поставщиков. Если вам нужна отдельная синхронизация, скажем, "Контакты", "Почта" или "Календарь", вам понадобятся разные провайдеры для каждого из них, даже если они попадают в одну базу данных или синхронизируются с одной и той же услугой, потому что адаптеры синхронизации привязаны напрямую к конкретный поставщик.

Насколько я могу судить, вы можете использовать только один SQLiteOpenHelper для каждой базы данных, хотя, поскольку он хранит свою метаинформацию в таблице в базе данных. Поэтому, если ваш ContentProviders доступ к одной и той же базе данных, вам придётся совместно использовать Helper.

6

Примечание. Это пояснение/изменение ответа, которое предоставляет Opy.

Этот подход подразделяет каждый из методов insert, delete, update и getType с операторами switch для обработки каждой из ваших отдельных таблиц. Вы будете использовать CASE, чтобы идентифицировать каждую таблицу (или uri) для ссылки. Каждый CASE затем сопоставляется с одной из ваших таблиц или URI. Например, TABLE1 или URI1 выбран в CASE # 1 и т.д. Для всех таблиц, которые использует ваше приложение.

Вот пример подхода. Это для метода вставки. Он реализовал несколько иначе, чем Opy, но выполняет ту же функцию. Вы можете выбрать стиль, который вы предпочитаете. Я также хотел, чтобы вставка вернула значение, даже если вставка таблицы не удалась. В этом случае он возвращает a -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]

Ещё вопросы

Сообщество Overcoder
Наверх
Меню