32

L'obiettivo: aggiornare database da dati XMLAndroid: le transazioni SQLite quando si usa ContentResolver

Il processo:

  • avviare la transazione
  • Elimina tutte le righe esistenti dalle tabelle
  • Per ogni elemento principale di XML analizzato inserire la riga nella tabella principale e ottenere PK
  • Per ogni bambino del principale elemento inserto record nella seconda tabella che fornisce FK dal passaggio precedente
  • commit della transazione

roba piuttosto standard per quanto riguarda le operazioni di db. Il problema è che le operazioni CRUD non vengono eseguite entro ContentProvider, ma piuttosto usando ContentResolver così l'inserto ad esempio si presenta come resolver.insert(CONTENT_URI, contentValues). L'API ContentResolver non sembra avere nulla a che fare con la transazione e non posso usare bulkInsert poiché sto inserendo in 2 tabelle in modo intermittente (in più voglio anche avere delete all'interno della transazione).

Stavo pensando di registrare il mio personalizzato ContentProvider come ascoltatore utilizzando registerContentObserver ma dal momento che ContentResolver#acquireProvider metodi sono nascosti Come ottengo il riferimento giusto?

Sono sfortunato?

+2

vedono questo: http://stackoverflow.com/questions/4655291/semantics-of-withvaluebackreference –

+0

Hai mai trovato una soluzione a questo? Non riesco a trovare una soluzione che funziona – jamesc

risposta

41

ho visto che nel codice sorgente dell'applicazione Google I/O, hanno la precedenza su ContentProvider s' applyBatch() metodo e utilizzare le transazioni all'interno di esso. Quindi, si crea un lotto di ContentProviderOperation se poi si chiama getContentResolver().applyBatch(uri_authority, batch).

Sto pensando di utilizzare questo approccio per vedere come funziona. Sono curioso di sapere se qualcun altro l'ha provato.

+7

Ho provato questo approccio e funziona bene. Tuttavia, ogni ContentProviderOperation nel batch è un'operazione atomica. Ciò che intendo è che non esiste un modo per gestire correttamente le operazioni dipendenti per le relazioni principale-dettaglio in cui è necessaria la chiave di identità creata dalla prima operazione come input per le operazioni successive. L'ho già chiesto in precedenza, ma ho ricevuto risposte zero (http://stackoverflow.com/questions/3224857/master-detail-using-contentresolver-applybatch). – Dan

+0

L'ho provato anche io e ho notato un aumento delle prestazioni di oltre il 1000 percento. Semplicemente copiando il codice dal Progetto IOShed al mio Provider. – fmo

+0

Questa risposta non è corretta. L'impl di default di applyBatch() 'esegue solo iterazioni sulle operazioni e le applica in isolamento. Questo fornisce solo un mezzo per implementare una transazione, sovrascrivendo 'applyBatch()' nel tuo ContentProvider. Non fornisce un comportamento transazionale per conto proprio. Se non controlli l'implementazione di 'ContentProvider', sei sfortunato. –

4

Va bene, quindi questo non si confonde senza meta: l'unico modo che posso pensare è codificare startTransaction e endTransaction come richieste di query basate su URL. Qualcosa come ContentResolver.query(START_TRANSACTION, null, null, null, null). Poi, nel ContentProvider#query in base alla sede di avvio chiamata URL o transazione fine

16

È possibile eseguire inserimenti multi tabella basati sulle transazioni in modo abbastanza pulito da Android 2.1 utilizzando ContentProviderOperation, come menzionato da Kaciula.

Quando si crea l'oggetto ContentProviderOperation, è possibile chiamare .withValueBackReference (fieldName, refNr). Quando l'operazione viene applicata utilizzando applyBatch, il risultato è che l'oggetto ContentValues ​​fornito con la chiamata insert() avrà un intero iniettato. Il numero intero sarà codificato con la stringa fieldName e il suo valore verrà recuperato da ContentProviderResult di ContentProviderOperation precedentemente applicato, indicizzato da refNr.

Fare riferimento al codice di esempio riportato di seguito. Nell'esempio, una riga viene inserita in table1 e l'ID risultante (in questo caso "1") viene quindi utilizzato come valore quando si inserisce la riga nella tabella 2. Per brevità, ContentProvider non è connesso a un database. Nel ContentProvider, ci sono stampe dove sarebbe opportuno aggiungere la gestione della transazione.

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

È possibile ottenere l'attuazione dell'oggetto fornitore di contenuti in sé (se nello stesso processo, suggerimento: è possibile controllare il processo del provider con multiprocesso = "true" o di un processo = "" http://developer.android.com/guide/topics/manifest/provider-element.html) usando ContentProviderClient.getLocalContentProvider() che può essere castato all'implementazione del tuo provider che può fornire funzionalità extra come un reset() che chiude ed elimina il database e puoi anche restituire un'istanza della classe Transaction personalizzata con i metodi save() e 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()); 
} 

Poi:

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