Android 四大组件之 ContentProvider

作者: kevenZheng | 来源:发表于2019-04-08 23:28 被阅读70次

读前思考

学习一门技术或者看一篇文章最好的方式就是带着问题去学习,这样才能在过程中有茅塞顿开、灯火阑珊的感觉,记忆也会更深刻。

  1. ContentProvider 是什么?
  2. ContentProvider 如何使用?
  3. ContentProvider 和其他通信方式比有什么区别与优点?
  4. 什么是 URI ?怎么书写?有什么含义?

认识 URI

通用资源标志符(Universal Resource Identifier, 简称 "URI")
Uri 代表要操作的数据,Android 上可用的每种资源、图像、视频片段等都可以用 Uri 来表示。Uri 唯一标识每种资源。
Uri一般由三部分组成: 1.访问资源的命名机制。 2.存放资源的主机名。 3.资源自身的名称,由路径表示。
android 的 Uri 由以下三部分组成:
"content://"、数据的路径、标示 ID(可选)

一个标准的内容 URI 写法是这样的:

content://com.example.app.provider/table1

//这就表示调用方期望访问的是 com.example.app 这个应用的 table1 表中的数据。

除此之外,我们还可以在这个内容 URI 的后面加上一个 id,如下所示:

content://com.example.app.provider/table1/1

//这就表示调用方期望访问的是 com.example.app 这个应用的 table1 表中 id 为 1 的数据。

内容 URI 的格式主要就只有以上两种,以路径结尾就表示期望访问该表中所有的数据,以 id 结尾就表示期望访问该表中拥有相应 id 的数据。
我们可以使用通配符的方式来分别匹 配这两种格式的内容 URI,规则如下。
1.*表示匹配任意长度的任意字符
2.#: 表示匹配任意长度的数字

所以,一个能够匹配任意表的内容 URI 格式就可以写成:

content://com.example.app.provider/*

而一个能够匹配 table1 表中任意一行数据的内容 URI 格式就可以写成:

content://com.example.app.provider/table1/#

什么是 ContentProvider ?

作为四大组件之一,ContentProvider 主要负责存储和共享数据。与文件存储、SharedPreferences 存储、SQLite 数据库存储这几种数据存储方法不同的是,后几者保存下的数据只能被该应用程序使用,而前者可以让不同应用程序之间进行数据共享,它还可以选择只对哪一部分数据进行共享,从而保证程序中的隐私数据不会有泄漏风险。

ContentProvider 有两种形式

  1. 可以使用现有的内容提供者来读取和操作相应程序中的数据。
  2. 也可以创建自己的内容提供者给这个程序的数据提供外部访问接口。

从系统提供的 Provider 访问数据

例子:读取联系人的电话

1.清单文件中添加读取权限

<uses-permission android:name="android.permission.READ_CONTACTS" />
  1. 针对 6.0+ 系统动态权限申请
//判断是否有读取联系人权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
    ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, 1);
} else {
    readContacts();
}
  1. 重写 onRequestPermissionsResult( ) 方法获取权限申请返回
@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(this, "获取权限失败!", Toast.LENGTH_SHORT);
            }
            break;
        default:
            break;
        }
    }
  1. 获取手机通讯录
private void readContacts() {
    List contactsList=null;
    Cursor cursor=null;
    try {
        contactsList=new ArrayList();
        //查询联系人数据,使用了getContentResolver().query方法来查询系统的联系人的数据
        //CONTENT_URI就是一个封装好的Uri,是已经解析过得常量
        cursor=getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null);
        //对cursor进行遍历,取出姓名和电话号码
        if (cursor!=null){
            while (cursor.moveToNext()){
                //获取联系人姓名
                String displayName=cursor.getString(cursor.getColumnIndex(
                    ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
                    ));
                //获取联系人手机号
                String number=cursor.getString(cursor.getColumnIndex(
                    ContactsContract.CommonDataKinds.Phone.NUMBER
                    ));
                //把取出的两类数据进行拼接,中间加换行符,然后添加到listview中
                contactsList.add(displayName+"\n"+number);
                LogUtils.i("姓名:"+displayName+"\n"+"电话:"+number);
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //记得关掉cursor
            if (cursor!=null){
                cursor.close();
            }
        }
    }
  1. 最后的结果输出
com.keven.jianshu I/TAG: 姓名:孙**
    电话:+86157****429
com.keven.jianshu I/TAG: 姓名:井**
    电话:+861836****299
com.keven.jianshu I/TAG: 姓名:张**
    电话:+861866****830
com.keven.jianshu I/TAG: 姓名:钉**
    电话:0105****898
com.keven.jianshu I/TAG: 姓名:彩**
    电话:0108****604

创建自己的 Provider

  1. 自定义类继承 ContentProvider,重写六个方法
public class Part1dMyProvider extends ContentProvider {
    /**
     * 初始化内容提供器的时候调用。通常会在这里完成对数据库的创建和升级等操作,
     * 返回 true 表示内容提供器初始化成功,返回 false 则表示失败。注意,只有
     * 当存在 ContentResolver 尝试访问我们程序中的数据时,内容提供器才会被初始化。
     */
    @Override
    public boolean onCreate() {
        return false;
    }
    /**
     * 从内容提供器中查询数据。使用 uri 参数来确定查询哪张表,projection 参数用
     * 于确 定查询哪些列,selection 和 selectionArgs 参数用于约束查询哪些行,
     * sortOrder 参数用于 对结果进行排序,查询的结果存放在 Cursor 对象中返回。
     */
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        return null;
    }
    /**
     * 向内容提供器中添加一条数据。使用 uri 参数来确定要添加到的表,待添加的数据
     * 保存在 values 参数中。添加完成后,返回一个用于表示这条新记录的 URI。
     */
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        return null;
    }
    /**
     * 更新内容提供器中已有的数据。使用 uri 参数来确定更新哪一张表中的数据,新数
     * 据保存在 values 参数中,selection 和 selectionArgs 参数用于约束更新哪些行,
     * 受影响的 行数将作为返回值返回。
     */
    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        return 0;
    }
    /**
     * 从内容提供器中删除数据。使用 uri 参数来确定删除哪一张表中的数据,selection
     * 和 selectionArgs 参数用于约束删除哪些行,被删除的行数将作为返回值返回。
     */
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }
    /**
     * 根据传入的内容 URI 来返回相应的 MIME 类型。 可以看到,几乎每一个方法都会
     * 带有 Uri 这个参数,这个参数也正是调用 ContentResolver的增删改查方法时传
     * 递过来的。而现在,我们需要对传入的 Uri 参数进行解析,从中分析出 调用方
     * 期望访问的表和数据。
     */
    @Override
    public String getType(Uri uri) {
        return null;
    }
}
  1. 使用 UriMatcher
public class Part1dMyProvider extends ContentProvider {
    public static final int TABLE1_DIR = 0;

    public static final int TABLE1_ITEM = 1;

    public static final int TABLE2_DIR = 2;

    public static final int TABLE2_ITEM = 3;

    private static UriMatcher uriMatcher; static {

        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

        uriMatcher.addURI("com.keven.jianshu.provider", "table1", TABLE1_DIR);

        uriMatcher.addURI("com.keven.jianshu.provider ", "table1/#", TABLE1_ITEM);

        uriMatcher.addURI("com.keven.jianshu.provider ", "table2", TABLE2_ITEM);

        uriMatcher.addURI("com.keven.jianshu.provider ", "table2/#", TABLE2_ITEM);

    }
    ......
}
  1. 以 query( ) 方法为例示范(insert()、update()、delete() 实现类似)
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
switch (uriMatcher.match(uri)) {
    case TABLE1_DIR:
    // 查询table1表中的所有数据
        break;

    case TABLE1_ITEM:
    // 查询table1表中的单条数据
        break;

    case TABLE2_DIR:
    // 查询table2表中的所有数据
        break;

    case TABLE2_ITEM:
    // 查询table2表中的单条数据
        break;

    default:
        break;
    }
    ……
}
……
}

除此之外,还有一个方法你会比较陌生,即 getType() 方法。它是所有的内容提供器都必 须提供的一个方法,用于获取 Uri 对象所对应的 MIME 类型。一个内容 URI 所对应的 MIME 字符串主要由三部分组分,Android 对这三个部分做了如下格式规定。

  1. 必须以 vnd 开头。
  2. 如果内容 URI 以路径结尾,则后接 android.cursor.dir/,如果内容 URI 以 id 结尾, 则后接 android.cursor.item/。
  3. 最后接上 vnd.<authority>.<path>。

所以,对于 content://com.example.app.provider/table1 这个内容 URI,它所对应的 MIME
类型就可以写成:

vnd.android.cursor.dir/vnd.com.example.app.provider.table1

对于 content://com.example.app.provider/table1/1 这个内容 URI,它所对应的 MIME 类型 就可以写成:

vnd.android.cursor.item/vnd.com.example.app.provider.table1

则我们的自定义 Provider 可以完善为

@Override

public String getType(Uri uri) { 

switch (uriMatcher.match(uri)) { 

    case TABLE1_DIR:
        return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1";

    case TABLE1_ITEM:
        return "vnd.android.cursor.item/vnd.com.example.app.provider.table1";

    case TABLE2_DIR:
        return "vnd.android.cursor.dir/vnd.com.example.app.provider.table2";

    case TABLE2_ITEM:
        return "vnd.android.cursor.item/vnd.com.example.app.provider.table2";

    default:
        break;
}
return null;
}

到这里,一个完整的内容提供器就创建完成了,现在任何一个应用程序都可以使用 ContentResolver 来访问我们程序中的数据。

文章已经读到末尾了,不知道最初的几个问题你都会了吗?如果不会的话?可以再针对不会的问题进行精读哦!答案都在文中,相信你肯定可以解决的!

相关文章

网友评论

    本文标题:Android 四大组件之 ContentProvider

    本文链接:https://www.haomeiwen.com/subject/teomiqtx.html