2016-04-09 3 views
0

J'ai implémenté Content Provider qui utilise SQLiteDatabase comme source de données de sauvegarde.SQLite DB avec conditions de concurrence Content Provider

Une activité d'écriture dans le DB en appelant getContentResolver().applyBatch(operations), qui devrait être atomique.

protected void onPause() { 
    new Thread(){ 
     @Override 
     public void run() { 
      ArrayList<ContentProviderOperation> ops = new ArrayList<>(); 
      ContentProviderOperation.Builder builder; 
      for (Tag tag: mTopicAdapter.getTags()) { 
       builder = ContentProviderOperation.newUpdate(QuizProvider.TAG_URI); 
       builder.withValue(Tag.Table.SELECTED, tag.getSelectionStatus()); 
       builder.withSelection(Tag.Table._ID + " = " + tag.getId(), null); 
       ops.add(builder.build()); 
      } 
      try { 
       ContentProviderResult[] res = getContentResolver().applyBatch(QuizProvider.AUTHORITY, ops); 
       Timber.d("Update result: %d", res.length); 
       getContentResolver().notifyChange(QuizProvider.TAG_URI, null); 
       getContentResolver().notifyChange(QuizProvider.QUESTION_URI, null); 
      } catch (RemoteException e) { 
       e.printStackTrace(); 
      } catch (OperationApplicationException e) { 
       e.printStackTrace(); 
      } 
     } 
    }.start(); 
    super.onPause(); 
} 

activité deuxième lecture de la DB à l'aide du curseur Loader et obtient parfois des données anciennes (condition de course).

@Override 
public Loader<Cursor> onCreateLoader(int id, Bundle args) { 
    Uri randQuestionUri = QuizProvider.QUESTION_URI 
      .buildUpon() 
      .appendPath("rand").appendPath(Integer.toString(QUIZ_SIZE)) 
      .build(); 
    return new CursorLoader(this, randQuestionUri, null, null, null, null); 
} 

@Override 
public void onLoadFinished(Loader<Cursor> loader, Cursor data) { 
    if (DEBUG) Timber.d("load finished: %d", data.hashCode()); 
    mPagerAdapter.swapCursor(data); 
} 

@Override 
public void onLoaderReset(Loader<Cursor> loader) { 
    mPagerAdapter.swapCursor(null); 
} 

Projet complet here.

Log sortie:

NSA:QuizProvider:138: update db: selected=false _id = 4 
NSA:QuizProvider:138: update db: selected=false _id = 5 
NSA:QuizProvider:138: update db: selected=false _id = 26 
NSA:QuizProvider:138: update db: selected=false _id = 19 
NSA:QuizProvider:138: update db: selected=false _id = 28 
NSA:QuizProvider:138: update db: selected=false _id = 10 
NSA:QuizProvider:138: update db: selected=false _id = 12 
NSA:QuizProvider:138: update db: selected=false _id = 15 
NSA:QuizProvider:138: update db: selected=false _id = 18 
NSA:QuizProvider:138: update db: selected=false _id = 25 
NSA:QuizProvider:138: update db: selected=false _id = 16 
NSA:QuizProvider:138: update db: selected=false _id = 17 
NSA:QuizProvider:138: update db: selected=false _id = 8 
NSA:QuizProvider:138: update db: selected=false _id = 3 
NSA:QuizProvider:138: update db: selected=false _id = 20 
NSA:QuizProvider:138: update db: selected=false _id = 29 
NSA:QuizProvider:138: update db: selected=false _id = 24 
NSA:QuizProvider:138: update db: selected=false _id = 23 
NSA:QuizProvider:138: update db: selected=false _id = 30 
NSA:QuestionsPagerAdapter:42: counter: 0 
NSA:QuizProvider:103: query db: content://doit.study.droi question/ran 280 null null 
NSA:QuizProvider:138: update db: selected=false _id = 6 
NSA:QuestionsPagerAdapter:42: counter: 0 
NSA:QuestionsPagerAdapter:42: counter: 0 
NSA:QuizProvider:138: update db: selected=false _id = 1 
NSA:QuizProvider:138: update db: selected=false _id = 14 
NSA:QuestionsPagerAdapter:42: counter: 0 
NSA:QuizProvider:138: update db: selected=false _id = 7 
NSA:QuizProvider:138: update db: selected=false _id = 27 
NSA:QuestionsPagerAdapter:42: counter: 0 
NSA:QuestionsActivity:84: load finished: 154982045 
NSA:QuestionsPagerAdapter:66: swap cursor, cnt: 104 
NSA:QuestionsPagerAdapter:42: counter: 104 
NSA:QuestionsPagerAdapter:42: counter: 104 
NSA:QuestionsPagerAdapter:42: counter: 104 
NSA:QuestionsPagerAdapter:42: counter: 104 
NSA:QuestionsPagerAdapter:35: instantiateItem, pos=0 
NSA:QuestionsPagerAdapter:25: getItem, pos=0 
NSA:QuestionsPagerAdapter:35: instantiateItem, pos=1 
NSA:QuestionsPagerAdapter:25: getItem, pos=1 
NSA:QuestionsPagerAdapter:42: counter: 104 
NSA:QuizProvider:138: update db: selected=false _id = 2 
NSA:QuizProvider:138: update db: selected=false _id = 11 
NSA:QuizProvider:138: update db: selected=false _id = 22 
NSA:QuizProvider:138: update db: selected=false _id = 9 
NSA:QuizProvider:138: update db: selected=false _id = 31 
NSA:QuizProvider:138: update db: selected=false _id = 21 
NSA:QuizProvider:138: update db: selected=false _id = 32 
NSA:QuizProvider:138: update db: selected=false _id = 13 
NSA:QuizProvider:138: update db: selected=false _id = 4 
W/FragmentManager: moveToState: Fragment state for QuestionFragment{5c25e01 #0 id=0x7f0f00e5} not updated inline; expected state 3 found 2 
NSA:QuestionsPagerAdapter:42: counter: 104 
NSA:QuestionsPagerAdapter:52: title pos: 0, questions: tags: [User Interfaces] 
NSA:QuestionsPagerAdapter:42: counter: 104 
NSA:QuestionsPagerAdapter:52: title pos: 1, questions: tags: [User Interfaces] 
NSA:QuestionsPagerAdapter:42: counter: 104 
NSA:QuizProvider:103: query db: content://doit.study.droi tag null null 
NSA:QuestionsPagerAdapter:42: counter: 104 
NSA:QuestionsPagerAdapter:42: counter: 104 
NSA:QuestionsPagerAdapter:42: counter: 104 
NSA:QuestionsPagerAdapter:42: counter: 104 
NSA:QuestionsPagerAdapter:42: counter: 104 
NSA:QuizProvider:138: update db: selected=false _id = 5 
NSA:QuizProvider:138: update db: selected=false _id = 26 
NSA:QuizProvider:103: query db: content://doit.study.droi tag null null 
NSA:QuizProvider:138: update db: selected=false _id = 19 
NSA:QuizProvider:138: update db: selected=false _id = 28 
NSA:QuizProvider:138: update db: selected=false _id = 10 
NSA:QuizProvider:138: update db: selected=false _id = 12 
NSA:QuizProvider:138: update db: selected=false _id = 15 
NSA:QuizProvider:138: update db: selected=false _id = 18 
NSA:QuizProvider:138: update db: selected=false _id = 25 
NSA:QuizProvider:138: update db: selected=false _id = 16 
NSA:QuizProvider:138: update db: selected=false _id = 17 
NSA:QuizProvider:138: update db: selected=false _id = 8 
NSA:QuizProvider:138: update db: selected=false _id = 3 
NSA:QuizProvider:138: update db: selected=false _id = 20 
NSA:QuizProvider:138: update db: selected=false _id = 29 
NSA:QuizProvider:138: update db: selected=false _id = 24 
NSA:QuizProvider:138: update db: selected=false _id = 23 
NSA:QuizProvider:138: update db: selected=false _id = 30 
NSA:QuizProvider:138: update db: selected=false _id = 6 
NSA:QuizProvider:138: update db: selected=false _id = 1 
NSA:QuizProvider:138: update db: selected=false _id = 14 
NSA:QuizProvider:138: update db: selected=false _id = 7 
NSA:QuizProvider:138: update db: selected=false _id = 27 
NSA:QuizProvider:138: update db: selected=false _id = 2 
NSA:QuizProvider:138: update db: selected=false _id = 11 
NSA:QuizProvider:138: update db: selected=false _id = 22 
NSA:QuizProvider:138: update db: selected=false _id = 9 
NSA:QuizProvider:138: update db: selected=false _id = 31 
NSA:QuizProvider:138: update db: selected=false _id = 21 
NSA:QuizProvider:138: update db: selected=false _id = 32 
NSA:QuizProvider:138: update db: selected=false _id = 13 
NSA:QuizProvider:103: query db: content://doit.study.droi question/ran 280 null null 
NSA:QuestionsActivity:84: load finished: 19434496 
NSA:QuestionsPagerAdapter:66: swap cursor, cnt: 0 
NSA:QuestionsPagerAdapter:42: counter: 0 
NSA:QuestionsPagerAdapter:42: counter: 0 

Log montre que applyBatch est pas terminé et le curseur Loader obtient partiellement les données modifiées (curseur compteur = 104, doit être de 0 ou 280).

Certaines ressources (désolé, ne peut pas ajouter plus de deux liens):

_http: //developer.android.com/guide/topics/providers/content-provider-basics.html#Batch

_http: //www.androiddesignpatterns.com/2012/10/sqlite-contentprovider-thread-safety.html

_http: //stackoverflow.com/questions/8104832/sqlite-simultaneous-reading-and-writing

_http: //www.grokkingandroid.com/better-performance-with-contentproviderope ration/

Avez-vous une idée de ce qui ne va pas?

+0

Votre code rencontre deux problèmes: la «condition de concurrence» à laquelle vous êtes confronté est causée par l'utilisation d'un nouveau thread chaque fois que vous soumettez une modification au lieu d'utiliser une file d'attente (par exemple AsyncTask avec Executor par défaut). Votre code n'appelle pas 'ContentResolver # notifyChange', donc le CursorLoader n'est pas rechargé quand un changement survient. – user1643723

+0

Merci de votre réponse. Je soumets des modifications une fois en quittant l'activité One dans la méthode onPause(). Ajouté notifyChange() (sur votre conseil). Encore ne peut pas obtenir la signification atomique pour applyBatch (opérations). Comme la sortie du journal montre que ce n'est pas atomique. –

+0

Vous devez appeler 'notifyChange' dans les méthodes de ContentProvider. Vous êtes également censé définir Uri observable sur SQLiteCursor créé en même temps.Apprendre la bonne façon de faire les choses en étudiant le code des ContentProviders existants (je recommande de regarder le code, généré par [AnnotatedSQL] (https://github.com/hamsterksu/Android-AnnotatedSQL/)). Et assurez-vous d'utiliser * AsyncTask au lieu de 'new Thread' *. – user1643723

Répondre

4

Il y a fondamentalement trois choses à considérer ici:

  • Déplacer les notifyChange() appels à votre ContentProvider. De cette façon, vous pouvez être sûr qu'ils sont appelés chaque fois que vous faites un changement. Ou en d'autres termes: vous pourriez oublier de le faire ailleurs. Le client ne devrait pas être responsable de ceci - il n'appartient pas ici.
  • Assurez-vous que votre méthode applyBatch() utilise effectivement des transactions. Ce n'est qu'alors que vous obtenez les avantages de performance souhaités et que seuls les verrous sont utilisés comme vous le souhaitez.
  • Assurez-vous que pendant que applyBatch() est en cours d'exécution, aucune notification n'est émise. Sinon, votre chargeur serait appelé trop souvent. Vous voulez vraiment éviter cela.

Vous pouvez voir mon cpsample project pour un fournisseur de contenu qui respecte ces règles.

+1

Merci, je ne peux pas encore surpasser. –

+0

Pas de problème. N'oubliez pas de revenir quand vous le pouvez :-) –