四大组件之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常量。例如,匹配电话号码到联系人表格的

image-20220510172148193

A:标准的前缀,用于标识该数据由Content Provider管理,不需修改。

B:URI的authority部分,用于标识该Content Provider。对于第三方应用,该部分应该是完整的类名(使用小写形式)来保证唯一性。在:元素的authorities属性中声明authority.

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中的数据,需要以下三点:

  1. 标识该Content Provider的URL.
  2. 需要查询的数据字段名称。
  3. 字段中数据的类型。

如果需要查询特定的记录,那么还需要提供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、步骤

  1. 创建一个类继承ContentProvider

  2. 重写六个方法

    • onCreat():初始化内容提供器的时候调用,通常在这里完成数据库的创建和升级
    • query():查询
    • insert():插入
    • updata():更新相应的MIME类型
  • delete():删除
    • getType():根据传入的URi返回
  1. 在注册文件中添加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个部分做了如下格式规定:

  1. 必须以vnd开头。
  2. 如果内容URI以路径结尾,则后接android.cursor.dir/,如果内容URI以id结尾,则后接android.cursor.item/.
  3. 最后接上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;
    }
posted @   凉菜花生  阅读(254)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示