ContentProvider
简介
ContentProvider(内容提供者)
是提供跨程序数据访问的Android组件。简而言之,相当于一个数据接口,可以将程序内部的数据向其他应用程序公开,让其他应用程序可以增删查改操作。
在Android
原生的应用很多都应用了ContentProvider
例如通讯录,文件系统等。
存在的原因
- 解决应用程序的数据库在私有目录下,其他应用程序使用常规方法无法访问数据库。
- 程序内部的数据库结构复杂,即使可以访问,但是如果不了解表的构造也很难访问,所以通过
ContentProvider
封装对数据的访问,给外部程序访问数据的一个直观的接口。 - 如果直接提供其他应用程序访问数据的权限不加以控制,那么数据安全也是一个问题,所以通过
ContentProvider
即可以提供数据接口并且可以控制访问权限。
简单的使用
主要步骤:数据库提供方:继承ContentProvider
实现有关数据库的相关操作,然后再AndroidMainfest.xml
文件中注册。 数据库访问方: 通过'URI'访问对应的ContentProvider
。
PS: 一定要在AndroidMainfest.xml
文件中注册,每一个ContentProvider
对应一个authorities
,需要注意的是authorities
在整个系统必须是唯一的,否则安装会有冲突。并且要注明是android:exported="true"
才能被外部访问。
注册例子如下:
上面只是粗略的总结了一下用法,不过肯定这几行字,你们还是不太清楚具体如何使用。接下来我会一步一步的带着大家来自定义ContentProvider
以及使用它。接下来要长篇大论了,做好准备啦,千万别晕!
这里我们统一一下,把提供数据的应用程序称之为服务端,把请求数据的应用程序称之为客户端。
服务端
- 在服务器自定义一个ContentProvider提供数据访问接口。
1.自定义ContentProvider
自定义ContentPorvider
继承自ContentPorvider
,这里我们自定义一个ContactProvider
用于获取Contact
数据表的数据,Contact
表用于存储联系人信息。先贴一下完整代码:
package com.doris.provide;
import com.orhanobut.logger.Logger;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;
/**
* Created by Doris on 2017/2/24.
*/
public class ContactProvider extends ContentProvider {
public static final int CODE_CONTACT = 0X108;
private static final String TAG = ContactProvider.class.getSimpleName();
private static final String AUTHORITYS = "com.doris.contact.provider";
private static final String TABLE_NAME = "Contact";
//通过UriMatcher匹配Uri从而执行对应的操作。
private static UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
mUriMatcher.addURI(AUTHORITYS, TABLE_NAME, CODE_CONTACT);
}
@Override
public boolean onCreate() {
Logger.d(TAG, "onCreate");
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor cursor = null;
switch (mUriMatcher.match(uri)) {
case CODE_CONTACT :
cursor = DBHelper.getInstance().query(TABLE_NAME, selection, selectionArgs, sortOrder);
break;
}
return cursor;
}
@Override
public String getType(Uri uri) {
return uri.toString();
}
@Override
public Uri insert(Uri uri, ContentValues values) {
switch (mUriMatcher.match(uri)) {
case static {
mUriMatcher.addURI(AUTHORITYS, TABLE_NAME, CODE_CONTACT);
} : {
long rowId2 = DBHelper.getInstance().insert(TABLE_NAME, values);
if (rowId2 > 0) {
Uri noteUri = ContentUris.withAppendedId(uri, rowId2);
getContext().getContentResolver().notifyChange(noteUri, null);
return noteUri;
}
}
break;
}
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0;
switch (mUriMatcher.match(uri)) {
case CODE_CONTACT :
count = DBHelper.getInstance().delete(TABLE_NAME, selection, selectionArgs);
break;
}
return count;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = 0;
switch (mUriMatcher.match(uri)) {
case CODE_CONTACT :
count = DBHelper.getInstance().update(CPosterManager.TABLE_NAME, values, selection, selectionArgs);
break;
}
return count;
}
}
在上面这段代码中,我们自定义了一个ContactProvider
提供了对Contact
表的增删查改操作,而增删查改动作又是通过DBHelper
类来实现的,DBHelper
类封装了对数据库的操作。
我们来解析一下代码。以上代码的关键就是UriMatch
如其名,用来匹配Uri
来解读对应的操作CODE,从而对应到某个操作上。因为其他程序都是通过uri来访问ContactProvider
的所以需要匹配区分,访问同一个Provider
的时候分别是要请求什么数据。比如:content:\\com.doris.contact.provider\contact
,通过UriMathc
匹配则是匹配到CODE_CONTACT
.
在ContactProvider
里面只有一个CODE
就是CODE_CONTACT
,通过这个来标示操作Contact
表。
static {
//在这句代码里面就是通过addURI来添加UriMatcher的一个匹配规则。
mUriMatcher.addURI(AUTHORITYS, TABLE_NAME, CODE_CONTACT);
}
//addURI的方法如下:
/**
* Add a URI to match, and the code to return when this URI is
* matched. URI nodes may be exact match string, the token "*"
* that matches any text, or the token "#" that matches only
* numbers.
*
* Starting from API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
* this method will accept a leading slash in the path.
*
* @param authority the authority to match
* @param path the path to match. * may be used as a wild card for
* any text, and # may be used as a wild card for numbers.
* @param code the code that is returned when a URI is matched
* against the given components. Must be positive.
*/
public void addURI(String authority, String path, int code){
.......
}
//从上面的注解可以知道,addURI就是用于添加一个匹配的URi给URIMatch,第三个参数code就是当uri匹配的时候返回的CODE。然后开发人员通过CODE 来做相应的处理。
所以如果你想要ContentProvider
能够接受访问某个表的请求是就要用addURI
添加匹配规则,这样就其他程序就可以通过这个ContentProvider
来访问该表的数据了。Are you get it?
在上面的代码中的query,delete, insert,update
方法中都是利用UriMatch
来匹配第一个uri
参数,然后根据返回的Code
来匹配的对应的操作。这里当我们匹配到是CODE_CONTACT
则通过DBHelper
来操作Contact
表,从这里也可以看出来一个ContentProvider
可以对应多个表,多个数据。
-
在服务端AndroidMainfest.xml文件里面注册自定义的ContentProvider
ContentProvider
也是Android
组件之一,所以也需要在AndroidMainfest.xml
文件中声明才能使用。声明语句如下:
此处的authorities
是类似于ContactProvider
的访问链接或者是可以理解为一个指针当你在uri组合这个authorities
来使用的时候就指向了ContactProvider
。android:exported="true"
这句也是很关键的一句,它表明你同意将这个Provider
被外部程序访问,如果这个是false
则其他应用就不能访问数据,所以一定要是true
。
经过上面两步,你就可以在其他程序中访问ContactProvider
了。至于DBHelper
其实就是一个访问数据库的帮助类。
客户端
其实客户端访问ContentProvder
是通过ContentResolver
来访问的,这个类就是通过URI来跨进程连接ContentProvider
的。所以客户端要访问需要通过ContentResolver
来请求数据。客户端示例程序如下:
// 考虑到程序的拓展性,多态等特性我们先写一个抽象的类里面对ContentResolver访问数据进行了封装。
public abstract class BaseContentHelper {
protected Context mContext;
public BaseContentHelper(Context context) {
this.mContext = context.getApplicationContext();
}
protected abstract Uri getUri();
public Cursor query(String[] projection, String whereSql, String[] whereValue, String orderBy) {
ContentResolver contentResolver = mContext.getContentResolver();
return contentResolver.query(getUri(), projection, whereSql, whereValue, orderBy);
}
public Uri insert(ContentValues cv) {
ContentResolver contentResolver = mContext.getContentResolver();
return contentResolver.insert(getUri(), cv);
}
public int update(ContentValues cv, String where, String[] selectionArgs) {
ContentResolver contentResolver = mContext.getContentResolver();
return contentResolver.update(getUri(), cv, where, selectionArgs);
}
public int delete(String whereSQL, String[] whereValue) {
ContentResolver contentResolver = mContext.getContentResolver();
return contentResolver.delete(getUri(), whereSQL, whereValue);
}
/**
* clear all record
*
* @return
*/
public int delete() {
ContentResolver contentResolver = mContext.getContentResolver();
return contentResolver.delete(getUri(), null, null);
}
}
从上面的代码可以看出,增删查改都是通过ContentResolver
以及getUri()
传递相关的参数来完成的。
其中getUri()
是有子类实现用来提供,子类对应的ContentProvider
的访问Uri的。
//我们定义一个ContactManager来获取服务端的数据
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import com.doris.db.BaseContentHelper;
import com.orhanobut.logger.Logger;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
public class ContactManager extends BaseContentHelper {
//定义ContactProvider的authoritys常量用于拼接URi
public static final String AUTHORITYS = "com.doris.support.provider.ContactProvider";
public static final String URI_BASE = "content://" + AUTHORITYS + "/";
public static final String TABLE_NAME = "contact";
public static final String PERSON_NAME = "name";
public static final String PERSON_TELE = "telephone";
public static final String PERSON_DESCRIPTION = "description";
private static String TAG = ContactManager.class.getSimpleName();
private static Uri mUri = Uri.parse(URI_BASE + "contact"); //最终拼接出来的URI
private static ContactManager mInstance;
private ContactManager(Context ctx) {
super(ctx);
}
/**
* singelten
*
* @return
*/
public static synchronized ContactManager obtain(Context ctx) {
if (mInstance == null)
mInstance = new ContactManager(ctx);
return mInstance;
}
public List getAllPersonName() {
List allName = new ArrayList<>();
Cursor cursor = query(null, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
Persion vo = parserPersonVoFromCursor(cursor);
allName.add(vo.getName());
}
cursor.close();
}
return allName;
}
public List getAllPerson() {
List allList = new ArrayList();
Cursor cursor = query(null, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
Person vo = parserPersonVoFromCursor(cursor);
allList.add(vo);
}
cursor.close();
}
return allList;
}
/**
* CN:根据name获取person列表
*
* @param name
* eg:doris
* @return
*/
public List getPersonList(String name) {
List allList = new ArrayList();
Cursor cursor = query(null, PERSON_NAME + "=?", new String[]{name}, null);
if (cursor != null) {
while (cursor.moveToNext()) {
Person vo = parserPersonVoFromCursor(cursor);
allList.add(vo);
}
cursor.close();
}
return allList;
}
private Person parserPersonVoFromCursor(Cursor cursor) {
Person vo = new Person();
vo.setName(cursor.getString(cursor.getColumnIndex(PERSON_NAME)));
vo.setTelephone(cursor.getString(cursor.getColumnIndex(PERSON_TELE)));
vo.setDescription(cursor.getString(cursor.getColumnIndex(PERSON_DESCRIPTION)));
return vo;
}
/**
重写父类的getUri 返回uri供ContentResolver使用。
**/
@Override
protected Uri getUri() {
return mUri;
}
}
上面的BaseContentHelper
封装了增删查改,ContactManager
通过继承BaseContentHelper
封装了一些数据操作的接口,比如通过名字获得Person
对象,获取所有的Person列表。
其实上面就把客户端如何请求数据以及服务端如何提供ContentProvider
给讲完了,只要你开始用起来,你就会发现其实很简单,只要自定义一个ContentProvider
并且在AndroidMainfest.xml
文件里注册一下就可以被跨程序访问了。 客户端也只需结合ContentResolver
以及Uri
便可以访问数据了。 是不是炒鸡简单。一开始我也搞不懂,但是用了几次之后,也知道是怎么回事了。又一次验证了重在实践,所以还没用的快用起来吧。写这篇文章有两个目的: 1.让还不熟悉ContentProvider的你能了解如何使用。 2.给自己留下一个涉猎的足迹,毕竟好记性不如烂笔头,以后有疑惑可以翻出来再看看。
疑问点
-
ContentProvider 何时创建?
在应用一开始的时候,
ContentProvider
就由系统创建。在应用中,无需直接为它创建一个实例。 -
其他程序是如何访问到ContentProvider的?
通过
ContentResolver
结合uri来访问,当ContentResolver
访问某个uri时,系统会识别uri解析出authoritys来匹配对应ContentProvider
. 然后传递请求与参数。 注意,同一个安卓系统中,
ContentProvider
的authoritys
必须是唯一的,其实你很容易就可以知道是不是唯一的,如果你运行一个应用,运行不上的时候,就很有可能是因为authoritys
冲突了,不过首先你得看看提示信息,再做判断。其实
ContentProvider
不仅可以访问数据库中的数据,可以是调用其他程序中的某个方法然后获取返回值。示例如下:
//在ContactProvider里面重写 call方法
private static final String FUNC_GET_MESSAGE = "func_get_messge";
private static final String MESSAGE = "message";
@Override
public Bundle call(String method, String arg, Bundle extras) {
Bundle ret = null;
if (method.equals(FUNC_GET_MESSAGE)) {
String message = getMessage();
ret = new Bundle();
ret.putString(MESSAGE, message);
}
return ret;
}
客户端调用:
//在ContactManager里面添加getMessage方法
private static final String FUNC_GET_MESSAGE = "func_get_messge";
private static final String MESSAGE = "message";
public String getMessage() {
Bundle ret = mContext.getContentResolver().call(getUri(), FUNC_GET_MESSAGE, null, null);
ret.setClassLoader(getClass().getClassLoader());
String list = ret.getString(MESSAGE);
return list;
}
//通过这种方式可以调用另一个应用的某个方法来获取某些数据,不过本质上都是获取一些数据。
合理使用
ContentProvider
,暴露应该暴露的接口,敏感接口不要暴露给用户。可以通过
notifyChange()
来通知数据的改变,这个很有用的,当你通过ContentProvider
来访问某个数据时,但是又希望在数据改变的时候监听到,那么就可以通过设置resolver.registerContentObserver
来监听数据的改变.
//方法声明如下:第一个是uri是用来匹配要监听的数据的,第二个参数是一个ContentObserver对象通过继承ContentObserver,重写onChange方法来再监听到数据改变的时候做相应的操作。
/**
* Register an observer class that gets callbacks when data identified by a
* given content URI changes.
*
* @param uri The URI to watch for changes. This can be a specific row URI, or a base URI
* for a whole class of content.
* @param notifyForDescendents When false, the observer will be notified whenever a
* change occurs to the exact URI specified by uri
or to one of the
* URI's ancestors in the path hierarchy. When true, the observer will also be notified
* whenever a change occurs to the URI's descendants in the path hierarchy.
* @param observer The object that receives callbacks when changes occur.
* @see #unregisterContentObserver
*/
registerContentObserver(@NonNull Uri uri, boolean notifyForDescendents,
@NonNull ContentObserver observer) ;
实例:
private class SettingsObserver extends ContentObserver {
public SettingsObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
String option = uri.getLastPathSegment();
Slog.d(TAG, "onChange, option = " + option);
switch (option) {
...........
}
}
}
在安卓的一些自带的应用中,其实很多监听系统属性的变化的时候,都是通过ContentProvider
以及监听来做相应的处理的。所以你可以运用到你的应用中。让你的应用更加完美。
许久未更新博客,总觉得自己知道的不够高深,但是想想每一个记录都是为别人为自己积累知识,不管简单还是复杂。所以再一次码起了文字。希望大家多多指教,有不对的提出来,一起学习成长。O(∩_∩)O哈哈~。