2011-06-24 87 views
16

所有我见过的使用registerContentObserver()的例子都是通过ContentProvider接口来实现的。但Cursor有一个registerContentObserver()的调用,所以我想也许Android的人们已经把一些深奥的魔力放在一起,当一个活动的结果集中的某一行改变时,可以允许更新一个SQLite游标。要么我做错了,要么没有这样的魔法。这里是我正在使用的代码,期望当我点击按钮更新第1行的值时,我会得到一个onChange()回调。有任何想法吗?原始SQLite光标上的registerContentObserver()光标

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  
     } 
    } 
} 
+0

嘿,接受的答案是错误的,我知道这是你自己的答案,但它不是正确的答案。 –

回答

15

没有接受者吧?这是一个很好的理由挖掘自己,并找出它。对于那些可能对我很好奇的人,如果你下载平台源码,可以看看相关文件:

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

看起来mContentObservable在那里表示运行在Cursor中的缓存结果集的程序的不同部分,而observable是从缓存的结果集到让消费者知道缓存何时被转储/更新。它不绑定到SQLite实现来触发底层数据存储操作时的事件。

不完全令人惊讶,但鉴于文档,我想也许我错过了一些东西。

+0

我不明白的是什么让ContentProvider变得特别?如果ContentProvider由SQLiteDatabase支持,它的registerContentObserver()如何做正确的事情? – jfritz42

+1

它通过内容解析器挂钩。当提供者在uri的解析器上调用notifyChange时,解析器负责回调所有观察者。 – mikerowehl

+0

@ jfritz42一个'ContentProvider'负责读写。所以,即使它是由SQLite数据库支持的,它也可以在保持写入操作之后调用任何监听器。 – Felix

20

这实际上是可能的。

你必须定义一个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加载程序类中的一个公共静态内部类,如果您使用支持库,它将是android.support.v4.content.Loader。ForceLoadContentObserver

同样装载机确保您forceReload如果数据变化:

@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);当您下载游标时

这样,当您注册观察者时,下层数据更改时会通知您。

+1

它工作得很好!感谢Daniele – Shatazone

+0

令人惊叹的解决方案Daniele!我可以在iOS上做同样的事吗? –

+0

我真的怀疑它Geet Choubey,Loaders和ContentResolver是Android Framework的一部分:-) –