(学习参考书:第一行代码第2版)

在实际开发中,会遇到将程序中的数据共享给其他程序作为进行二次开发的基础性数据。
内容提供其主要用于在不同程序之间实现数据共享的功能,它提供了一套完整的机制,相比于文件和SharedPreferences存储,内容提供器在实现跨程序数据共享时更加安全可靠。使用内容提供器是Android实现跨程序共享数据的标准方式。

一、Android权限机制

Android将所有权限分为两类:普通权限、危险权限。普通权限是指不会直接威胁到用户的安全和隐私的权限,系统会自动进行授权。危险权限是指可能触及隐私或者对设备安全性造成影响的权限,需要用户手动授权。运行时权限是在用户使用过程中再对某一项危险权限申请进行授权。每当要使用一个权限时,如果是危险权限需要进行运行时权限处理,否则是普通权限在AndroidManifest.xml中添加权限声明即可。
点此查看:Android系统完整权限列表

二、在程序运行时申请权限

操作方法如下:

  1. 判断用户是否已经授权过该权限:使用ContextCompat.checkSelfPermission()方法,该方法接收两个参数第一个是Context,第二个参数是具体的权限名。然后使用该方法的返回值和PackageManager.PERMISSION_GRANTED作比较,相等说明用户已经授权,不等则没有授权。
  2. 如果授权,直接执行逻辑操作;如果没有授权,需要调用ActivityCompat.requestPermissions()方法来向用户申请授权,该方法接收三个参数:第一个是Activity的实例,第二个是String数组,把要申请的权限名放里面,第三个是请求码,只要是唯一值就可以
  3. 调用完第2步的方法后,系统会弹出权限申请的对话框,供用户选择同意或拒绝。不管是哪种结果都会回调onRequestPermissionsResult()方法,而授权的结果会封装在grantResult参数中。这时只需要判断最后的授权结果,就可以决定是否调用逻辑操作方法

三、访问其他程序中的数据

内容提供器一般由两种,一种是使用现有的内容提供器来读取和操作相应程序中的数据,另一种是创建自己的内容提供器给我们程序提供外部访问接口。如果一个应用程序通过内容提供器对其数据提供了外部访问接口,那么任何其他程序都可以对这部分数进行访问。
内容提供器类ContentResovler的基本用法
ContentResovler类可以通过Context中的getContentResolver()方法获取实例。ContentResovler类中提供了一系列的方法对数据进行增删改查操作。与SQLiteDatabase类似:insert()添加数据、updata()更新数据、delete()删除数据、query()查询数据。但在方法参数上有一定的区别。
ContentResovler不同于SQLiteDatabase,方法都不接受表名参数,而是使用一个Uri参数代替,这个参数被称为内容URI。内容URI给内容提供器中的数据建立了唯一标识符,由两部分组成:

authority:用于对不同应用程序做区分,为了避免冲突,都会采用程序包名的方法来命名。如:com.example.text.provider
path:用于对同一应用程序不同的表做区分,通常 添加到authority的后面。如:com.example.text.provider/table1
综上所述,完整的内容URI标准格式为:content://com.example.test.provider/table1

在得到内容URI字符串后,还需要将它解析为Uri对象才可以作为参数传入,解析方法为调用Uri类的parse()方法,如下:

Uri uri = Uri.parse(“content://com.example.test.provider/table1”)

剩下的操作与SQLiteDatabase类似,增改使用ContentValues对象,删除直接让ContentResovler实例调用delete()方法,查询使用Cursor对象。

四、创建内容提供器

通过新建一个类继承ContentProvider的方式创建一个自定义的内容提供器。ContentProvider中有六个抽象方法,继承时,需要将这6个方法全部重写

public class MyProvider extends ContentProvider {
    public boolean onCreate() {
        return false;
    }

    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        return null;
    }

    public String getType(Uri uri) {
        return null;
    }

    public Uri insert(Uri uri, ContentValues values) {
        return null;
    }

    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        return 0;
    }
}

// onCreate() 初始化内容提供器调用,通常在这里完成数据库的创建和升级
// getType() 根据传入的URI返回相应的MIME类型

URI解析
由上面可知,一个标准的URI格式为:

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

除此之外,还可以在其后加上一个id:

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

表示调用方期望访问的是table1表中id为1的数据。
如此一来,URI格式有上述两种,以路径结尾表示期望访问表中所有的数据,以id结尾表示期望访问表中拥有相应id的数据。可以使用通配符来匹配两种格式的URI:

* 表示匹配任意长度的任意字符
# 表示匹配任意长度的数字

故一个匹配任意任意表的URI为:

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

匹配table1表的中任意一行数据的URI为:

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

借助UriMatcher类可以轻松实现匹配内容URI的功能,UriMatcher提供了一个addURI()方法,该方法接收三个参数,可以分别把authority、path、自定义代码传进去。这样在调用UriMacther的match()方法时,就可以将一个Uri对象传入,返回值是某个能匹配这个Uri对象的自定义代码,利用这个代码就可以判断调用方期望访问的是哪张表了。
URI对象对应的MIME类型:一个URI的MIME包含三个部分:

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