欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ContentProvider , ContentResolver, ContentObserver 相关

程序员文章站 2022-03-10 14:48:56
...

关于 ContentResolverContentObserver

ContentObserver

ContentObserver 内容观察者,观察 Uri 引起 ContentProvider 中的数据变化,并且去通知,在 onChange()方法中,回调监听。

它的简单使用:

  1. 创建一个类,继承与 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);
   }
}
  1. 然后创建一个实例,并注册:
    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)

  1. 注册后,在需要销毁的地方记得及时销毁 :

    getContentResolver().unregisterContentObserver(externalObserver);
    

它与 ContentProvider 的使用

  1. 注册 ContentObserver

    getContentResolver().registerContentObserver (uri);
    
  2. 当该 URIContentProvider 数据发生变化时, 通知外界。

    public class TestContentProvider extends ContentProvider {
        
        ...
        public Uri insert(Uri uri, ContentValues values) {
            db.insert("user", "userId", values);
            
            //通知访问者
            getContext().getContentResolver.notifyChange(uri, null);
        }
    }
    
  3. 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 的通话记录。


有关 ContentProvidercall() 方法

这是一个 未公开的函数.

当从 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 这三者的简单使用。

如有错误的地方,请指正,谢谢。

参考链接: