1.什么是ContentProvider?
内容提供程序管理对数据结构化数据集的访问。它们封装数据,并提供用于定义数据安全性的机制。内容提供者是连接一个
进程
中的数据与另一个进程
中运行的代码的标准界面。
是不同应用程序之间进行数据交换的标准API,以某种Uri的形式对外提供数据,允许其他应用访问或修改数据;其他程序使用ContentResolver根据Uri去访问操作指定的数据。
是IPC通信的一种。
2.ContentProvider和ContentResolver
- 创建ContentProvider的派生类,重写必要的方法,方法有:(
ContentResolve
r的方法与它类似)
- onCreate():当其他程序第一个访问ContentProvider时回调此方法。
- String getType(Uri uri):用于返回当前Uri所代表的MIME类型,如果包含多条数据则返回
“vnd.android.cursor.dir/开头
”,单条数据则返回“vnd.android.cursor.item/开头
”,开头部分格式:vnd.<authority>.<path>
- insert(Uri uri,ContentValue values):根据该uri插入对应的value值
- update(Uri uri,…):根据uri修改对应参数条件的所有值
- delete(Uri uri):根据uri删除符合参数条件的所有值
- query(Uri uri):根据uri查询符合条件的所有值
- AndroidManifest.xml文件中为其注册
<provider
android:name=".contentProvider.MyContentProvider"
android:authorities="org.wdl.book"
android:enabled="true"
android:exported="true"
/>
属性 | 意义 |
---|---|
android:name | 类名 |
android:authorities | 指定相应的域名 |
android:exported | 是否提供给外部程序使用 |
- 客户端程序通过ContentResolver的一系列方法调用ContentProvider提供的API进行数据的CRUD
相对应的流程如图:
ContentResolver.query()。 query() 方法会某某提供程序所定义的 ContentProvider.query() 方法。 以下代码行显示了 ContentResolver.query() 调用:(其他方法类似)
// Queries the user dictionary and returns results
mCursor = getContentResolver().query(
UserDictionary.Words.CONTENT_URI, // The content URI of the words table
mProjection, // The columns to return for each row
mSelectionClause // Selection criteria
mSelectionArgs, // Selection criteria
mSortOrder); // The sort order for the returned rows
其中参数与之前SQLite存储提到的类似,详见](https://blog.csdn.net/qq_34341338/article/details/84069391)
由上图可以看出URI在这数据交互的过程中起到了关键性(进行数据交换的标识
)的作用,因此,该URI所对应的要求如下:`
- 以
content://
开头,固定不变的 - 中间部分为之前xml文件中配置的
android:authorities
,固定不变的 - 第三部分代表
资源
部份。这个部分是动态改变,根据访问者的需求更改 content://authorities/source/#
,#代表通配符,可以为1,2,3等;content://authorities/*
,*代表任意长度字符,表示所有表
分析:
例:
content://org.wdl.provider/books
意味访问books数据
content://org.wdl.provider/book/2
意味访问books数据中ID为2的数据
content://org.wdl.provider/book/2/name
意味访问books数据中ID为2的数据中的name字段
Uri工具类提供了parse静态方法,用于将字符串转为Uri
ContentResolver
调用方法中传递的Uri
必须能够与ContentProvider
中所暴露Uri
的相匹配,否则会抛出异常。为了确保正常进行数据CRUD,Android提供了UriMatcher
工具类:
void addURI(String authority,String path,int code):
向UriMatcher中注册Uri,authority+path构成Uri,code表示标识码,根据它来判断是否能够进行数据操作
int match(Uri uri):
根据注册的Uri判断指定Uri对应的标识码,匹配不到返回-1
例:
matcher = new UriMatcher(UriMatcher.NO_MATCH);
matcher.addURI(Books.AUTHORITY, "books", MyContentProvider.BOOKS);
matcher.addURI(Books.AUTHORITY, "book/#", MyContentProvider.BOOK);
code = matcher.match(uri)
假如现在我有一个需求:
查询Books中id为2的数据,需对原始的Uri进行拼接
,Android为此提供了另一个工具类ContentUris
,主要方法如下:
- withAppendedId(Uri uri,int id):在路径上拼接ID部分
- parseId(Uri uri):解析出指定Uri所包含的ID值
例:
ContentUris.withAppendedId(Books.Book.BOOK_CONTENT_URI,1)
拼接后的uri为:content://org.wdl.provider/book/1
long id = ContentUris.parseId(uri);
id值为:1
3.详细使用
MyContentProvider.class
package com.wdl.crazyandroiddemo.contentProvider;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import com.wdl.crazyandroiddemo.MySQLiteHelper;
import java.util.Objects;
public class MyContentProvider extends ContentProvider {
private MySQLiteHelper helper;
private static UriMatcher matcher;
private static final int BOOKS = 1;
private static final int BOOK = 2;
static {
matcher = new UriMatcher(UriMatcher.NO_MATCH);
matcher.addURI(Books.AUTHORITY, "books", MyContentProvider.BOOKS);
matcher.addURI(Books.AUTHORITY, "book/#", MyContentProvider.BOOK);
}
public MyContentProvider() {
}
@Override
public boolean onCreate() {
Log.e("wdl", "----------onCreate----------");
helper = new MySQLiteHelper(getContext(), "demo.db", null, 3);
return true;
}
@Override
public int delete(@NonNull Uri uri, String where, String[] whereArgs) {
Log.e("wdl", "----------delete----------");
SQLiteDatabase db = helper.getReadableDatabase();
int num = 0;
switch (matcher.match(uri)) {
case BOOKS:
num = db.delete("book", where, whereArgs);
break;
case BOOK:
long id = ContentUris.parseId(uri);
String whereClause = Books.Book.ID + "=" + id;
if (!TextUtils.isEmpty(where)) {
whereClause = whereClause + " and " + where;
}
num = db.delete("book", whereClause, whereArgs);
break;
default:
throw new IllegalArgumentException("未知uri");
}
Objects.requireNonNull(getContext()).getContentResolver().notifyChange(uri, null);
return num;
}
@Override
public String getType(@NonNull Uri uri) {
Log.e("wdl", "----------getType----------");
switch (matcher.match(uri)) {
case BOOKS:
return "vnd.android.cursor.dir/wdl.books";
case BOOK:
return "vnd.android.cursor.item/wdl.book";
default:
throw new IllegalArgumentException("未知uri");
}
}
@Override
public Uri insert(@NonNull Uri uri, ContentValues values) {
Log.e("wdl", "----------insert----------");
//获取数据库实例
SQLiteDatabase db = helper.getReadableDatabase();
switch (matcher.match(uri)) {
case BOOKS:
//插入
long rowId = db.insert("book", Books.Book.ID, values);
if (rowId > 0) {
//uri末尾添加id
Uri bookUri = ContentUris.withAppendedId(uri, rowId);
//通知数据已经改变
Objects.requireNonNull(getContext()).getContentResolver().notifyChange(bookUri, null);
return bookUri;
}
break;
default:
throw new IllegalArgumentException("Not yet implemented");
}
return null;
}
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
Log.e("wdl", "----------query----------");
SQLiteDatabase db = helper.getReadableDatabase();
switch (matcher.match(uri)) {
case BOOKS:
return db.query("book",
projection,
selection,
selectionArgs,
null,
null, sortOrder);
case BOOK:
long id = ContentUris.parseId(uri);
String whereClause = Books.Book.ID + "=" + id;
if (!TextUtils.isEmpty(selection)) {
whereClause = whereClause + " and " + selection;
}
return db.query("book", projection, whereClause, selectionArgs, null, null, sortOrder);
default:
throw new IllegalArgumentException("未知uri");
}
}
@Override
public int update(@NonNull Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
Log.e("wdl", "----------update----------");
SQLiteDatabase db = helper.getReadableDatabase();
int num = 0;
switch (matcher.match(uri)) {
case BOOKS:
num = db.update("book", values, selection, selectionArgs);
break;
case BOOK:
long id = ContentUris.parseId(uri);
String whereClause = Books.Book.ID + "=" + id;
if (!TextUtils.isEmpty(selection)) {
whereClause = whereClause + " and " + selection;
}
num = db.update("book", values, whereClause, selectionArgs);
break;
default:
throw new IllegalArgumentException("未知uri");
}
Objects.requireNonNull(getContext()).getContentResolver().notifyChange(uri, null);
return num;
}
}
Books.class,此类封装了可访问Uri以及可访问的数据列:
package com.wdl.crazyandroiddemo.contentProvider;
import android.net.Uri;
import android.provider.BaseColumns;
/**
* author: wdl
* time: 2018/11/17 13:43
* des: TODO
*/
@SuppressWarnings("unused")
public class Books {
//定义contentProvider 的Authorities值
public static final String AUTHORITY = "org.wdl.book";
//定义静态内部类,定义contentProvider所包含的数据列
public static final class Book implements BaseColumns {
//定义所允许操作的三个数据列
public final static String ID = "id";
public final static String NAME = "name";
public final static String PRICE = "price";
public final static String DATE = "publishdate";
//定义提供服务的两个Uri
public final static Uri BOOKS_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/books");
public final static Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book");
}
}
在Client中调用:
package com.wdl.contentproviderclient
import android.content.ContentUris
import android.content.ContentValues
import android.content.Intent
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.android.synthetic.main.activity_main.*
import java.lang.StringBuilder
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val contentResolver = contentResolver
btnQuery.setOnClickListener {
val intent = Intent("com.wdl.mime")
intent.data = Books.Book.BOOKS_CONTENT_URI
startActivity(intent)
val cursor = contentResolver.query(Books.Book.BOOKS_CONTENT_URI
,null,"price=?", arrayOf("50.2"),null)
//cursor.close()
val stringBuilder = StringBuilder()
while (cursor.moveToNext()){
val id = cursor.getInt(cursor.getColumnIndexOrThrow(Books.Book.ID))
val name = cursor.getString(cursor.getColumnIndexOrThrow(Books.Book.NAME))
val price = cursor.getInt(cursor.getColumnIndexOrThrow(Books.Book.PRICE))
stringBuilder.append("id=$id,name=$name,price=$price \n")
}
cursor.close()
Log.e("wdl",stringBuilder.toString())
}
btnDelete.setOnClickListener {
//val index = contentResolver.delete(uri,null,null)
val index = contentResolver.delete(Books.Book.BOOKS_CONTENT_URI,"price=?", arrayOf("55.2"))
Log.e("wdl",""+index)
}
btnInsert.setOnClickListener {
val value = ContentValues()
value.put(Books.Book.NAME,"smwdl")
value.put(Books.Book.PRICE,50.2)
val uri = contentResolver.insert(Books.Book.BOOKS_CONTENT_URI,value)
Log.e("wdl",uri.toString())
}
btnUpdate.setOnClickListener {
val value = ContentValues()
value.put(Books.Book.PRICE,55.2)
val uri = ContentUris.withAppendedId(Books.Book.BOOK_CONTENT_URI,1)
val index = contentResolver.update(uri
,value,"name like ?", arrayOf("smwdl"))
Log.e("wdl",""+index)
}
}
}
注意事项:
- 在MyContentPrivoder中实现的CRUD方法,都需要通过
uri判断
,根据match返回值来执行对应的方法。例如:
switch (matcher.match(uri)) {
case BOOKS:
return db.query("book",
projection,
selection,
selectionArgs,
null,
null, sortOrder);
case BOOK:
long id = ContentUris.parseId(uri);
String whereClause = Books.Book.ID + "=" + id;
if (!TextUtils.isEmpty(selection)) {
whereClause = whereClause + " and " + selection;
}
return db.query("book", projection, whereClause, selectionArgs, null, null, sortOrder);
default:
throw new IllegalArgumentException("未知uri");
}
上段代码中,对匹配码做出了判断,并进行区分处理:Books代表对所有数据进行操作,Book代表对单项数据进行操作。而单项数据进行操作的化需要进行SQL语句的拼接
。通常为添加ID。
- 在static静态代码块中需进行UriMatcher的注册
- 权限声明,在清单文件中配置相对应的读写权限。系统自带的ContentProvider需进行其他权限的声明。在6.0之后需动态的申请所需权限,否则无法进行数据的CRUD
- 在insert,update,delete之后都必须进行
getContext().getContentResolver().notifyChange(uri, null)
进行数据改变的响应