1 ContentProvider相关概念
ContentProvider是跨应用数据交换的标准,主要用于在许可的情况下获取其他应用的数据。为了更好的了解ContentProvider,我们需要了解URI和MIME。
1.1 URI(Uniform Resource Identifier)
统一资源标识符
这个标识符主要用于唯一标识 ContentProvider和
其中的数据。URI 为系统中的每一个资源赋予一个名字。
1.1.1 URI格式解读
每一个 ContentProvider 都拥有一个公共的 URI,用于表示 ContentProvider 所提供的数据。URI 的格式如下:
// contentprovider规则与案例
[scheme:][//host:port][path][?query]
content://com.ags.myprovider/tablename/id:
// 网页案例对比
[协议][主机][唯一资源名]
https://blog.csdn.net/vviccc?type=blog
URI格式内容解读:
- 标准前缀(scheme)对应content://,用来说明这个Content Provider控制数据。
-
URI 的标识(host:port)对应com.ags.myprovider,用于唯一标识ContentProvider,外部调用可以根据这个标识来找到它(注意:对于第三方应用程序,为了保证 URI 标识的唯一性,它必须是一个完整的、小写的类名);在Androidmanifest.xml配置文件中Contentprovider组件中的authorities属性中进行说明。
-
路径(path)对应tablename,可以理解为要操作的数据库中的表名(比如:操作tablename表中id为7的记录,构建路径为:/tablename/7;操作tablename表中id为7的记录的name字段,构建路径:/tablename/7/name)。
-
记录ID(query)对应id,如果URI中包含表示需要获取的记录的 ID,则返回该id对应的数据,如果没有ID,就表示返回全部。
如果需要将一个字符串转换成Uri,可以使用Uri类中的parse()方法,如:
Uri uri = Uri.parse("content://com.ags.myprovider/tablename");
1.1.2 Uri 的工具类
Android 提供了两个用于操作 Uri 的工具类,分别为 UriMatcher 和 ContentUris 。
@1 UriMatcher:用于匹配 Uri,它的使用步骤如下:
创建端使用如下:
//@创建端
//定义常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码
UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
//注册uri内容,如果有调用match()方法,发现匹配“content:...”的路径,则返回匹配码为1
uriMatcher.addURI("content://com.ags.myprovider", " tablename ", 1);
调用端使用如下:
//@调用端
//使用uriMatcher.match(Uri) 方法对输入的 Uri 进行匹配,如果匹配就返回对应的匹配码
switch (uriMatcher.match(Uri.parse("content://..."))) {
case 1:
//match 1, todo something
break;
//...
default:
//match nothing, todo something
break;
}
@2 ContentUris
用于操作Uri路径后面的ID,它的关键方法是withAppendedId(Uri uri, long id) 和 parseId(Uri uri)。使用解读如下:
//通过字符串生成Uri
Uri uri = Uri.parse("content://cn.ags.myprovider/user")
//withAppendedId:为路径加上ID
//添加ID后缀,执行后Uri为:content://cn.scu.myprovider/user/7
Uri resultUri = ContentUris.withAppendedId(uri, 7);
//parseId:从路径中获取ID
//获取的结果为:7
long personid = ContentUris.parseId(uri);
1.2 MIME
MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型。是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。简而言之,就是根据MIME标识位判定 数据/文件 的类别,进而做出不同的操作。
多用途互联网邮件扩展,它是一个互联网标准,在1992年最早应用于电子邮件系统,但后来也应用到浏览器。服务器会将它们发送的多媒体数据的类型告诉浏览器,而通知手段就是说明该多媒体数据的MIME类型,从而让浏览器知道接收到的信息哪些是MP3文件,哪些是Shockwave文件等等。服务器将MIME标志符放入传送的数据中来告诉浏览器使用哪种插件读取相关文件。
@1 MIME Type包括许多文件类型,比如:图片、视频、音频等,如下所示:
MIME字段 | 对应文件类型 |
---|---|
text/plain | 纯文本 |
image/gif | gif图像 |
image/jpeg | jpeg图像 |
video/mpeg | MPEG动画 |
audio/mp3 | mp3音乐 |
更多格式可以参考文档:MIME 参考手册
@2 Android中ContentProvider根据URI来返回MIME类型解读
ContentProvider 会返回一个包含两部分的字符串,每个内容类型的 Android MIME 类型有两种形式:多条记录(集合)和单条记录。如下:
- vnd.android.cursor.dir/自定义,集合类型(自定义部分可自己填写)
- vnd.android.cursor.item/自定义,单条记录(自定义部分可自己填写)
注意:vnd 表示这些类型和子类型具有非标准的、供应商特定的形式。Android中类型已经固定好了,因此只能区别是集合还是单条具体记录。
ContentProvider在使用Intent时会用到MIME类型,根据 Mimetype 打开符合条件的活动。
1.3 ContentProvider的关键方法
有了前面Uri和MIME的了解,再来看Contentprovider类的关键方法有创建回调、CRUD操作(增删改查)、类型获取,对于Contentprovider的子类而言,关键的几个方法如下所示:
onCreate()
调用它来初始化提供程序。query(Uri, String[], Bundle, CancellationSignal)
它将数据返回给调用者,(对应R,retrieve)。insert(Uri, ContentValues)
它将新数据插入内容提供程序(对应C,Create)。update(Uri, ContentValues, Bundle)
更新内容提供者中的现有数据(对应U)。delete(Uri, Bundle)
从内容提供者中删除数据(对应D)。getType(Uri)
它返回内容提供程序中的 MIME 类型的数据。
2 ContentProvider配置
在AndroidManifest.xml里声明,如下所示:
<provider android:name="MyProvider"
android:exported="true"
android:authorities="com.ags.myprovider"
/>
常见属性一般有name、exported(是否允许被其他应用调用),permission(相当于配置了readPermission和writePermission两个权限),这里针对关键参数进行说明:
- authorities:指定了ContentProvider的Uri,一般是provider所在包包名+provider名。
- permission:这里会细分,可单独配置readPermission表示读ContentProvider的权限,writePermission表示写ContentProvider的权限,permission属性。
3 ContentProvider & ContentResolver
ContentProvider的执行涉及两个应用(一个通过ContentProvider来提供数据,另一个通过ContentResolver来获取数据),关系图如下所示:
可以这样理解,应用/进程B调用ContentResolver中的CRUD方法实际上就是调用了应用/进程A中对应Uri的ContentProvider的CRUD方法。
4 ContentProvider使用
4.1 提供端ContentProvider
@1 自定义ContentProvider
先定义一个辅助类DbOpenHelper,代码如下:
public class DbOpenHelper extends SQLiteOpenHelper {
final String CREATE_SQL = "CREATE TABLE test(_id INTEGER PRIMARY KEY AUTOINCREMENT,name)";
public DbOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory,int version) {
super(context, name, null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_SQL);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
}
}
这里定义一个MyContentProvider,代码如下所示:
public class MyContentprovider extends ContentProvider {
private static String TAG = "MyContentprovider";
private static final int MATCH_SUCCESS_VALUE = 1;
//初始化UriMatcher和DbOpenHelper
private static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
private DbOpenHelper dbOpenHelper;
static{
matcher.addURI("com.ags.myprovider", "test", MATCH_SUCCESS_VALUE);
}
@Override //被创建后调用
public boolean onCreate() {
Log.d(TAG, "onCreate: ");
dbOpenHelper = new DbOpenHelper(this.getContext(), "test.db", null, 1);
return false;
}
@Nullable
@Override //返回Uri所代表的MIME数据类型,结果是多条记录/单条记录
public String getType(@NonNull Uri uri) {
Log.d(TAG, "getType: ");
return null;
}
@Nullable
@Override //C(create):根据Uri插入value值
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
Log.d(TAG, "insert: ");
switch(matcher.match(uri))
{
case MATCH_SUCCESS_VALUE:
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
long rowId = db.insert("test", null, values);
if(rowId > 0)
{
Uri nameUri = ContentUris.withAppendedId(uri, rowId);
getContext().getContentResolver().notifyChange(nameUri, null);
Log.d(TAG, "insert: notifyChange");
return nameUri;
}
break;
default:
throw new IllegalStateException("Unexpected value: " + matcher.match(uri));
}
return null;
}
@Nullable
@Override //R(Retrieve):根据Uri查询selection条件多匹配的所有记录
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
Log.d(TAG, "query: ");
return null;
}
@Override //U(update):根据Uri更新selection条件多匹配的所有记录
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
Log.d(TAG, "update: ");
return 0;
}
@Override //D(delete):根据Uri删除selection条件多匹配的所有记录
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
Log.d(TAG, "delete: ");
return 0;
}
}
之后在AndroidManifest中注册即可,上面的配置直接copy过来,如下:
<provider android:name="MyProvider"
android:exported="true"
android:authorities="com.ags.myprovider"
/>
作为提供数据端的ContentProvider该部分就完成了。
@2 系统ContentProvider
系统提供ContentProvider很多,详细参考文档:android系统支持的provider集合概览
4.2 使用端ContentResolver
@1 针对自定义ContentProvider的处理
通过ContentResolver来获取自定义MyContentProvider提供的数据,ContentResolver部分的代码实现如下所示:
public class MainActivity extends AppCompatActivity {
private static String TAG = "MainActivity";
private Button btn_start;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_start = findViewById(R.id.btn_start);
btn_start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//1 构建value值
ContentValues values = new ContentValues();
values.put("name", "test");
//2 根据Contentprovider获取对应的Uri
Uri uri = Uri.parse("content://com.ags.myprovider/test");
Log.d(TAG, "onClick: insert operation");
//3 获取ContentResolver对象,根据Uri找到对应的ContentProvider,插入values
getContentResolver().insert(uri, values);
}
});
}
}
@2 针对系统ContentProvider的处理
这里以获取联系人信息为例。通过ContentResolver来获取联系人应用对应的Provider提供的数据(联系人名称、联系人电话号码、联系人ID号),ContentResolver部分的代码实现如下所示:
首先需要在AndroidManifest.xml文件中添加权限,如下:
<uses-permission android:name="android.permission.READ_CONTACTS" />
其次,ContentResolver部分的代码实现如下所示:
public class MainActivity extends AppCompatActivity {
private static String TAG = "MainActivity";
private Button btn_start;
private Uri uri;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_start = findViewById(R.id.btn_start);
btn_start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String contactId = "";
uri = Uri.parse("content://com.android.contacts/raw_contacts");
Cursor cursor= getContentResolver().query(uri, null, null, null, null);
while(cursor.moveToNext())
{
//获取联系人ContactID和姓名
contactId = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));
String cName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
Log.d(TAG, "Contact:ID:" + contactId);
Log.d(TAG, "Contact:name:" + cName);
Cursor phoneCursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=" + contactId, null, null);
while (phoneCursor.moveToNext()) {
//根据ContactID获取联系人电话号码
String phone = phoneCursor.getString(phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
phone = phone.replace("-", "").replace(" ", "");
//phone = phone.replace(" ", "");
Log.d(TAG, "Contact: TelNum:"+phone);
Log.d(TAG, "Contact:=================");
}
phoneCursor.close();
}
cursor.close();
}
});
}
}
注意:电话号码要单独查询,一方面有多个,另一方面因为有空格需要特殊处理下。
5 ContentObserver监听机制
如果ContentProvider的数据获取者要知道数据变化,则在ContentProvider数据提供者发生数据变化时调用getContentResolver().notifyChange(uri, null)来通知注册在此URI上的数据获取者。比如在4.1部分中关于insert操作中就有该实现,如下所示:
public class MyContentprovider extends ContentProvider {
{
//....
public Uri insert(Uri uri, ContentValues values) {
switch(matcher.match(uri))
{
case MATCH_SUCCESS_VALUE:
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
long rowId = db.insert("test", null, values);
if(rowId > 0)
{
Uri nameUri = ContentUris.withAppendedId(uri, rowId);
getContext().getContentResolver().notifyChange(nameUri, null);
return nameUri;
}
break;
//...
}
return null;
}
//...
}
数据提供者在数据变化时给出了通知,接下来在数据获取者ContentResolver部分中需要以下关键步骤:
- 自定义ContentObserver类并实现onChange方法,实现监听器类。
- 调用ContentResolver的registerContentObserver方法注册监听器。
相关代码实现如下:
public class MainActivity extends AppCompatActivity {
private static String TAG = "MainActivity";
private Button btn_start;
//1 创建ContentObserver的子类MyObserver并实现onChange方法
private final class MyObserver extends ContentObserver{
public MyObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
getContentResolver().query( \
Uri.parse("content://com.ags.myprovider/test"),null,null,null,null);
Log.d(TAG, "onChange: effected");
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_start = findViewById(R.id.btn_start);
//2 注册MyObserver监听器
getContentResolver().registerContentObserver( \
Uri.parse("content://com.ags.myprovider/test"),true,new MyObserver(new Handler()));
btn_start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//1 构建value值
ContentValues values = new ContentValues();
values.put("name", "test");
//2 根据Contentprovider获取对应的Uri
Uri uri = Uri.parse("content://com.ags.myprovider/test");
Log.d(TAG, "onClick: insert operation");
//3 获取ContentResolver对象,根据Uri找到对应的ContentProvider,插入values
getContentResolver().insert(uri, values);
}
});
}
}
6 官方文档索引
关于ContentProvider更多内容查看文档:Android组件之ContentProvider组件
总结
- 了解Uri和MIME、ContentProvider和ContentResolver基本概念、Androidmanifest配置、常见系统ContentProvider、ContentObserver监听机制。
- 自定义ContentProvider和ContentResolver交互、系统ContentProvider获取数据方式、ContentObserver监听实现。