四大组件之ContentProvider
ContentProvider
Content Provider用于保存和获取数据,并使其对所有应用程序可见。这是不同应用程序间共享数据的唯一方式,因为在Android中没有提供所有应用共同访问的公共存储区域。
Content Provider内部如何保存数据由其设计者决定,但是所有的Content Provider都实现一组通用的方法,用来提供数据的增、删、改、查功能。
客户端通常不会直接使用这些方法,大多数是通过ContentResolver对象实现对Content Provider的操作。开发人员可以通过调用Activity或者其他应用程序组件的实现类中的getContentResolver()方法来获得ContentProvider对象
1、URI的用法
每个Content Provider提供公共的URI(使用Uri类包装)来唯一标识其数据集。管理多个数据集(多个表格)的ContentProvider为每个数据集提供了单独的URI。所有为provider提供的URI都以“content:/P作为前缀,“content:∥模式表示数据由Content Provider来管理
如果自定义Content Provider,则应该为其URI也定义个常量,来简化客户端代码并让日后更新更加简洁。Android为当前平台提供的Content Provider定义了CONTENT URI常量。例如,匹配电话号码到联系人表格的
A:标准的前缀,用于标识该数据由Content Provider管理,不需修改。
B:URI的authority部分,用于标识该Content Provider。对于第三方应用,该部分应该是完整的类名(使用小写形式)来保证唯一性。在
C:Content Provider的路径部分,用于决定哪类数据被请求。如果Content Provider仅提供一种数据类型,可以省略该部分;如果provider提供几种类型,包括子类型,这部分可以由几部分组成。
D:被请求的特定记录的D值。这是被请求记录的D值。如果请求不仅限于单条记录,该部分及其前面的斜线应该删除。
2、预定义的Content Provider
Android系统为常用数据类型提供了很多预定义的Content Provider(声音、视频、图片、联系人等),它们大多位于android.provider包中。开发人员可以查询这些provider以获得其中包含的信息(尽管有些需要适当的权限来读取数据)。Android系统提供的常见Content Provider说明如下。
Browser | 读取或修改书签,游览历史或网络搜索 |
---|---|
CallLog | 查看或更新同通话历史 |
Contacts | 获取、修改或保存联系人信息 |
LiveFolder | 由Content Provider提供的特定文件夹 |
MediaStore | 访问声音、视频和音乐 |
Setting | 查看和获取蓝牙设置、铃声和其他偏好设置 |
SyncStateContract | 用于使用数据数组账号关联的Content Provider约束。希望用标准方式保存数据的provider时可以使用 |
UserDictionary | 在可预测文本输入时,提供用户定义单词给输入法1使用。应用程序和输入法可以增加数据到该字典。单词可以关联频率信息和本地化信息 |
3、查询数据
查询Content Provider中的数据,需要以下三点:
- 标识该Content Provider的URL.
- 需要查询的数据字段名称。
- 字段中数据的类型。
如果需要查询特定的记录,那么还需要提供ID
为了查询Content Provider中的数据,开发人员需要使用ContentResolver.query()或Activity.managedQuery()方法。这两个方法使用相
同的参数,并且都返回Cursor对象。但是managedQuery()方法导致Activity管理Cursor的生命周期。托管的Cursor处理所有的细节,如
当Activity暂停时卸载自身,当Activity重启时加载自身。调用Activity.startManagingCursor()方法可以让Activity管理未托管的Cursor对象
基本用法如下:
Cursor cursor = getContentResolver().query(
Uri, projection, selection, selectionArgs, sortOrder
);
query()方法参数 | 对应SQL部分 | 描述 |
---|---|---|
uri | form table_name | 指定查询某个应用程序下的某一张表 |
projection | select column1, clumn2 | 指定查询的列名 |
selection | where column = value | 指定where的约束条件 |
selectionArgs | - | 为where中的占位符提供具体的值 |
orderBy | oder by column1, column2 | 指定查询结果的排序方式 |
if (cursor != null){
while (cursor.moveToNext()){
String column1 = cursor.getString(cursor.getColumnIndex("column1"));
int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
}
cursor.close();
}
4、插入数据
ContentValues contentValues = new ContentValues();
contentValues.put("nolumn1", "id");
contentValues.put("nolumn2", "name");
getContentResolver().insert(Uri, contentValues);
5、更新数据
使用updata方法清空刚才添加的内容
ContentValues contentValues1 = new ContentValues();
contentValues1.put("nolumn1", " ");
getContentResolver().update(Uri, contentValues1, "column1 = ? and column2 = ?", new String[]{"id", "name"});
6、删除数据
getContentResolver().delete(Uri, "column2 = ?", new String[]{"name"});
7、范例:读取联系人
在模拟器的电话簿中创建两个联系人
编写布局文件,显示查询到的联系人
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/list"/>
</LinearLayout>
编写主活动内容
public class MainActivity extends AppCompatActivity {
ArrayAdapter<String> adapter;
List<String> list = new ArrayList<>();
ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.list);
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, list);
listView.setAdapter(adapter);
//判断是否有读取联系人的权限
if (ContextCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED){
//没有权限则申请权限
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.READ_CONTACTS}, 1);
}else {
//有权限则直接读取联系人
readContacts();
}
}
private void readContacts() {
try {
Cursor cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null, null, null, null);
if (cursor != null){
while (cursor.moveToNext()){
//获取姓名
@SuppressLint("Range") String name = cursor.getString(cursor.getColumnIndex(
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
//获取号码
@SuppressLint("Range") String number = cursor.getString(cursor.getColumnIndex(
ContactsContract.CommonDataKinds.Phone.NUMBER));
list.add(name + ":" + number);
}
}
}catch (Exception e){
Toast.makeText(MainActivity.this, "读取发生错误", Toast.LENGTH_SHORT).show();
}finally {
adapter.notifyDataSetChanged();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode){
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
readContacts();
}else{
Toast.makeText(MainActivity.this, "你拒绝了读取联系人的权限", Toast.LENGTH_SHORT).show();
}
break;
default:
}
}
}
在注册文件中添加权限
<uses-permission android:name="android.permission.READ_CONTACTS"/>
8、创建自己的content provider
8.1、步骤
-
创建一个类继承ContentProvider
-
重写六个方法
- onCreat():初始化内容提供器的时候调用,通常在这里完成数据库的创建和升级
- query():查询
- insert():插入
- updata():更新相应的MIME类型
- delete():删除
- getType():根据传入的URi返回
- 在注册文件中添加content provider
public class myProvider extends ContentProvider {
@Override
public boolean onCreate() {
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
return 0;
}
}
通配符
'*':表示匹配任意长度的任意字符
'#':表示匹配任意长度的数字
content://com.example.app.provider/*
//表示可以匹配com.example.app这个程序中的任意表
content://com.example.app.provider/table/#
//表示可以匹配com.example.app这个程序中的table表中的任意一行数据
UriMatcher类
这个类提供了一个addURI()方法,这个方法接收3个参数,可以分别把authority、path和一个自定义代码传进去。这样,当调用UriMatcher的match()方法时,就可以将一个Uri对象传入,返回值是某个能够匹配这个Uri对象所对应的自定义代码,利用这个代码,我们就可以判断出调用方期望访问的是哪张表中的数据了。
修改myProvider中的代码,如下所示:
public class myProvider extends ContentProvider {
private static final int TABLE1_DIR = 0;
private static final int TABLE1_ITEM = 1;
private static final int TABLE2_DIR = 2;
private static final int TABLE2_ITEM = 3;
private static UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("com.android.app", "table1", TABLE1_DIR);
uriMatcher.addURI("com.android.app", "table1/#", TABLE1_ITEM);
uriMatcher.addURI("com.android.app", "table2", TABLE2_DIR);
uriMatcher.addURI("com.android.app", "table2/#", TABLE2_ITEM);
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
switch (uriMatcher.match(uri)){
case TABLE1_DIR:
//返回表一的所有数据
break;
case TABLE1_ITEM:
//返回表一的单行数据
break;
case TABLE2_DIR:
//返回表二的所有数据
break;
case TABLE2_ITEM:
//返回表二的单行数据
break;
default:
break;
}
}
}
其他insert()、updata()、delete()方法都可以通过这种方法实现
getType()方法。它是所有的内容提供器都必须提供的一个方法,用于获取Uri对象所对应的MME类型。一个内容URI所对应的MIME字符串主要由3部分组成,Android对这3个部分做了如下格式规定:
- 必须以vnd开头。
- 如果内容URI以路径结尾,则后接android.cursor.dir/,如果内容URI以id结尾,则后接android.cursor.item/.
- 最后接上vnd.
.
content://com.example.app.provider/table1
//MIME类型为
vnd.android.cursor.dir/vnd.com.android.app.provider.table1
content://com.example.app.provider/table1/1
//MIME类型为
vnd.android.cursor.item/vnd.com.android.app.provider.table1/1
修改getType()代码
public String getType(@NonNull Uri uri) {
String MIME = null;
switch (uriMatcher.match(uri)){
case TABLE1_DIR:
MIME = "vnd.android.cursor.dir/vnd.com.android.app.provider.table1";
break;
case TABLE1_ITEM:
MIME = "vnd.android.cursor.item/vnd.com.android.app.provider.table1";
break;
case TABLE2_DIR:
MIME = "vnd.android.cursor.dir/vnd.com.android.app.provider.table2";
break;
case TABLE2_ITEM:
MIME = "vnd.android.cursor.item/vnd.com.android.app.provider.table2";
break;
default:
break;
}
return MIME;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了