2010-09-28 2 views
87

Je suis en train de créer une application dans laquelle j'ai une table pour les événements et une table pour les lieux. Je veux pouvoir accorder à d'autres applications l'accès à ces données. J'ai quelques questions liées aux meilleures pratiques pour ce genre de problème.Meilleures pratiques pour exposer plusieurs tables à l'aide de fournisseurs de contenu dans Android

  1. Comment dois-je structurer les classes de base de données? J'ai actuellement des classes pour EventsDbAdapter et VenuesDbAdapter, qui fournissent la logique pour interroger chaque table, tout en ayant un DbManager séparé (extends SQLiteOpenHelper) pour gérer les versions de base de données, créer/mettre à jour les bases de données. Est-ce la solution recommandée, ou serais-je mieux soit de tout consolider à une classe (ie le DbManager) ou de tout séparer et de laisser chaque adaptateur étendre SQLiteOpenHelper?

  2. Comment devrais-je concevoir des fournisseurs de contenu pour plusieurs tables? En étendant la question précédente, devrais-je utiliser un fournisseur de contenu pour l'ensemble de l'application ou devrais-je créer des fournisseurs distincts pour les événements et les sites?

La plupart des exemples ne concernent que les applications à table unique, donc j'apprécierais n'importe quel pointeur ici.

Répondre

10

Je recommande de vérifier le code source de l'Android 2.x ContactProvider. (Qui peut être trouvé en ligne). Ils gèrent les requêtes inter-tables en fournissant des vues spécialisées sur lesquelles vous exécutez ensuite des requêtes sur l'arrière-plan. Sur le frontal, ils sont accessibles à l'appelant via différents URI via un seul fournisseur de contenu. Vous voudrez probablement également fournir une classe ou deux pour contenir des constantes pour les noms de champs de votre table et les chaînes d'URI. Ces classes peuvent être fournies sous la forme d'une API incluse ou d'une classe de dépôt, et faciliteront grandement l'utilisation de l'application consommatrice.

C'est un peu complexe, vous pouvez également vouloir vérifier comment le calendrier aussi bien pour avoir une idée de ce que vous faites et n'avez pas besoin. Vous n'aurez besoin que d'un seul adaptateur de base de données et d'un seul fournisseur de contenu par base de données (pas par table) pour effectuer la majeure partie du travail, mais vous pouvez utiliser plusieurs adaptateurs/fournisseurs si vous le souhaitez. Cela rend les choses un peu plus compliquées.

+5

com.android.providers.contacts.ContactsProvider2.java https://github.com/android/platform_packages_providers_contactsprovider/blob/master/src/com/android/providers/contacts/ContactsProvider2.java – Alex

+0

@ Marloke Merci. Ok, je comprends que même l'équipe Android utilise la solution 'switch', mais cette partie que vous avez mentionnée:' Ils traitent les requêtes inter-tables en fournissant des vues spécialisées sur lesquelles vous exécutez ensuite des requêtes sur le back-end. Sur le frontal, ils sont accessibles à l'appelant via différents URI via un seul fournisseur de contenu ». Pensez-vous que vous pourriez expliquer cela un peu plus en détail? – eddy

112

Il est probablement un peu tard pour vous, mais d'autres peuvent trouver cela utile.

Tout d'abord, vous devez créer plusieurs 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"); 

Ensuite, vous développez votre 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);  
} 

Ensuite, créez vos tables

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

Ne pas oublier d'ajouter la deuxième DATABASE_CREATE à onCreate()

Vous allez utiliser un bloc pour déterminer quelle table est utilisée.Ceci est mon code d'insertion

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

Vous devrez Devide le delete, update, getType, etc. Quel que soit votre fournisseur appelle à DATABASE_TABLE ou CONTENT_URI vous ajouterez un cas et ont DATABASE_TABLE1 ou CONTENT_URI1 dans un seul et # 2 dans le prochain et ainsi de suite pour autant que vous le souhaitez.

+1

Merci pour votre réponse, c'était assez proche de la solution que j'ai fini par utiliser. Je trouve que les fournisseurs complexes qui travaillent avec plusieurs tables obtiennent beaucoup de déclarations de changement, ce qui ne semble pas si élégant. Mais je comprends que c'est la façon dont la plupart des gens le font. –

+0

Est-ce que notifyChange est vraiment censé utiliser le _uri et non l'uri d'origine? – span

+16

Est-ce la norme acceptée avec Android? Cela fonctionne, évidemment, mais semble un peu "maladroit". – prolink007

7

Un ContentProvider peut servir plusieurs tables, mais elles devraient être quelque peu liées. Cela fera une différence si vous avez l'intention de synchroniser vos fournisseurs. Si vous voulez synchroniser séparément, disons Contacts, Mail ou Calendrier, vous aurez besoin de différents fournisseurs pour chacun d'entre eux, même s'ils finissent dans la même base de données ou sont synchronisés avec le même service, car les adaptateurs de synchronisation sont directement liés à un fournisseur particulier. Pour autant que je sache, vous ne pouvez utiliser qu'un seul SQLiteOpenHelper par base de données, car il stocke ses méta-informations dans une table de la base de données. Donc, si votre ContentProviders accède à la même base de données, vous devrez partager l'Helper un peu.

6

Note: Ceci est une clarification/modification apportée par Opy.

Cette approche subdivisant chacun des insert, delete, update et getType méthodes avec les déclarations de commutation afin de traiter chacun de vos tables individuelles. Vous utiliserez un CAS pour identifier chaque table (ou uri) à référencer. Chaque CAS est ensuite mappé à l'une de vos tables ou URI. Par exemple, TABLE1 ou URI1 est sélectionné dans CAS # 1, etc. pour toutes les tables utilisées par votre application.

Voici un exemple de l'approche. C'est pour la méthode d'insertion. Il est implémenté un peu différemment d'Opy mais remplit la même fonction. Vous pouvez sélectionner le style que vous préférez. Je voulais aussi m'assurer que l'insert retourne une valeur même si l'insertion de la table échoue. Dans ce cas, il renvoie un -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] 
Questions connexes