registerContentObserver () sul Cursore SQLite non elaborato

Tutti gli esempi che ho visto di utilizzare registerContentObserver() fanno attraverso un’interfaccia ContentProvider . Ma Cursor ha una chiamata registerContentObserver() , quindi ho pensato che forse gli utenti di Android hanno messo insieme qualche magia profonda che permettesse di ottenere aggiornamenti su un cursore SQLite quando una delle righe da un set di risultati attivo è cambiata. O sto sbagliando, o non c’è questa magia. Ecco il codice con cui sto lavorando, l’aspettativa è che quando faccio clic sul pulsante per aggiornare il valore nella riga n. 1 onChange() un callback onChange() . Qualche idea?

 public class MainActivity extends Activity { private static final String DATABASE_NAME = "test.db"; public static final String TAG = "dbtest"; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button make = (Button)findViewById(R.id.btn_make_record); make.setOnClickListener(new OnClickListener() { public void onClick(View v) { MyOpenHelper oh = new MyOpenHelper(v.getContext()); SQLiteDatabase wdb = oh.getWritableDatabase(); ContentValues cv = new ContentValues(); cv.put("value", String.valueOf(System.currentTimeMillis())); if (wdb.insert(MyOpenHelper.TABLE_NAME, null, cv) == -1) { Log.d(TAG, "Unable to insert row"); } else { Log.d(TAG, "Inserted row "); } wdb.close(); } }); Button update = (Button)findViewById(R.id.btn_update_record); update.setOnClickListener(new OnClickListener() { public void onClick(View v) { MyOpenHelper oh = new MyOpenHelper(v.getContext()); SQLiteDatabase wdb = oh.getWritableDatabase(); ContentValues cv = new ContentValues(); cv.put("value", String.valueOf(System.currentTimeMillis())); int count = wdb.update(MyOpenHelper.TABLE_NAME, cv, "_id = ?", new String[] {"1"}); Log.d(TAG, "Updated " + count + " row(s)"); wdb.close(); } }); MyOpenHelper oh = new MyOpenHelper(this); SQLiteDatabase rdb = oh.getReadableDatabase(); Cursor c = rdb.query(MyOpenHelper.TABLE_NAME, null, "_id = ?", new String[] {"1"}, null, null, null); startManagingCursor(c); contentObserver = new MyContentObserver(new Handler()); c.registerContentObserver(contentObserver); } private class MyContentObserver extends ContentObserver { MyContentObserver(Handler handler) { super(handler); } public boolean deliverSelfNotifications() { return true; } public void onChange(boolean selfChange) { super.onChange(selfChange); Log.d(TAG, "Saw a change in row # 1"); } } MyContentObserver contentObserver; public class MyOpenHelper extends SQLiteOpenHelper { private static final int DATABASE_VERSION = 1; private static final String TABLE_NAME = "test"; private static final String TABLE_CREATE = "CREATE TABLE " + TABLE_NAME + " (" + "_id INTEGER PRIMARY KEY AUTOINCREMENT," + "value TEXT);"; MyOpenHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(TABLE_CREATE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // TODO Auto-generated method stub } } } 

Questo è effettivamente ansible.

Devi definire un Uri da qualche parte (una costante forse).

Consiglierei di usare qualcosa del genere:

 public static final Uri URI_MY_TABLE = Uri.parse("sqlite://com.example./table"); 

Quindi, quando aggiorni i dati del database che chiami:

 context.getContentResolver().notifyChange(Constants.URI_MY_TABLE, null); 

E dal CursorLoader che stai scrivendo fai qualcosa come:

 final ForceLoadContentObserver observer; public MyCursorLoader(final Context context, ...) { super(context); // ... this.observer = new ForceLoadContentObserver(); } @Override public Cursor loadInBackground() { SQLiteDatabase db = this.dbHelper.getReadableDatabase(); final Cursor c = queryDatabase(db); if (c != null) { // Ensure the cursor window is filled c.getCount(); // this is to force a reload when the content change c.registerContentObserver(this.observer); // this make sure this loader will be notified when // a notifyChange is called on the URI_MY_TABLE c.setNotificationUri(getContext().getContentResolver(), Constants.URI_MY_TABLE); } return c; } 

ForceLoadContentObserver è una class interna statica pubblica all’interno della class Loader , se si utilizza la libreria di supporto sarà android.support.v4.content.Loader.ForceLoadContentObserver

Nello stesso caricatore assicurati di forzare il ricaricamento se i dati cambiano:

 @Override protected void onStartLoading() { if (this.cursor != null) { deliverResult(this.cursor); } // this takeContentChanged() is important! if (takeContentChanged() || this.cursor == null) { forceLoad(); } } 

Un buon inizio per CursorLoader se non sai come scriverlo è questo: Uso di CursorLoader senza ContentProvider

Il tuo CursorAdapter dovrebbe ora essere inizializzato in questo modo:

 public MyCursorAdapter(Context context, Cursor c) { super(context, c, 0); } 

ContentResolver si prenderà cura di notificare l’osservatore sullo uri che hai impostato.

Questo è esattamente come funziona ContentProvider .

Se non stai usando un Loader puoi fare lo stesso, le modifiche importanti sono:

  • Chiama ContentResolver.notifyChange (URI, null); quando cambi i dati
  • Chiama Cursor.setNotificationUri (ContentResolver, URI); quando carichi il cursore

In questo modo quando registri un osservatore verrai avvisato quando i dati sottostanti cambiano.

Nessun acquirente eh? Beh, un’ottima scusa per scavare in me stesso e capirlo. Per coloro che potrebbero essere curiosi dopo di me, se scarichi la fonte della piattaforma, i file rilevanti da esaminare sono:

framework / base / core / java / android / database / sqlite / SQLiteCursor.java frameworks / base / core / java / android / database / AbstractCursor.java

Sembra che mContentObservable sia lì per segnalare parti diverse di un programma che si trova fuori dal set di risultati memorizzato nella cache in un cursore, e l’osservabile è un segnale dal set di risultati memorizzato nella cache per informare gli utenti quando la cache è stata scaricata / aggiornata. Non si collega all’implementazione SQLite per generare eventi quando viene manipolato il datastore sottostante.

Non esattamente sorprendente, ma dato i documenti ho pensato che forse mi mancava qualcosa.