32 votes

Android : Transactions SQLite lors de l'utilisation de ContentResolver

Le but : rafraîchir la base de données à partir de données XML

Le processus :

  • Début de la transaction
  • Supprimer toutes les lignes existantes des tables
  • Pour chaque élément principal du XML analysé insérer dans la table principale et obtenir PK
  • Pour chaque enfant de l'élément principal insérer enregistrement dans la 2ème table fournissant FK de l'étape précédente
  • Transaction d'engagement

C'est plutôt standard en ce qui concerne les opérations de base de données. Le problème est que les opérations CRUD ne sont pas effectuées au sein de la base de données. ContentProvider mais plutôt en utilisant ContentResolver donc l'insertion par exemple ressemble à resolver.insert(CONTENT_URI, contentValues) . L'API ContentResolver ne semble pas avoir d'éléments relatifs à la transaction et je ne peux pas utiliser la fonction bulkInsert puisque je fais des insertions dans 2 tables par intermittence (et je veux avoir delete à l'intérieur de la transaction également).

J'envisageais d'enregistrer ma marque personnalisée ContentProvider comme auditeur en utilisant registerContentObserver mais comme ContentResolver#acquireProvider sont cachées, comment puis-je obtenir la bonne référence ?

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

41voto

kaciula Points 2747

J'ai vu que dans le code source de l'application Google I/O, ils remplacent ContentProvider 's applyBatch() et utiliser des transactions à l'intérieur de celle-ci. Ainsi, vous créez un lot de ContentProviderOperation et ensuite appeler getContentResolver().applyBatch(uri_authority, batch) .

J'ai l'intention d'utiliser cette approche pour voir comment elle fonctionne. Je suis curieux de savoir si quelqu'un d'autre l'a essayé.

16voto

David Burström Points 748

Il est possible d'effectuer des insertions multi-tables basées sur des transactions de manière assez propre depuis Android 2.1 en utilisant ContentProviderOperation, comme mentionné par kaciula.

Lorsque vous construisez l'objet ContentProviderOperation, vous pouvez appeler .withValueBackReference(fieldName, refNr). Lorsque l'opération est appliquée à l'aide de applyBatch, le résultat est que l'objet ContentValues qui est fourni avec l'appel insert() aura un entier injecté. Le nombre entier est associé à la chaîne fieldName et sa valeur est récupérée dans le ContentProviderResult d'une ContentProviderOperation appliquée précédemment, indexée par refNr.

Veuillez vous référer à l'exemple de code ci-dessous. Dans cet exemple, une ligne est insérée dans la table 1, et l'ID résultant (dans ce cas "1") est ensuite utilisé comme valeur lors de l'insertion de la ligne dans la table 2. Pour des raisons de brièveté, le ContentProvider n'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
}

4voto

Bostone Points 14208

D'accord - pour que cela ne soit pas sans but : le seul moyen auquel je pense est de coder startTransaction et endTransaction comme des requêtes basées sur des URL. Quelque chose comme ContentResolver.query(START_TRANSACTION, null, null, null, null) . Ensuite, dans ContentProvider#query sur la base de la transaction de début ou de fin d'appel de l'URL enregistrée

0voto

Eric Woodruff Points 1886

Vous pouvez obtenir l'implémentation de l'objet fournisseur de contenu lui-même (s'il est dans le même processus, astuce : vous pouvez contrôler le processus du fournisseur avec multiprocess="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 une fonctionnalité supplémentaire comme un reset() qui ferme et supprime la base de données et vous pouvez également retourner une instance de classe de 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 ());
}

Ensuite :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X