开发者

registerContentObserver() on raw SQLite Cursor

开发者 https://www.devze.com 2023-03-14 22:52 出处:网络
All of the examples I\'ve seen of using registerContentObserver() do so through a ContentProvider interface. But Cursor has a registerContentObserver() call, so I figured maybe the Android folks have

All of the examples I've seen of using registerContentObserver() do so through a ContentProvider interface. But Cursor has a registerContentObserver() call, so I figured maybe the Android folks have put together some deep magic that would allow for getting updates on a SQLite cursor when one of the rows from an active result set changed. Either I'm doing it wrong, or there is no such magic. Here's the code I'm working with, the expectation being that when I click on the button to update the value in row #1 I would get an onChange() callback. Any ideas?

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.开发者_如何学JAVATABLE_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    
        }
    }
}


This is actually possible.

You have to define an Uri somewhere (a constant maybe).

I would advice to use something like this:

public static final Uri URI_MY_TABLE = 
    Uri.parse("sqlite://com.example.<your-app-package>/table");

Then when you update the database data you call:

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

And from the CursorLoader you are writing do something like:

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;
}

The ForceLoadContentObserver is a public static inner class inside the Loader class, if you are using the Support Library it will be android.support.v4.content.Loader.ForceLoadContentObserver

In the same Loader make sure you forceReload if data change:

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

A good start for your CursorLoader if you do not know how to write it is this: CursorLoader usage without ContentProvider

Your CursorAdapter should now be initialized like this:

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

The ContentResolver will take care of notifying observer on the uri you set up.

This is exactly how the ContentProvider works.

If you are not using a Loader you can do the same stuff, the important modifications are:

  • Call ContentResolver.notifyChange(URI,null); when you change the data
  • Call Cursor.setNotificationUri(ContentResolver, URI); when you load the cursor

This way when you register an observer you will be notified when the underling data change.


No takers huh? Well an excellent excuse to dig in myself and figure it out. For those who might be curious after me, if you download the platform source the relevant files to look at are:

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

It looks like the mContentObservable is there to signal different parts of a program running off the cached result set in a Cursor, and the observable is a signal from the cached result set to let consumers know when the cache has been dumped/updated. It doesn't tie into the SQLite implementation to fire events when the underlying datastore is manipulated.

Not exactly surprising, but given the docs I thought maybe I was missing something.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号