一 ContentProvider
ContentProvider Android四大组件之一(可能是四大组件里面用的最少的一个),如果当前应用需要给其他应用提供一致的数据(一般是据文件或者表结构)访问(增、删、改、查表结构,读取修改文件内容)的时候,ContentProvider可能就是最好的方式了。而且Android系统中也多次用到了ContentProvider比如:联系人、日历备忘录等等都是通过ContentProvider的方式来提供给其他应用访问。再比如实际使用中有这么一个情况,机顶盒所有通过卫星搜索下来的节目我们可以通过ContentProvider的方式提供给其他应用访问。(ContentProvider也是跨进程通信方式的一种,有点类似于C/S结构:ContentProvider端相当于S端、访问端相当于C端)。
ContentProvider使用的时候两个关键的步骤:自定义ContentProvider类继承ContentProvider,AndroidManifest.xml文件中 provider标签。
- 自定义ContentProvider类,实现ContentProvider增删改查的逻辑(一般是配合文件或者数据库操作),自定义CotentProvider的时候有几个函数要特别留意:
/**
* ContentProvider创建后就会被调用(ContentProvider在其它应用第一次访问它时就会被创建)
*/
public boolean onCreate();
/**
* 重写ContentProvider的时候,此方法是期望被实现的
* 根据给定的Uri返回一个MIME类型的数据
* 如果是单条数据,MIME类型应该以vnd.android.cursor.item开头 比如vnd.android.cursor.item/single
* 如果是多条数据,MIME类型的数据应该以vnd.android.cursor.dir开头 比如vnd.android.cursor.item/multi
*
* @param uri uri
* @return MIME
*/
public String getType(@NonNull Uri uri);
/**
* 如果ContentProvider提供的是文件,此方法是期望被实现的
* 为给定内容URI返回一个MIME类型String数组
*
* @param uri uri
* @param mimeTypeFilter MIME类型的过滤参数
* @return MIME类型数组 如果我们提供的ContentProvider根据给定的URI判断他 对.jpg、.png、.gif格式的文件感兴趣,mimeTypeFilter = “image/*”(任意格式的图片)那么返回 { "image/jpeg", "image/png",
* "image/gif"}数组
*/
public String[] getStreamTypes(@NonNull Uri uri, @NonNull String mimeTypeFilter);
/**
* 用于供外部应用操作ContentProvider提供的文件
*/
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException;
/**
* 用于供外部应用查询数据
*
* @param uri uri
* @param projection 要查询的列列表,如果是null查询所有的列
* @param selection 条件
* @param selectionArgs 条件里面需要的值
* @param sortOrder 排序
* @return 带返回结果的Cursor 举个例子 projection = ["username","password"] selection = "username=?" selectionArgs = ["tuacy"] sortOrder =
* "username ASC" 表示查询指定数据库中的username,password 列,结果按照username升序排序,并且满足username=tuacy的条件
*/
public Cursor query(@NonNull Uri uri,
@Nullable String[] projection,
@Nullable String selection,
@Nullable String[] selectionArgs,
@Nullable String sortOrder);
/**
* 用于供外部应用往ContentProvider添加数据
*
* @return 成功的时候返回uri(在原始uri中在appendId(id是插入成功的行号))、失败返回null
*/
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values);
/**
* 用于供外部应用删除数据
*
* @param uri uri
* @param selection 条件
* @param selectionArgs 条件里面需要的值
* @return 删除了多少列
*/
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs);
/**
* 用于供外部应用更新数据
*
* @param uri uri
* @param values 键值对,和数据库中的ContentValues是一样的(key:列名称,value:列对应值)
* @param selection 条件
* @param selectionArgs 条件里面需要的值
* @return 更新了多少列
*/
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs);
- ContentProvider AndroidManifest.xml文件声明的时候几个最常用标签的解释:
- android:authorities:ContentProvider可以处理的authroities,如果有多个authroities以分号(;)隔开
- android:name:我们重写的ContentProvider对应的包名。
- android:exported:ContentProvider是否可以被其他的程序访问(我们经常设为true,作为内容提供者肯定是可以让其他程序访问的)。
- android:permission:ContentProvider的操作权限(读+写权限),如果我们定义了permission权限,别的应用访问的时候也是要添加湘雅的权限才能访问到ContentProvider。
- android:readPermission:ContentProvider的读操作权限(查询)。
- android:writePermission:ContentProvider的写操作权限(增、删、改)。
android:permission包括了android:readPermission、android:writePermission。
provider一个简单的AndroidManifest.xml文件标签声明如下:
<provider android:authorities="com.tuacy.contentproviderserver" android:name="com.tuacy.contentproviderserver.provider.AppContentProvider" android:readPermission="com.tuacy.contentproviderserver.READ_CONTENTPROVIDER" android:writePermission="com.tuacy.contentproviderserver.WRITE_CONTENTPROVIDER"
android:exported="true" />
二 ContentResolver
ContentResolver是在客户端(C)端使用。使用ContentResolver来操作ContentProvider中的数据,当外部应用需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,可以使用ContentResolver类来完成(要获取ContentResolver对象,可以使用Activity提供的getContentResolver()方法)。 ContentResolver类提供了与ContentProvider类非常类似的方法正好和CotentProvider里面的方法一一对应(帮我们把所有跨进程通信的方法都封装起来了)。可以简单的认为ContentResolver能帮我们正确找到对应的CotentProvider然后调用ContentProvder里面的方法完成跨进程间的通信。
三 ContentProvider Uri
ContentProvider Uri代表了ContentProvider要操作的数据。ContentProvider 的Uri由三个部分组成:scheme、authority、path(有些地方可能path后面还包括一个id,我们把id归根到path中去)。如下图所示。
里面可能就path稍微复杂一点了,我们举例来说明path。ContentProvider一般用来分享两种数据一个是文件数据、一个是表数据(配合数据库使用)。
当分享的是文件数据的时候:path里面一般放置的就是文件路径可以使用Uri.getPath()来获取path:一个完整的Uri如下 content://com.tuacy.contentprovider//data/com.tuacy.contentprovider/1.txt 则path = /data/com.tuacy.contentprovider/1.txt(文件路径)。
当分享的是表结构:ContentProvider使用表结构存储的时候一般会配合数据库来使用。这个时候path的用处就非常大了,可以在path里面标记我们要操作的是哪个表,还可以带很多操作的参数。
比如:content://com.tuacy.contentprovider/user 我们可以用user来标记咱们要操作User对应的数据库(操作参数不在uri里面给出,在ContentResolver对应的方法中给出)。
在比如:content://com.tuacy.contentprovider/user/10
表示我们要操作user表示的数据库,并且带了一个整数10过来了(ContentUris.parseId(uri)得到10),那这个时候咱们就可以灵活处理了,比如我们调用的是delete函数,我们就可以规定是删除表中id=10的数据了。
在比如一个content://com.tuacy.contentprovider/user/tuacy/123 表示我们要操作user表示的数据库,并且带了tuacy(uri.getPathSegments().get(1)获取)和123(uri.getPathSegments().get(2)获取)过来了,比如我们调用的是delete函数,我们就可以规定是删除表中name=tuacy password=123的数据了。
总结:ContentProvider Uri先会指明我们要操作的是那个ContentProvider,在指明ContentProvider的基础上带上ContentProvider要操作的数据。我们只要自己灵活使用就可以。
四 UriMatcher
UriMatcher用于匹配Uri的帮助类,一般ConentProvider操作表数据的时候用到,我们给每一种匹配都设置相对于的falg code 标记,这样通过code就能知道要对那个数据库做操作,并且使用的时候通过不同的code对Uri的path参数做不同的解析。
static UriMatcher buildUriMatcher() {
final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
/**
* 表示要操作user对应的数据库,这个时候path里面不带参数(Uri不带参数,参数直接在ContentResolver调用的方法中给出)
*/
matcher.addURI("com.tuacy.contentproviderserver", "user", 1);
/**
* (#匹配的是整数)匹配user标记并且带一个整数参数的Uri(content://com.tuacy.contentproviderserver/user/10)
* 通过ContentUris.parseId(uri)来获取这一参数
* 有了这个参数我们就可以做很多限制了,比如查询的时候查询数据库中某个字段为10的一条数据等等,完全有自己定
*/
matcher.addURI("com.tuacy.contentproviderserver", "user/#", 2);
/**
* (*匹配任何文本)匹配user标记并且带文本参数的Uri(content://com.tuacy.contentproviderserver/user/tuacy/123)
* 通过uri.getPathSegments().get(1)来获取tuacy,同uri.getPathSegments().get(2)来获取123
* 也是对ContentProvider的操作做一些限制,比如查询的时候查询数据库中某个字段为tuacy并且某个字段为123的一条数据等等,完全有自己定
*/
matcher.addURI("com.tuacy.contentproviderserver", "user/*", 3);
/**
* 表示要操作department对应的数据库
*/
matcher.addURI("com.tuacy.contentproviderserver", "department", 4);
return matcher;
}
这样通过code对应的1、2、3、4我们知道对那个数据库做操作,而且通过code知道参数的规范。
五 ContentUris
ContentUris是一个帮助类,用于解析或者添加Uri中的id(path中的id)。里面就三个函数非常好理解。
/**
* 从Uri中解析id
*/
public static long parseId(Uri contentUri) {
String last = contentUri.getLastPathSegment();
return last == null ? -1 : Long.parseLong(last);
}
/**
* 添加id到Uri中
*/
public static Uri.Builder appendId(Uri.Builder builder, long id) {
return builder.appendEncodedPath(String.valueOf(id));
}
/**
* 添加id到Uri中
*/
public static Uri withAppendedId(Uri contentUri, long id) {
return appendId(contentUri.buildUpon(), id).build();
}
六 监听ContnentProvder的变化(配合Loader使用)
当ContentProvider的的数据有变化的时候为了让其他的程序能收到变化做相应的数据更新,我们通用的做法是在ConentProvider里面的insert、update、delete函数调用getContext().getContentResolver().notifyChange(uri, null)。同时还得在ContentProvider里面的query函数调用cursor.setNotificationUri(getContext().getContentResolver(), uri)。
七 ContnentProvder DEMO实例
最后写了一个非常简单的ContentProvider例子ContentProviderDEMO传送门 请先跑contentproviderserver在跑contentproviderclient