16

我见过的所有使用示例registerContentObserver()都是通过ContentProvider界面来实现的。但是 Cursor 有一个调用,所以我想也许 Android 人员已经组合了一些深刻的魔法,当活动结果集中的行之一发生更改时,registerContentObserver()可以在游标上获取更新。SQLite要么我做错了,要么没有这样的魔法。这是我正在使用的代码,期望当我单击按钮更新第 1 行中的值时,我会得到一个onChange()回调。有任何想法吗?

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

2 回答 2

21

这实际上是可能的。

你必须在某处定义一个Uri(也许是一个常数)。

我建议使用这样的东西:

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

然后,当您更新您调用的数据库数据时:

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

从您正在编写的CursorLoader执行以下操作:

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是Loader类中的一个公共静态内部类,如果您使用 Support Library,它将是android.support.v4.content.Loader.ForceLoadContentObserver

如果数据发生变化,请确保在同一个Loader中强制重新加载:

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

如果您不知道如何编写 CursorLoader,这是一个好的开始:CursorLoader usage without ContentProvider

您的CursorAdapter现在应该像这样初始化:

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

ContentResolver将负责通知观察者您设置的 uri。

这正是ContentProvider的工作原理。

如果你不使用加载器,你可以做同样的事情,重要的修改是:

  • 调用ContentResolver.notifyChange(URI,null); 当您更改数据时
  • 调用Cursor.setNotificationUri(ContentResolver, URI); 当您加载光标时

这样,当您注册观察者时,您将在基础数据更改时收到通知。

于 2012-12-11T16:39:52.833 回答
15

没有接盘侠吧?好吧,这是一个很好的借口来挖掘自己并弄清楚。对于那些可能对我感兴趣的人,如果您下载平台源,则要查看的相关文件是:

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

看起来 mContentObservable 用于指示程序的不同部分在游标中运行缓存结果集,而 observable 是来自缓存结果集的信号,让消费者知道缓存何时被转储/更新。它不会绑定到 SQLite 实现以在操作底层数据存储时触发事件。

并不完全令人惊讶,但考虑到文档,我认为我可能遗漏了一些东西。

于 2011-06-24T16:37:52.747 回答