registerContentObserver() на необработанном курсоре SQLite

Все примеры использования registerContentObserver(), которые я видел, делают это через интерфейс ContentProvider. Но у Cursor есть вызов registerContentObserver(), так что я подумал, что, может быть, ребята из Android собрали какую-то глубокую магию, которая позволит получать обновления для курсора 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    
        }
    }
}

person mikerowehl    schedule 24.06.2011    source источник
comment
Эй, чувак, принятый ответ неверен, я знаю, что это твой собственный ответ, но он не правильный.   -  person Daniele Segato    schedule 28.01.2017


Ответы (2)


Это действительно возможно.

Вы должны где-то определить 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. Если вы используете библиотеку поддержки, это будет android.support.v4.content.Loader. ForceLoadContentObserver

В том же Loader убедитесь, что вы forceReload при изменении данных:

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

Хорошее начало для вашего CursorLoader, если вы не знаете, как его написать, это: Использование CursorLoader без ContentProvider

Теперь ваш CursorAdapter должен быть инициализирован следующим образом:

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

ContentResolver позаботится об уведомлении наблюдателя о настроенном вами URI.

Именно так работает ContentProvider.

Если вы не используете Loader, вы можете сделать то же самое, важные изменения:

  • Вызовите ContentResolver.notifyChange(URI,null); при изменении данных
  • Вызовите Cursor.setNotificationUri(ContentResolver, URI); при загрузке курсора

Таким образом, когда вы регистрируете наблюдателя, вы будете уведомлены об изменении подчиненных данных.

person Daniele Segato    schedule 11.12.2012
comment
Удивительное решение Даниэле!! Могу ли я сделать то же самое на iOS? - person Geet Choubey; 27.01.2017
comment
Я очень сомневаюсь, что Geet Choubey, Loaders и ContentResolver являются частью Android Framework :-) - person Daniele Segato; 28.01.2017
comment
есть ли какая-либо последняя реализация для этого, потому что на это был дан ответ 4 года назад. Но эта реализация потрясающая. Есть ли какие-либо последние разработки в этой области? - person M.Yogeshwaran; 16.03.2017
comment
Я думаю, что вы все еще можете использовать то же самое сегодня. Я больше не использую загрузчики, в любом случае я переключился на RxJava и другие шаблоны для обработки подобных вещей. - person Daniele Segato; 18.04.2017

Нет желающих, да? Что ж, отличный повод покопаться в себе и разобраться. Для тех, кому может быть любопытно после меня, если вы загрузите исходный код платформы, соответствующие файлы для просмотра:

рамки/база/ядро/java/android/база данных/sqlite/SQLiteCursor.java рамки/база/ядро/java/android/база данных/AbstractCursor.java

Похоже, что mContentObservable предназначен для того, чтобы сигнализировать о различных частях программы, работающих с кэшированным набором результатов в Cursor, а observable — это сигнал из кэшированного набора результатов, чтобы сообщить потребителям, когда кеш был сброшен/обновлен. Он не связан с реализацией SQLite для запуска событий при манипулировании базовым хранилищем данных.

Не совсем удивительно, но, учитывая документы, я подумал, что, возможно, я что-то упускаю.

person mikerowehl    schedule 24.06.2011
comment
Чего я не понимаю, так это того, что делает ContentProvider особенным? Если ContentProvider поддерживается SQLiteDatabase, как его registerContentObserver() работает правильно? - person jfritz42; 27.10.2011
comment
Он подключается через преобразователь контента. Когда провайдер вызывает notifyChange для распознавателя для uri, распознаватель позаботится об обратном вызове всех наблюдателей. - person mikerowehl; 27.10.2011
comment
@ jfritz42 a ContentProvider отвечает как за чтение, так и за запись. Таким образом, даже если он поддерживается базой данных SQLite, он может вызывать любые слушатели после сохранения операций записи. - person Felix; 16.09.2013
comment
Что особенного в контент-провайдере: 1) это единственный способ предоставить данные вашего приложения (даже файлы) контролируемым образом внешним приложениям. 2) это синглтон, управляемый Android таким образом, что вы просто не можете сделать это самостоятельно, например, если у вас есть несколько процессов в вашем приложении (используйте адаптер синхронизации), CP позволит вам обеспечить единый доступ к вашей базе данных используется. База данных Sqlite представляет собой файл, два помощника не могут использовать его одновременно (если только вы не включите ведение журнала с опережением записи) - person Daniele Segato; 21.11.2013