在Android开发中常遇见数据的实时刷新的场景:后台服务获取的信息,然后插入数据库中,数据自动加载到当前的UI上。或者当某个数据源发生改变时,及时在UI刷新显示。
这种场景很适合观察者模式,当被观察者发生改变时候,及时通知观察者做出响应。
SQLite+自定义的ContentProvider+CursorLoader可以实现,当数据库中某个数据源发生改变的时候,自动通知。
数据库中数据源发生改变到自动更新UI的过程:
数据库--->Contentprovider/Contentresolver监控
--->cursor的观察者-数据源发生改变
--->cursorloader重新加载到数据
--->新数据源更新在UI上
1. 配置数据库
创建BaseColumns的实现类,存放数据库中使用的常量字段。
public final class Data implements BaseColumns {
/**
* 数据库信息
*/
public static final String SQLITE_NAME="sqlitePrcactice.db";
public static final int SQLITE_VERSON=1;
/**
* 信息表,及其字段
*/
public static final String TABLE_NAME="message";
public static final String COLUMN_CONTENT="content";
public static final String COLUMN_DATE="date";
}
创建数据库,创建表:
public class DataHelper extends SQLiteOpenHelper {
public static final String CREATE_MESSAGE="create table "+
Data.TABLE_NAME+"("+
Data._ID+" integer primary key autoincrement,"+
Data.COLUMN_CONTENT+" text,"+
Data.COLUMN_DATE+" text"
+")";
public DataHelper(Context context) {
super(context, Data.SQLITE_NAME, null, Data.SQLITE_VERSON);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_MESSAGE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
}
2. 自定义ContentProvider
创建ContentProvider需要用到authority,数据库表对应的Uri:
public final class Data implements BaseColumns {
..........
/**
* ContentProvider的authorities
*/
public static final String AUTHORITY="com.xingen.sqlitepractice.db.DataContentProvider";
/**
* Scheme
*/
public static final String SCHEME="content";
/**
* ContentProvider的URI
*/
public static final Uri CONTENT_URI=Uri.parse(SCHEME+"://"+AUTHORITY);
/**
* Message表的URI
*/
public static final Uri MESSAGE_URI=Uri.withAppendedPath(CONTENT_URI,TABLE_NAME);
}
ContentProvider的自类,还需配置Uri的监听和Uri改变通知。
具体步骤:
1. 对cursor数据设置监听的url,即在contentprovider中query()调用cursor.setNotificationUri()。
2. ContentProvider的insert()、update()、delete()等方法中调用ContentResolver的notifyChange()方法。
ContentProvider子类代码如下:
public class DataContentProvider extends ContentProvider {
private static final int TABLE_DIR=1;
private static UriMatcher uriMatcher;
private DataHelper dataHelper;
static {
uriMatcher=new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(Data.AUTHORITY,Data.TABLE_NAME,TABLE_DIR);
}
@Override
public boolean onCreate() {
dataHelper=new DataHelper(getContext());
return true;
}
@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = dataHelper.getReadableDatabase();
Cursor cursor=null;
switch (uriMatcher.match(uri)){
case TABLE_DIR:
cursor = db.query(Data.TABLE_NAME, projection, selection,
selectionArgs, null, null, sortOrder);
break;
default:
break;
}
if(cursor!=null){
//添加通知对象
cursor.setNotificationUri(getContext().getContentResolver(),uri);
}
return cursor;
}
@Nullable
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)){
case TABLE_DIR:
return "vnd.android.cursor.dir/vnd." +Data.AUTHORITY
+ Data.TABLE_NAME;
default:
break;
}
return null;
}
@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase sqLiteDatabase=dataHelper.getWritableDatabase();
Uri returnUri=null;
switch (uriMatcher.match(uri)){
case TABLE_DIR:
long rowId= sqLiteDatabase.insert(Data.TABLE_NAME,null,values);
returnUri=Uri.parse("content://" + Data.AUTHORITY + "/"
+ Data.TABLE_NAME + "/" + rowId);
break;
default:
break;
}
//通知,数据源发生改变
getContext().getContentResolver().notifyChange(uri,null);
return returnUri;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase sqLiteDatabase=this.dataHelper.getWritableDatabase();
int deliteRow=0;
switch (uriMatcher.match(uri)){
case TABLE_DIR:
deliteRow=sqLiteDatabase.delete(Data.TABLE_NAME,selection,selectionArgs);
break;
default:
break;
}
//通知,数据源发生改变
getContext().getContentResolver().notifyChange(uri,null);
return deliteRow;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
SQLiteDatabase sqLiteDatabase=dataHelper.getWritableDatabase();
int updateRow=0;
switch (uriMatcher.match(uri)){
case TABLE_DIR:
updateRow=sqLiteDatabase.update(Data.TABLE_NAME,values,selection,selectionArgs);
break;
default:
break;
}
if(updateRow>0){ //通知,数据源发生改变
getContext().getContentResolver().notifyChange(uri,null);
}
return 0;
}
}
3. CursorLoader自动加载到数据
跟随Activity或者Fragment的生命周期来创建和销毁CursorLoader,LoaderManager.LoaderCallbacks的回调响应。
public class AutomaticObservedActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor> {
/**
* 加载器的标示
*/
private final int LOADER_ID=1;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
.......
//初始化loader
getSupportLoaderManager().initLoader(LOADER_ID,null,this);
}
@Override
protected void onDestroy() {
//销毁loader,释放资源
getSupportLoaderManager().destroyLoader(LOADER_ID);
super.onDestroy();
}
/**
* 初始化加载器的回调
* @param id
* @param args
* @return
*/
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
if(id==LOADER_ID){
return new CursorLoader(this, Data.MESSAGE_URI,null,null,null,Data.ORDER_BY);
}
return null;
}
/**
* 首次加载成功或者数据源发生改变时候进行回调
* @param loader
* @param cursor
*/
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
if(loader.getId()==LOADER_ID){
if(cursor!=null&&cursor.moveToFirst()){//实时刷新数据到UI上,这里,Cursor对象会自动被关闭
List<Message> list=new ArrayList<>();
do {
list.add(ValuesTransform.transformMessage(cursor));
}while (cursor.moveToNext());
this.adapter.addData(list);
}
}
}
/**
* 加载器重置的回调
* @param loader
*/
@Override
public void onLoaderReset(Loader<Cursor> loader) {
}
}
4. 使用ContentResolver来增,删,改:
自定义的ContentProvider中增,删,改的方法添加通知操作,因此使用ContentResolver。若是直接用数据库对象操作增,删,改操作,无法通知Uri。
例如,这里往数据库中新增信息:
Message message=new Message();
message.setContent(content_Edit.getText().toString().trim());
message.setDate(date_Edit.getText().toString().trim());
getContentResolver().insert(Data.MESSAGE_URI,ValuesTransform.transformContentValues(message));
运行,项目效果如下:
项目代码Github上:https://github.com/13767004362/SQLitePractice
PS : 若是觉得自定义ContentProvider太麻烦,还有更简单的方式,借助RxJava,SQLBrite框架实现。
下篇介绍,使用RxJava+SQLBrite实现数据库的观察者模式,实时刷新UI。