32

L'objectif: actualiser la base de données à partir des données XMLAndroid: les transactions SQLite lors de l'utilisation ContentResolver

Le processus:

  • transaction Début
  • Supprimer toutes les lignes existantes des tables
  • Pour chaque élément principal de XML analysé insérez ligne dans la table principale et obtenir PK
  • par chaque enfant de l'élément principal insert enregistrement dans 2 tableau fournissant FK de l'étape précédente
  • commit transaction

plutôt standard dans la mesure où les opérations db. Le problème est que les opérations CRUD ne sont pas effectuées au sein de ContentProvider mais plutôt en utilisant ContentResolver donc l'insertion ressemble par exemple à resolver.insert(CONTENT_URI, contentValues). L'API ContentResolver ne semble pas avoir de transaction et je ne peux pas utiliser bulkInsert puisque je l'insère dans 2 tables par intermittence (plus je veux aussi avoir delete dans la transaction).

Je pensais à enregistrer mon personnalisé ContentProvider comme auditeur en utilisant registerContentObserver mais depuis ContentResolver#acquireProvider méthodes sont cachés comment puis-je obtenir la bonne référence?

Est-ce que je n'ai pas de chance?

+2

voir ceci: http://stackoverflow.com/questions/4655291/semantics-of-withvaluebackreference –

+0

Avez-vous trouvé jamais une solution à cela? Je ne peux pas trouver une solution qui fonctionne – jamesc

Répondre

41

J'ai vu que dans le code source de l'application Google I/O, ils remplacent la méthode de ContentProviderapplyBatch() et les transactions utilisation à l'intérieur de celui-ci. Donc, vous créez un lot de ContentProviderOperation s et ensuite appelez getContentResolver().applyBatch(uri_authority, batch). Je prévois d'utiliser cette approche pour voir comment cela fonctionne. Je suis curieux de savoir si quelqu'un d'autre a essayé.

+7

J'ai essayé cette approche et cela fonctionne bien. Cependant chaque ContentProviderOperation dans le lot est des opérations atomiques. Ce que je veux dire par là est qu'il n'y a aucun moyen de gérer correctement les opérations dépendantes pour les relations maître-détail où la clé d'identité créée par la première opération est nécessaire comme entrée pour les opérations suivantes. Je l'ai déjà demandé, mais je n'ai reçu aucune réponse (http://stackoverflow.com/questions/3224857/master-detail-using-contentresolver-applybatch). – Dan

+0

Je l'ai essayé aussi et j'ai remarqué un gain de performance de plus de 1000 pour cent. Juste en copiant le code du projet IOShed à mon fournisseur. – fmo

+0

Cette réponse est incorrecte. L'impl implicite de 'applyBatch()' ne fait qu' itérer les opérations et les applique isolément. Cela fournit seulement un moyen d'implémenter une transaction en remplaçant 'applyBatch()' dans votre 'ContentProvider'. Il ne fournit pas de comportement transactionnel par lui-même. Si vous ne contrôlez pas l'implémentation 'ContentProvider', vous n'avez pas de chance. –

4

bien - si cela ne dingle pas sans but: la seule façon que je peux penser est à coder startTransaction et endTransaction que les demandes d'interrogation à base d'URL. Quelque chose comme ContentResolver.query(START_TRANSACTION, null, null, null, null). Puis, en ContentProvider#query en fonction du début de l'appel d'URL enregistrée ou d'une opération de fin

16

Il est possible de faire des insertions multi-tables basées sur les transactions de manière plutôt simple depuis Android 2.1 en utilisant ContentProviderOperation, comme mentionné par kaciula. Lorsque vous générez l'objet ContentProviderOperation, vous pouvez appeler .withValueBackReference (fieldName, refNr).

Lorsque vous générez l'objet ContentProviderOperation, vous pouvez appeler .withValueBackReference (fieldName, refNr). Lorsque l'opération est appliquée en utilisant applyBatch, le résultat est que l'objet ContentValues ​​fourni avec l'appel insert() aura un entier injecté. L'entier sera calée avec la chaîne fieldName, et sa valeur est extraite de la ContentProviderResult d'un ContentProviderOperation appliqué précédemment, indexé par Refnr.

Veuillez vous reporter à l'exemple de code ci-dessous. Dans l'exemple, une ligne est insérée dans le tableau 1, et l'ID résultant (dans ce cas « 1 ») est ensuite utilisée en tant que valeur lors de l'insertion de la rangée dans le tableau 2. Par souci de concision, le ContentProvider est pas connecté à une base de données. Dans le ContentProvider, il y a des impressions où il serait approprié d'ajouter la gestion des transactions.

public class BatchTestActivity extends Activity { 
    /** Called when the activity is first created. */ 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.main); 

     ArrayList<ContentProviderOperation> list = new 
      ArrayList<ContentProviderOperation>(); 

     list.add(ContentProviderOperation. 
      newInsert(BatchContentProvider.FIRST_URI).build()); 
     ContentValues cv = new ContentValues(); 
     cv.put("name", "second_name"); 
     cv.put("refId", 23); 

     // In this example, "refId" in the contentValues will be overwritten by 
     // the result from the first insert operation, indexed by 0 
     list.add(ContentProviderOperation. 
      newInsert(BatchContentProvider.SECOND_URI). 
      withValues(cv).withValueBackReference("refId", 0).build()); 

     try { 
      getContentResolver().applyBatch(
       BatchContentProvider.AUTHORITY, list); 
     } catch (RemoteException e) { 
      e.printStackTrace(); 
     } catch (OperationApplicationException e) { 
      e.printStackTrace(); 
     } 
    } 
} 

public class BatchContentProvider extends ContentProvider { 

    private static final String SCHEME = "content://"; 
    public static final String AUTHORITY = "com.test.batch"; 

    public static final Uri FIRST_URI = 
     Uri.parse(SCHEME + AUTHORITY + "/" + "table1"); 
    public static final Uri SECOND_URI = 
     Uri.parse(SCHEME + AUTHORITY + "/" + "table2"); 


    public ContentProviderResult[] applyBatch(
     ArrayList<ContentProviderOperation> operations) 
      throws OperationApplicationException { 
     System.out.println("starting transaction"); 
     ContentProviderResult[] result; 
     try { 
      result = super.applyBatch(operations); 
     } catch (OperationApplicationException e) { 
      System.out.println("aborting transaction"); 
      throw e; 
     } 
     System.out.println("ending transaction"); 
     return result; 
    } 

    public Uri insert(Uri uri, ContentValues values) { 
     // this printout will have a proper value when 
     // the second operation is applied 
     System.out.println("" + values); 

     return ContentUris.withAppendedId(uri, 1); 
    } 

    // other overrides omitted for brevity 
} 
0

Vous pouvez obtenir la mise en œuvre de l'objet fournisseur de contenu lui-même (si dans le même processus, indice: vous pouvez contrôler le processus du fournisseur avec multiprocessus = « true » ou process = « » http://developer.android.com/guide/topics/manifest/provider-element.html) en utilisant ContentProviderClient.getLocalContentProvider() qui peut être casté à votre implémentation de fournisseur qui peut fournir des fonctionnalités supplémentaires comme un reset() qui ferme et supprime la base de données et vous pouvez également retourner une instance de classe Transaction personnalisée avec les méthodes save() et close().

public class Transaction { 
    protected Transaction (SQLiteDatabase database) { 
     this.database = database; 
     database.beginTransaction(); 
    } 

    public void save() { 
     this.database.setTransactionSuccessful(); 
    } 

    public void close() { 
     this.database.endTransaction(); 
    } 

    private SQLiteDatabase database; 
} 

public Transaction createTransaction() { 
    return new Transaction (this.dbHelper.getWritableDatabase()); 
} 

Puis:

ContentProviderClient client = getContentResolver().acquireContentProviderClient (Contract.authorityLocal); 
Transaction tx = ((LocalContentProvider) client.getLocalContentProvider()).createTransaction();