Android ContentProvider介绍

一、定义
ContentProvider即内容提供者,Android四大组件之一。一般为存储和获取数据提供统一的接口,可以在不同的应用程序之间共享数据,就是说可以通过ContentProvider把应用中的数据共享给其他应用访问,其他应用可以通过ContentProvider对你应用中的数据进行增删改查。就像我们手机的通话记录,当要查看某个通话时间,就需要调用通话记录中的数据,此时就该用到ContentProvider数据共享。

二、应用场景
1.在不同的应用程序之间共享数据,就可以考虑用ContentProvider来实现。
2.访问Android系统里的许多数据也是使用ContentProvider来访问,这些是系统提供的,比如供开发者调用的(视频,音频,图片,通讯录、短信等)。

三、ContentProvider相关概念介绍
1.ContentProvider类
ContentProvider类可以理解为一个Android应用对外开放的接口,只要是符合它所定义的Uri格式的请求,均可以正常访问执行操作。如果要对外部应用提供访问数据,就可以自定义自己的ContentProvider继承ContentProvider类并覆盖相关的方法就可以实现对其它应用提供访问数据。后面章节将详细介绍自定义ContentProvider。
2.URI
即统一资源标示符(Uniform Resource Identifier),唯一标识 ContentProvider & 其中的数据。
主要包含了两部分信息:
1)需要操作的ContentProvider 。
2)对ContentProvider中的什么数据进行操作。
一个Uri由以下几部分组成:

image

image.gif
​​
ContentProvider(内容提供者)的scheme已经由Android所规定, scheme为:content://。
授权信息(或叫Authority)用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。
表名(path)可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下:
要操作User表中id为10的记录,可以构建这样的路径:/User/10
要操作User表中id为10的记录的name字段, User/10/name
要操作User表中的所有记录,可以构建这样的路径:/User
要操作xxx表中的记录,可以构建这样的路径:/xxx
当然要操作的数据不一定来自数据库,也可以是文件、xml或网络等其他存储方式,如下:
要操作xml文件中User节点下的name节点,可以构建这样的路径:/User/name
如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,如下:
例如:
// 设置URI
Uri uri = Uri.parse("content://com.carson.provider/User/1")
// 上述URI指向的资源是:名为 com.hbq.providerContentProvider 中表名 为User 中的 id为1的数据
// 特别注意:URI模式存在匹配通配符* #
// :匹配任意长度的任何有效字符的字符串
// 以下的URI 表示 匹配provider的任何内容
content://com.example.app.provider/

// #:匹配任意长度的数字字符的字符串
// 以下的URI 表示 匹配provider中的table表的所有行
content://com.example.app.provider/table/#
3.MIME数据类型
多功能Internet 邮件扩充服务(Multipurpose Internet Mail Extensions),它是一种多用途网际邮件扩充协议,在1992年最早应用于电子邮件系统,但后来也应用到浏览器。MIME类型就是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。作用指定某个扩展名的文件用某种应用程序来打开,有类型和子类型两部分组成,比如:application/pdf。
ContentProvider有两种形式:
// 形式1:单条记录
vnd.android.cursor.item/自定义
// 形式2:多条记录(集合)
vnd.android.cursor.dir/自定义
4.ContentResolver
统一管理不同 ContentProvider间的操作,通过 URI 即可操作不同的ContentProvider中的数据,外部进程通过 ContentResolver类与ContentProvider类进行交互。有些人可能会疑惑,为什么我们不直接访问ContentProvider,而是又在上面加了一层ContentResolver来进行对其的操作,这样岂不是更复杂了吗?其实不然,大家要知道一台手机中可不是只有一个ContentProvider内容,它可能安装了很多含有ContentProvider的应用,比如联系人应用,日历应用,字典应用等等。有如此多的ContentProvider,如果你开发一款应用要使用其中多个,而让你去了解每个ContentProvider的不同实现,岂不是要头都大了。所以Android为我们提供了ContentResolver来统一管理与不同ContentProvider间的操作。 所以ContentProvider很多对外可以访问的方法,在ContentResolver中均有同名的方法,是一一对应的,如图:
image

image.gif
​​
5.ContentUris类
辅助ContentProvider的工具类,方便操作操作 URI,提供的核心方法有两个:withAppendedId() &parseId()。用法如下:
// withAppendedId()作用:向URI追加一个id
Uri uri = Uri.parse("content://cn.scu.myprovider/user")
Uri resultUri = ContentUris.withAppendedId(uri, 10);
// 最终生成后的Uri为:content://cn.scu.myprovider/user/10
// parseId()作用:从URL中获取ID
Uri uri = Uri.parse("content://cn.scu.myprovider/user/10")
long personid = ContentUris.parseId(uri);
//获取的结果为:10
6.UriMatcher类
辅助ContentProvider的工具类,在ContentProvider 中注册URI,根据 URI 匹配 ContentProvider 中对应的数据表。具体使用如下:
private static final UriMatcher sMatcher;
static{
sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
sMatcher.addURI(DbUtil.AUTOHORITY, DbUtil.TNAME, DbUtil.ITEM);
sMatcher.addURI(DbUtil.AUTOHORITY, DbUtil.TNAME+"/#", DbUtil.ITEM_ID);
}
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
switch (sMatcher.match(uri)) {
case DbUtil.ITEM:
return DbUtil.CONTENT_TYPE;
case DbUtil.ITEM_ID:
return DbUtil.CONTENT_ITEM_TYPE;
default:
throw new IllegalArgumentException("Unknown URI"+uri);
}
}
7.ContentObserver类
ContentObserver翻译成中文就是内容观察者,目的是观察(捕捉)特定Uri引起的数据库的变化,继而做一些相应的处理。ContentObserver一般和系统或第三方程序提供的Provider一起使用,这些Provider一般情况下会有一个Uri,然后ContentObserver就去监听这些Uri数据的变化,然后做出相应的处理。
使用ContentObserver的步骤我这里总结如下:
1 首先创建一个ContentObserver的子类,然后实现里面的onChange方法,监听的Uri中的数据发生变化的时候,会调用onchange方法。
2 注册ContentObserver。
下面简单的列出例子代码:
ContentObserver的子类

package com.hbq.contentprovidertest.contentprovider;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.util.Log;
public class SMSObserver extends ContentObserver {
private Context mContext;
private Handler mHandler ; //此Handler用来更新UI线程
public SMSObserver(Context context,Handler handler) {
super(handler);
mContext = context;
mHandler = handler ;
}
@Override
public void onChange(boolean selfChange) {
Cursor cursor = mContext.getContentResolver().query(
Uri.parse("content://sms/inbox"), null, null, null, null);
while (cursor.moveToNext()) {
StringBuilder sb = new StringBuilder();
sb.append("address=").append(
cursor.getString(cursor.getColumnIndex("address")));
sb.append(";subject=").append(
cursor.getString(cursor.getColumnIndex("subject")));
sb.append(";body=").append(
cursor.getString(cursor.getColumnIndex("body")));
sb.append(";time=").append(
cursor.getLong(cursor.getColumnIndex("date")));
Log.e("---Receivered SMS::", "" + sb.toString());
}
}
}
下面给出MainActivity.java类使用ContentObserver:

package com.hbq.contentprovidertest.contentprovider;

import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import com.hbq.contentprovidertest.R;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//注册观察者Observser
this.getContentResolver().registerContentObserver(Uri.parse("content://sms"),
true,new SMSObserver(this,new Handler()));
}
}
观察系统的短信息数据发生了变化。当监听到短信数据发生变化时,查询所有已发送的短信并在日志打印出来。

四、ContentProvider的自定义以及使用
1.大体的实现步骤:
(1).创建一个数据源,例如继承SQLiteOpenHelper创建一个SQLite数据库;
(2).创建一个继承自ContentProvider的类,并重写insert、delete、query、update、getType、onCreate方法,在这些方法中实现对数据源的操作;
(3).在AndroidManifest.xml文件中添加<provider>标签,两个必写的属性是android:name和android:authorities;
(4).在本应用或者其它应用的Activity、Service等组件中使用ContentResolver通过对应的URI来操作该自定义ContentProvider。
2.示例代码如下:
(1)工具类DbUtil:
public class DbUtil {
public static final String DBNAME = "hbqdb";
public static final String TNAME = "user";
public static final int VERSION = 1;
public static String TID = "tid";
public static final String EMAIL = "email";
public static final String USERNAME = "username";
public static final String DATE = "date";
public static final String SEX = "sex";
public static final String AUTOHORITY = "com.hbq.contentprovidertest";
public static final int ITEM = 1;
public static final int ITEM_ID = 2;
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.hbq.login";
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.hbq.login";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTOHORITY + "/user");
}
(2)数据源DBHelper类:

public class DBHelper extends SQLiteOpenHelper {
public DBHelper(Context context) {
super(context, DbUtil.DBNAME, null, DbUtil.VERSION);
// TODO Auto-generated constructor stub
}
@Override
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
db.execSQL("create table "+ DbUtil.TNAME+"(" +
DbUtil.TID+" integer primary key autoincrement not null,"+
DbUtil.EMAIL+" text not null," +
DbUtil.USERNAME+" text not null," +
DbUtil.DATE+" interger not null,"+
DbUtil.SEX+" text not null);");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
}
public void add(String email,String username,String date,String sex){
SQLiteDatabase db = getWritableDatabase();
ContentValues values = new ContentValues();
values.put(DbUtil.EMAIL, email);
values.put(DbUtil.USERNAME, username);
values.put(DbUtil.DATE, date);
values.put(DbUtil.SEX, sex);
db.insert(DbUtil.TNAME,"",values);
}
}
(3)自定义的ContentProvider类MyProvider:
public class MyProvider extends ContentProvider {
private DBHelper dBlite;
private SQLiteDatabase db;
private static final UriMatcher sMatcher;
static{
sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
sMatcher.addURI(DbUtil.AUTOHORITY, DbUtil.TNAME, DbUtil.ITEM);
sMatcher.addURI(DbUtil.AUTOHORITY, DbUtil.TNAME+"/#", DbUtil.ITEM_ID);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// TODO Auto-generated method stub
db = dBlite.getWritableDatabase();
int count = 0;
switch (sMatcher.match(uri)) {
case DbUtil.ITEM:
count = db.delete(DbUtil.TNAME,selection, selectionArgs);
break;
case DbUtil.ITEM_ID:
String id = uri.getPathSegments().get(1);
count = db.delete(DbUtil.TID, DbUtil.TID+"="+id+(!TextUtils.isEmpty(DbUtil.TID="?")?"AND("+selection+')':""), selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI"+uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
switch (sMatcher.match(uri)) {
case DbUtil.ITEM:
return DbUtil.CONTENT_TYPE;
case DbUtil.ITEM_ID:
return DbUtil.CONTENT_ITEM_TYPE;
default:
throw new IllegalArgumentException("Unknown URI"+uri);
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// TODO Auto-generated method stub
db = dBlite.getWritableDatabase();
long rowId;
if(sMatcher.match(uri)!= DbUtil.ITEM){
throw new IllegalArgumentException("Unknown URI"+uri);
}
rowId = db.insert(DbUtil.TNAME, DbUtil.TID,values);
if(rowId>0){
Uri noteUri= ContentUris.withAppendedId(DbUtil.CONTENT_URI, rowId);
getContext().getContentResolver().notifyChange(noteUri, null);
return noteUri;
}
throw new IllegalArgumentException("Unknown URI"+uri);
}
@Override
public boolean onCreate() {
// TODO Auto-generated method stub
this.dBlite = new DBHelper(this.getContext());
// db = dBlite.getWritableDatabase();
// return (db == null)?false:true;
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// TODO Auto-generated method stub
db = dBlite.getWritableDatabase();
Cursor c;
Log.d("-------", String.valueOf(sMatcher.match(uri)));
switch (sMatcher.match(uri)) {
case DbUtil.ITEM:
c = db.query(DbUtil.TNAME, projection, selection, selectionArgs, null, null, null);
break;
case DbUtil.ITEM_ID:
String id = uri.getPathSegments().get(1);
c = db.query(DbUtil.TNAME, projection, DbUtil.TID+"="+id+(!TextUtils.isEmpty(selection)?"AND("+selection+')':""),selectionArgs, null, null, sortOrder);
break;
default:
Log.d("!!!!!!", "Unknown URI"+uri);
throw new IllegalArgumentException("Unknown URI"+uri);
}
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO Auto-generated method stub
return 0;
}
}
(4)在AndroidManifest.xml文件里的配置:

<provider
android:name=".contentprovider.MyProvider"
android:authorities="com.hbq.contentprovidertest"
android:enabled="true"
android:exported="true"></provider>
(5)在Activity中的调用:

public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
displayRecords();
updateRecord(1, "zhangsan");
}
private void displayRecords() {
//该数组中包含了所有要返回的字段
String columns[] = new String[]{DbUtil.USERNAME, DbUtil.EMAIL};
Uri mContacts = DbUtil.CONTENT_URI;
Cursor cur = managedQuery(
mContacts,
columns,// 要返回的数据字段
null, // WHERE子句
null,// WHERE 子句的参数
null// Order-by子句
);
if (cur.moveToFirst()) {
String name = null;
String phoneNo = null;
do {
// 获取字段的值
name = cur.getString(cur.getColumnIndex(DbUtil.USERNAME));
phoneNo = cur.getString(cur.getColumnIndex(DbUtil.EMAIL));
Toast.makeText(this, name + "" + phoneNo, Toast.LENGTH_LONG).show();
} while (cur.moveToNext());
}
}
private void updateRecord(int recNo, String name) {
Uri uri = ContentUris.withAppendedId(DbUtil.CONTENT_URI, recNo);
ContentValues values = new ContentValues();
values.put(DbUtil.USERNAME, name);
getContentResolver().update(uri, values, null, null);
}
}
以上就是关于ContentProvider的一些相关知识,如果有不合理之处请多多指正!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容