定义
在Android中,ContentProvider也是四大组件之一,它被翻译成内容提供者。如果在开发中需要实现跨包数据共享,那就需要使用ContentProvider,它存储数据的方式和使用它的应用程序无关,因为它为存储和获取数据提供统一的接口。Android附带了许多有用的ContentProvider,包括:
browser 存储诸如浏览器书签、浏览器历史记录等数据
call_log 存储诸如未接电话、通话详细信息等数据
contacts 存储联系人详细信息
media 存储媒体文件,如音频、视频和图像
settings 存储设备的设置和首选项
查询ContentProvider,可使用URI形式指定查询字符串:<standard_prefix>://<authority>/<data_path>/<id>。
其中:
standard_prefix: ContentProvider里始终是content://
authority: 指定名称,Android内置的Contacts名称如上列表:browser、call_log、contacts,等。而自定义的,一般如:com.xxx.xxx
data_path: 指定请求数据的类型。例如,如果是从Contacts获取所有联系人,那么datapath就是people,而URI为content://contacts/people。
id: 指定请求的特定记录,例如,从Contacts中查找10号联系人,那么URL为content:// contacts /people/10。
一些查询字符串的示例:
content://media/internal/images 返回设备上所有内部图像的列表
content://media/external/images 返回存储在外部的所有图像的列表
content://call_log/calls 返回在CallLog中记录的所有通话的列表
content://contacts/people 返回联系人列表
content://browser/bookmarks 返回存储在浏览器中的书签列表
使用ContentProvider查询系统联系人
Uri callContacts =ContactsContract.Contacts.CONTENT_URI; // Uri callContacts = Uri.parse("content://contacts/people");
Cursor contactsCursor;
if (Build.VERSION.SDK_INT < 11) {
contactsCursor = managedQuery(callContacts, null, null, null, null);
} else {
CursorLoader cursorLoader = new CursorLoader(this, callContacts, null,null, null, null);
contactsCursor = cursorLoader.loadInBackground();
}
if (contactsCursor.moveToFirst()) {
do {
String contactID =contactsCursor.getString(contactsCursor.getColumnIndex(ContactsContract.Contacts._ID));
String contactDisplayName = contactsCursor.getString(contactsCursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
}while (contactsCursor.moveToNext());
}
contactsCursor.close();
上例只是获取联系人ID和名称,但还没有获得电话号码,如果想获得联系人电话号码,由于这信息存储于另外一张表中,因此需要再次查询:
Uri callContacts =ContactsContract.Contacts.CONTENT_URI; // Uri callContacts = Uri.parse("content://contacts/people");
Cursor contactsCursor;
if (Build.VERSION.SDK_INT < 11) {
contactsCursor = managedQuery(callContacts, null, null, null, null);
} else {
CursorLoader cursorLoader = new CursorLoader(this, callContacts, null,null, null, null);
contactsCursor = cursorLoader.loadInBackground();
}
if (contactsCursor.moveToFirst()) {
do {
String contactID =contactsCursor.getString(contactsCursor.getColumnIndex(ContactsContract.Contacts._ID));
String contactDisplayName =contactsCursor.getString(contactsCursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
int hasPhone =contactsCursor.getInt(contactsCursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER));
if (hasPhone == 1) {
Cursor phoneCursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " +contactID, null, null);
while (phoneCursor.moveToNext()) {
String contactPhoneNumber =phoneCursor.getString(phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
}
phoneCursor.close();
}
}while (contactsCursor.moveToNext());
}
contactsCursor.close();
别忘了在AndroidManifest.xml中加入 <uses-permissionandroid:name="android.permission.READ_CONTACTS" /> 权限。
上面使用了Android中的一个预定义常量ContactsContract.Contacts.CONTENT_URI 来代替Uri.parse(“content://contacts/people”)
如果只想获取第一个联系人,可以这样:
Uri allContacts =ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, 1);
// 或者
Uri allContacts =Uri.parse(“content://contacts/people/1”);
投影projection
managedQuery方法的第二个参数和CursorLoader类的第三个参数是接收一个叫projection的字符串数组,我们叫它作投影,它是控制查询返回的列,即像SQL里SELECT后接着写的字段,如果此参数为null,则表示返回所有的列,也就像SQL里的SELECT * FROM XXX。我们来修改上例,加入一个projection的字符串数组:
String[] projection = new String[] {
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.HAS_PHONE_NUMBER
};
Uri callContacts = ContactsContract.Contacts.CONTENT_URI; // Uri callContacts =Uri.parse("content://contacts/people");
Cursor contactsCursor;
if(Build.VERSION.SDK_INT < 11) {
contactsCursor = managedQuery(callContacts, projection, null, null,null);
}else {
CursorLoader cursorLoader = new CursorLoader(this, callContacts,projection, null, null, null);
contactsCursor =cursorLoader.loadInBackground();
}
……
筛选selection
managedQuery方法的第三、第四个参数和CursorLoader类的第四和第五个参数,这两个参数分别接收一个字符串和一个字符串数组,它们是用来指定查询结果进行筛选的,即像SQL里WHERE。我们继续修改上例,加入相关selection参数:
……
Cursor contactsCursor;
if (Build.VERSION.SDK_INT < 11) {
// contactsCursor = managedQuery(callContacts, projection,ContactsContract.Contacts.DISPLAY_NAME + " LIKE '%云心'",null, null);
// 或
contactsCursor = managedQuery(callContacts, projection,ContactsContract.Contacts.DISPLAY_NAME + " LIKE ?", new String[] {"%云心" }, null);
}else {
// CursorLoader cursorLoader = new CursorLoader(this, callContacts,projection, ContactsContract.Contacts.DISPLAY_NAME + " LIKE '%云心'",null, null);
// 或
CursorLoader cursorLoader = newCursorLoader(this, callContacts, projection,ContactsContract.Contacts.DISPLAY_NAME + " LIKE ?", new String[] {"%云心" }, null);
contactsCursor = cursorLoader.loadInBackground();
}
……
排序sortOrder
managedQuery方法和CursorLoader类的最后一个参数可以用来指定一个像SQL里ORDER BY 子句来对查询结果排序,如:
……
Cursor contactsCursor;
if (Build.VERSION.SDK_INT < 11) {
contactsCursor = managedQuery(callContacts, projection,ContactsContract.Contacts.DISPLAY_NAME + " LIKE ?", new String[] {"%云心" }, ContactsContract.Contacts.DISPLAY_NAME + "ASC");
}else {
CursorLoader cursorLoader = new CursorLoader(this, callContacts,projection, ContactsContract.Contacts.DISPLAY_NAME + " LIKE ?", newString[] { "%云心" }, ContactsContract.Contacts.DISPLAY_NAME + "ASC");
contactsCursor = cursorLoader.loadInBackground();
}
……
创建自己的ContentProvider
除了一些内置的ContentProvider以外,还可以创建自己的ContentProvider。在Android中创建自己的ContentProvider非常简单,主要是扩展抽象类ContentProvider,并重写其中定义的各种方法即可。示例:
新建数据库帮助类:DbOpenHelper,使其继承自SQLiteOpenHelper:
public class DbOpenHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "persion_database";
public static final String TABLE_NAME = "persion_table";
private static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + "(_id INTEGER PRIMARY KEY," + "name TEXT NOT NULL)";
private static final String DROP_TABLE = "DROP TABLE IF EXISTS " + TABLE_NAME;
public DbOpenHelper(Context context) {
super(context, DATABASE_NAME, null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL(DROP_TABLE);
onCreate(db);
}
}
再新建PersonProvider类并让其继承ContentProvider和重写必要方法:
public class PersonProvider extends ContentProvider {
private DbOpenHelper mDbOpenHelper;
private static final UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
public static final String AUTHORITY = "com.zyx.PersonProvider";
private final static int ITEM = 1;
private final static int ID = 2;
static {
mUriMatcher.addURI(AUTHORITY, "person", ITEM);
mUriMatcher.addURI(AUTHORITY, "person/#", ID);
}
@Override
public boolean onCreate() {
mDbOpenHelper = new DbOpenHelper(this.getContext());
return true;
}
@Override
public String getType(Uri uri) {
switch (mUriMatcher.match(uri)) {
// case DIR:
// return "vnd.android.cursor.dir/person";
case ID:
case ITEM:
return "vnd.android.cursor.item/person";
default:
throw new IllegalArgumentException("this is unkown uri:" + uri);
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = mDbOpenHelper.getWritableDatabase();
switch (mUriMatcher.match(uri)) {
case ITEM:
long id = db.insert(DbOpenHelper.TABLE_NAME, "name", values);
Uri insertUri = ContentUris.withAppendedId(uri, id);
// 通知客户端,内容发生变化,客户端可通过getContentResolver().registerContentObserver方法来注册观察者
getContext().getContentResolver().notifyChange(insertUri, null);
return insertUri;
default:
throw new IllegalArgumentException("this is unkown uri:" + uri);
}
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = mDbOpenHelper.getWritableDatabase();
switch (mUriMatcher.match(uri)) {
case ITEM: {
int number = db.delete(DbOpenHelper.TABLE_NAME, selection, selectionArgs);
if (number > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return number;
}
case ID: {
long id = ContentUris.parseId(uri); // 取得跟在URI后面的数字
// 或者使用 String idVal = uri.getPathSegments().get(1);
int number = db.delete(DbOpenHelper.TABLE_NAME, "_id = ?", new String[]{String.valueOf(id)});
if (number > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return number;
}
default: {
throw new IllegalArgumentException("this is unkown uri:" + uri);
}
}
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = mDbOpenHelper.getReadableDatabase();
switch (mUriMatcher.match(uri)) {
case ITEM: {
Cursor cursor = db.query(DbOpenHelper.TABLE_NAME, projection, selection, selectionArgs,
null, null, sortOrder);
return cursor;
}
case ID: {
long id = ContentUris.parseId(uri);
Cursor cursor = db.query(DbOpenHelper.TABLE_NAME, projection, "_id = ?", new String[]{String.valueOf(id)},
null, null, sortOrder);
return cursor;
}
default:
throw new IllegalArgumentException("this is unkown uri:" + uri);
}
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
SQLiteDatabase db = mDbOpenHelper.getWritableDatabase();
switch (mUriMatcher.match(uri)) {
case ITEM: {
int number = db.update(DbOpenHelper.TABLE_NAME, values, selection, selectionArgs);
if (number > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return number;
}
case ID: {
long id = ContentUris.parseId(uri);
int number = db.update(DbOpenHelper.TABLE_NAME, values, "_id = ?", new String[]{String.valueOf(id)});
if (number > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return number;
}
default: {
throw new IllegalArgumentException("this is unkown uri:" + uri);
}
}
}
}
这类中重写的各个方法如下所示:
getType() 根据给定的Uri返回一个MIME类型的数据,如果是单条数据,那么我们的MIME类型应该以vnd.android.cursor.item开头,如果是多条数据,我们的MIME类型的数据应该以vnd.android.cursor.dir开头,同时,对于没有访问该ContentProvider权限的应用依然可以调用它的getType方法
onCreate() 当启动ContentProvider时调用
query() 接收客户端请求,结果以Cursor对象形式返回
insert() 向ContentProvider中插入一条新记录
delete() 向ContentProvider中删除一条现有的记录
update() 向ContentProvider中更新一条现有的记录
ContentProvider中可以自由选择如何存储数据,可以使有文件系统、XML、数据库或通过Web服务等,上例中使用了SQLite数据库方法,类DbOpenHelper.class便就是为其服务。UriMatcher对象mUriMatcher定义了两种访问ContentProvider的类型,分别是:单项ID和单项ITEM,如果想实现所有项的处理,可以再行定义DIR,这里就不再列举了。
接着修改AndroidManifest.xml,在application中添加provider项:
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
……
<provider
android:name=".PersonProvider"
android:authorities="com.zyx.PersonProvider"
android:enabled="true"
android:exported="true"/>
</application>
刚才新建的PersonProvider,可以在本程序使用也可以新建另一个程序来跨程序使用,增删改查的使用如下:
// 插入数据
ContentValues values = new ContentValues();
values.put("name", "子云心");
Uri uri = getContentResolver().insert(Uri.parse("content://" + PersonProvider.AUTHORITY + "/person"), values);
// 删除数据
int delResult =getContentResolver().delete(Uri.parse("content://"+PersonProvider.AUTHORITY+"/person/1"),null, null);
//int delResult =getContentResolver().delete(Uri.parse("content://"+PersonProvider.AUTHORITY+"/person"),"_id = ?", new String[]{"1"});
// 查询数据
Cursor cursor = getContentResolver().query(Uri.parse("content://"+PersonProvider.AUTHORITY+"/person/1"), null, null, null, null);
while (cursor != null && cursor.moveToNext()) {
String id = cursor.getString(cursor.getColumnIndex("_id"));
String name = cursor.getString(cursor.getColumnIndex("name"));
}
cursor.close();
// 更新数据
ContentValues contentValues2 = new ContentValues();
contentValues2.put("name", "子云心心");
int updateResult = getContentResolver().update(Uri.parse("content://"+PersonProvider.AUTHORITY+"/person/1"),contentValues2, null, null);