一、定义
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由以下几部分组成:
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.provider`的`ContentProvider` 中表名 为`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中均有同名的方法,是一一对应的,如图:
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 中对应的数据表。具体使用如下: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的一些相关知识,如果有不合理之处请多多指正!