关于 ContentResolver
和 ContentObserver
ContentObserver
ContentObserver
内容观察者,观察 Uri
引起 ContentProvider
中的数据变化,并且去通知,在 onChange()
方法中,回调监听。
它的简单使用:
- 创建一个类,继承与 ContentObserver, 实现它的
onChange()
方法:
private class MediaContentObserver extends ContentObserver {
private Uri contentUri;
/**
* Creates a content observer.
*
* @param handler The handler to run {@link #onChange} on, or null if none.
*/
public MediaContentObserver(Handler handler, Uri contentUri) {
super(handler);
this.contentUri = contentUri;
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
handlerMediaContentChange(contentUri);
}
}
- 然后创建一个实例,并注册:
ContentObserver externalObserver = new MediaContentObserver(handler, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
Application.getContext().getContentResolver().registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
false, externalObserver);
注意: 需要一个
handler
, 因为ContentObserver
内部使用了一个实现 Runnable 接口 的内部类NotificationRunnable
, 需要通过 handler 去做比如 UI 变化。
/**
* uri 是需要监听的 uri;
* 第二个参数: false 表示要精确匹配, 即只匹配该URI, 为 true 表示可以同时匹 配其派生 的 URI (例如:content://com.qin.cb/student 精确匹配;content:// com.qin.cb.student/# 派生, 为true 时,才会匹配到)
*/
registerContentObserver(Uri uri, boolean notifyForDescendants, ContentObserver observer)
-
注册后,在需要销毁的地方记得及时销毁 :
getContentResolver().unregisterContentObserver(externalObserver);
它与 ContentProvider
的使用
-
注册
ContentObserver
getContentResolver().registerContentObserver (uri);
-
当该
URI
的ContentProvider
数据发生变化时, 通知外界。public class TestContentProvider extends ContentProvider { ... public Uri insert(Uri uri, ContentValues values) { db.insert("user", "userId", values); //通知访问者 getContext().getContentResolver.notifyChange(uri, null); } }
-
unregister
同上。
URI
上述 URI (Uniform Resource Identifier)统一资源标识符 在 ContentProvider
中有固定的格式:
content://Authority/Path/Id
前面 的 content 是不可改变的,固定的部分;
Authority: 表示授权信息,用于区别不同的 ContentProvider
Path: 表名, 用以区分 ContentProvider 中不同的数据表;
Id: Id 号, 用以区别表中的不同数据
ContentProvider
是进程间进行数据交互 的工具, 可实现跨进程通信.
看到一个实现了 ContentProvider 的类主要代码:
public class TestContentProvider extends ContentProvider {
...
public static Uri createBaseUri(...) {
...
return ...;
}
@Nullable
@Override
public Bundle call(String method, String arg, Bundle extras) {
...
}
@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
}
主要操作 是为了数据的操作提供了接口回调的方法。
ContentProvider
并不会直接与外面的进程进行数据交互,而是通过 ContentResolver
.
ContentResolver
它可以帮助我们去查询所有有关SD卡目录下的一些文件信息,例如 媒体文件, 通话记录,照片等。
ContentResolver
提供了与 ContentProvider
相同名字的方法,用于数据的增,删查,改。
public final @Nullable Cursor query(@RequiresPermission.Read @NonNull Uri uri,
@Nullable String[] projection, @Nullable String selection,
@Nullable String[] selectionArgs, @Nullable String sortOrder) {
return query(uri, projection, selection, selectionArgs, sortOrder, null);
}
//其他几个方法类似
它的参数含义:
Uri uri
: 用于检索的 URI;String[] projection
: 查询后用来返回 的列的列表。 当值为null
时,会返回所有的列,效率会比较低;@Nullable String selection
: 用来声明要返回那些行的过滤器,其格式为 SQL WHERE 子句。 当为 null 时,会返回所有行。-
@Nullable String[] selectionArgs: 与第三个参数
selection
可配合使用。可以在selection
中包含 "?", 它将按照在selection
中显示的顺序替换 为selectionArgs
中的值(字符串), 例如:CallLog.Calls.DATE + ">=? and " + CallLog.Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE, new String[]{ "" + getTimeInMillisSomeDaysAgo(30) }
意思是,查询 CallLog.Call.DATE >= 30天,并且 TYPE = MISSED_TYPE 的通话记录.
@Nullable String sortOrder
: 行的排列依据,按照时间降序排列等, 当为 null 时 将使用默认排序顺序。
它的使用更加的常见,例如:
//获取到媒体库中的照片文件,该文件满足,时间降序的第一个文件,且只包含两列数据
cursor = getContext().getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[] {
MediaStore.Images.ImageColumns.DATA,
MediaStore.Images.ImageColumns.DATE_TAKEN
}, null, null,
MediaStore.Images.ImageColumns.DATE_ADDED + " desc limit 1");
// 去获取到系统所有的通话记录,并且只包含每个 callLog 的几列数据:
//CallLog.Calls.NUMBER, CallLog.Calls.DATE, CallLog.Calls.CACHED_NAME, CallLog.Calls.TYPE
cursor = getContext().getContentResolver().query(CallLog.Calls.CONTENT_URI, new String[]{
CallLog.Calls.NUMBER,
CallLog.Calls.DATE,
CallLog.Calls.CACHED_NAME,
CallLog.Calls.TYPE }, null, null, CallLog.Calls.DEFAULT_SORT_ORDER);
getContext().getContentResolver().delete(CallLog.Calls.CONTENT_URI, whereCause.toString(), null);
CallLog.Calls.CONTENT_URI 对应的是手机系统的 通话记录Uri 部分
删除符合条件的 whereCause 的通话记录。
有关 ContentProvider
的 call()
方法
这是一个 未公开的函数.
当从 ContentProvider
中获取疏浚时,一般都是通过调用它的 query()
来获得,而这个函数将数据放在 cursor 中来返回给调用者(上面的用法即是这样的用法)。
而上述过程,ContentProvider
传给第三方应用程序的数据,是通过匿名共享内存来传输的。当传输的数据量大时, 使用匿名共享内存来传输数据是有很大好处的,可以减少数据的拷贝,提高传输效率.
但是 当传输的数据比较少时,使用匿名共享内存来作为媒介就有点大材小用了,系统创建匿名共享内存也是有开销的。所以,ContentProvider
提供了 call()
函数,让开发者来获取一些自定义数据,这些数据一般都比较小,例如传输一个整数。 这样就可以达到利用好很小的代价达到跨进程传输数据的目的.
个人对 ContentProvider
的看法
所以, 当有部分很小的数据需要在两个进程间进行传递时,也利用 ContentProvider
进行操作。
假设进程 A, 进程B ,都需要访问一个数据 TestData, 那么我们可以利用 ContentProvider
中利用 call()
函数, 利用 Bundle
去传值。
例如:
// 在一个 ContentProvider 中
public static int getTestData(){
Bundle bundle = ....call(uri, METHOD_GET_TEST_DATA, null, null);
return null == bundle ? 0 : bundle.getInt(EXTRA_KEY_TEST_DATA, 0);
}
public static void setTestData(int test) {
Bundle bundle = new Bundle();
bundle.putInt(EXTRA_KEY_TEST_DATA, test);
....call(uri, METHOD_SET_TEST_DATA, null , bundle);
}
//复写 的 call() 方法
@Nullable
@Override
public Bundle call(String method, String arg, Bundle extras) {
Bundle bundle = new Bundle();
switch(method) {
case METHOD_GET_TEST_DATA:
bundle.putInt(EXTRA_KEY_TEST_DATA, PreferenceHelper.getInt(PREF_KEY_TEST_DATA, 0)(此处为 value));
break;
case METHOD_SET_TEST_DATA:
PreferenceHelper.putInt(PREF_KEY_TEST_DATA, extras.getInt(EXTRA_KEY_TEST_DATA));
break;
case ...
default:
break;
}
...
return bundle;
}
上面介绍了一些 对 ContentObserver
, ContentProvider
, ContentResolver
这三者的简单使用。
如有错误的地方,请指正,谢谢。
参考链接: