创建Content Provider

来源:互联网 发布:教师培训网络课程平台 编辑:程序博客网 时间:2024/05/01 10:26

1、创建Content Provider前的准备

(1)决定你是否需要Content Provider,如果你需要提供以下功能,则需要Content Provider

   1)你想为其他应用程序提供复杂数据

   2)你想允许用户将复杂的数据从你的应用复制到其他应用中

   3)你想使用搜索框架提供自定义搜索建议

   如果完全是在自己的应用中使用,则不需要使用Content Provider

2、接下来,按照以下步骤开发你的Content Provider

(1)为你的数据设计原始储存。Content Provider以两种方式提供数据:

    1)文件数据:通常存储在文件中的数据,如照片视频等,将文件存储在你的应用私有空间中,你的Content Provider可以应其他应用程序发出的文件请求提供文件句柄

    2)结构化数据:通常存储在数据库、数组或者类似结构中的数据。以兼容行列表的形式存储数据。行表示实体,如人员或库存项目,列表示实体的某项数据,如人员姓名或者商品价格。此类数据通常存储在sqlite数据库中,但你可以使用任何形式的持久储存。

 (2)定义Content Provider类及其所需方法

 (3)定义提供程序的autuority字符串、其内容URI以及列名称,如果你想要Content Provider的应用处理Intent,还要定义Intent操作、Extra数据以及flag。此外,还要定义想要访问你的数据的应用必须具备的autuority,你应该考虑在一个单独的协定类中将这些定义成常量,此后可以将该类公开给其他开发者

 (4)添加其他可选部分,如示例数据或可以在Content Provider与云数据之间同步数据的的AbstractThreadedSyncAdapter


3、设计数据储存

Content Provider是用于访问以结构化格式保存的数据的界面,在你创建该界面之前,必须决定如何储存数据,你可以按照自己的喜好以任何形式储存数据,然后根据需要设计读写数据的界面。

(1)以下是Android中提供的一些数据储存技术:

    1)Android系统包括一个SQLite数据库API,Android自己的Content Provider用他来存储面向表的数据,SQLiteOpenHelper类可帮助你创建数据库,SQLiteDatabase是访问数据库的基类。

请记住,你不必使用数据库来实现储存库,Content Provider在外部表现为一组表,与关系数据库类似,但这并不是对Content Provider内部实现的要求。

    2)对于储存文件数据,Android提供了各种面向文件的API。

    3)要想使用基于网络的数据,请使用java.net和android.net中的类,你也可以将基于网络的数据与本地数据存储同步,然后以表或文件的形式提供数据。

(2)数据设计考虑事项:以下是一些设计提供程序数据结构的技巧

    1)表数据应该始终有一个主键列,Content Provider将其作为每行对应的唯一数值加以维护。你可以使用此值将该行链接到其他表的相关行中(将其作为外键)。尽管你可以为此列使用任意名称,但使用BaseColumns._ID是最佳选择,因为将检索结果链接到ListView的条件是其中一个列的名称是_ID。

    2)如果你想提供位图图像或者其他非常庞大的文件导向型数据,请将数据存储在一个文件中,然后间接提供这些数据,而不是直接将其储存在表里,如果你执行了此操作,则需要提前告知用户,他们需要使用ContentResolver来访问数据。

    3)使用二进制大型对象(BLOB)数据类型存储大小或结构会发生变化的数据。例如,你可以使用BLOB来存储协议缓冲区和JSON结构,你也可以使用BLOB实现独立于架构的表,在这类表中,你需要以BLOB的形式定义一个主键列、一个MIME类型以及一个或者多个通用列。这些BLOB列中数据的含义通过MIME类型中的值指示。这样一来,你就可以在同一个表中存储不同类型的行。举例来说,联系人Content Provider的数据表ContractContracts.data便是一个独立于架构的表。


4、设计Content URI

Content URI是用于在Content Provider中标识数据的URI。内容URI包括整个Content Provider的符号名称(authority)和一个指向表或文件的名称(path)。可选ID部分指向表中的单个行。Content Provider的每一个数据访问内容都将Content URI作为参数,你可以利用这一点确定要访问的表、行或文件。


5、设计authority

Content Provider通常只有一个authority,该authority充当其Android内部名称。为避免与其他应用程序发生冲突,你应当使用Internet域所有权(反向)作为Content Provider的基础。由于此建议也适用于Android软件包名称,因此你可以将Content Provider的antuority定义为包含该Content Provider的软件包名称的扩展名。例如,你的Android软件包名称为com.example.<appname>,则应该为Content Provider的authority设置为com.example.<appname>.provider


6、设计path

开发者通常通过追加指向单个表的path来根据authority来创建Content URI。例如,你有两个表,table1和table2,则可以通过合并上一示例中的autuority来生成Content URI,com.example.<appname>.provider/table1,path不限定于单个段,也无需为每一级路径都创建一个表。

  //创建一个UriMatcher对象    private static final UriMatcher sUriMatcher = null;    /*    在这里调用addUri添加所有provider应该识别的Content URI模式,    在这个片段,只显示了表3的     */    static {        sUriMatcher.addURI("com.example.app.provider", "table3", 1);     /*     设置单个行数的代码为2,在这种情况下使用通配符#,     content://com.example.app.provider/table3/3是匹配的     content://com.example.app.provider/table3是不匹配的      */        sUriMatcher.addURI("com.example.app.provider", "table3/#", 2);    }    @Override    public boolean onCreate() {        return false;    }    @Nullable    @Override    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {        //选择表并查询和排序基于代码返回的uri        switch (sUriMatcher.match(uri)) {    //如果传入的URI是对于整个table3表的            case 1:                if (TextUtils.isEmpty(sortOrder));                break;            case 2:                //因为这是URI表示的是一行,_ID是当前有价值的一部分,从URI最后一部分提取这个值,就是_ID值                selection = selection + "_ID = " +uri.getLastPathSegment();                default:                    //如果没有识别在这里进行处理        }        //在这里执行查询        return null;            }


7、处理Content URI ID

按照惯例,Content Provider通过接收末尾具有行所对应的ID值来提供对表中单个行的访问。同样按照惯例,Content Provider会将该ID值与表的_ID进行匹配,并对匹配的行执行请求的访问。

这一惯例为访问Content Provider的应用的常见设计模式提供了便利。应用会对Content Provider执行查询,并使用CursorAdapter以ListView显示生成的Cursor,定义CursorAdapter的条件是Cursor的其中一列必须是_ID。

用户随后从从UI上显示的行中选取一行,以查看或修改数据,应用会从支持ListView的Cursor获取对应行,获取改行的_ID值,将其追加到Content URI,然后向Content Provider发送访问请求,然后Content Provider便可对用户选取的特定行执行查询或者修改。


8、Content URI模式

为帮助你选择对传入的Content URI进行操作,Content URI的API加入实用类UriMatcher,他会将Content URI模式映射到整形值,你可以在一个switch语句中使用这些整形值,为匹配特定模式的一个或者多个Content URI选择所需操作。

Content URI模式使用通配符匹配Content URI:

1)*:匹配任意长度的任何有效字符组成的字符串

2)#:匹配由任意长度的数字字符组成的字符串

以设计和编码内容URI处理为例,假设一个具有autuoritycom.example.app.provider的Content Provider识别以下指向表的Content URI:

3)content://com.example.app.provider/table1:一个名为table1的表

4)content://com.example.app.provide/table2/1:table2的表中ID为1的行

5)content://com.example.app.provider/*:匹配Content Provider中的任何Content URI

6)content://com.example.app.provider/table/#:匹配所有table所有行的Content URI

以下代码段说明了UriMatcher中方法的工作方式,此代码采用不同方式处理整个表的URI与单个行的URI,它为表使用的Content URI是content://<authority>/<path>,为单个行使用的Content URI则是content://<authoruty>/<path>/<id>。

方法addUri()会将authority和path映射到一个整形值,方法match()会返回URI的整形值。switch语句会在查询整个表与查询单个记录之间进行选择。

    //创建一个UriMatcher对象    private static final UriMatcher sUriMatcher = null;    /*    在这里调用addUri添加所有provider应该识别的Content URI模式,    在这个片段,只显示了表3的     */    static {        sUriMatcher.addURI("com.example.app.provider", "table3", 1);     /*     设置单个行数的代码为2,在这种情况下使用通配符#,     content://com.example.app.provider/table3/3是匹配的     content://com.example.app.provider/table3是不匹配的      */        sUriMatcher.addURI("com.example.app.provider", "table3/#", 2);    }    @Override    public boolean onCreate() {        return false;    }    @Nullable    @Override    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {        //选择表并查询和排序基于代码返回的uri        switch (sUriMatcher.match(uri)) {    //如果传入的URI是对于整个table3表的            case 1:                if (TextUtils.isEmpty(sortOrder));                break;            case 2:                //因为这是URI表示的是一行,_ID是当前有价值的一部分,从URI最后一部分提取这个值,就是_ID值                selection = selection + "_ID = " +uri.getLastPathSegment();                default:                    //如果没有识别在这里进行处理        }        //在这里执行查询        return null;    }


9、实现Content Provider类

Content Provider实例通过处理来自其他应用的请求来管理对结构化数据集的访问,所有形式的访问最终都会调用ContentResolver,后者接着调用Content Provider的具体方法来获取访问权限。


(1)必须方法

抽象类Content Provider定义了六个抽象方法,你必须将这些方法作为自己具体子类的实现,所有的这些方法都由一个尝试访问你客户端调用

1)query()

2)insert()

3)update()

4)delete()

5)getType():返回Content URI对应的MIME类型

6)onCreate()

你在实现这些方法的时候需要注意以下事项:

1)所有这些方法(onCreate()除外)都可以由多个线程同时调用,因此他们必须是线程安全方法

2)避免在onCreate()中执行长时间操作,将初始化任务推迟到实际需要时进行

3)尽管你必须实现这些方法,但你可以为了防止别人插入数据而让insert返回0


(2)实现query()方法

Content Provider.quert()方法必须返回cursor对象,如果失败,则会引发异常,如果你使用SQLite数据库作为数据储存,则只需返回由SQLiteDatabase类其中一个query()方法返回的cursor。如果查询不匹配任何行,你应该返回一个cursor实例(其getcount等于0)。只有查询过程内部出错才能返回null。


(3)实现insert方法

insert()会使用ContentValues参数中的值向相应表中添加新行,如果ContentValue参数为包含列名称,你应该在你的Content Provider提供默认值

此方法应该返回新行的Content URI,要想构建此方法,请使用withAppendId()向表的Content URI追加新行的_ID(或者其他主键值)


(4)实现onCreate()方法

Android系统会在启动Content Provider时调用onCreate(),你只应在此方法中执行快速的初始化任务,并将数据库创建和数据库加载推迟到Content Provider实际收到数据请求时进行。如果你在onCreate()中执行长时间的任务,则会减慢Content Provider的启动速度。

在onCreate()中

 mOpenHelper = new MainDatabaseHelper(            getContext(),        // the application context            DBNAME,              // the name of the database)            null,                // uses the default SQLite cursor            1                    // the version number        );
在实际使用时

 db = mOpenHelper.getWritableDatabase();

10、实现ContentProvider MIME类型

Content Provider类有两个返回MIME类型的方法

getType():你必须为任何Content Provider实现的必须方法之一

getStremType():系统在为你的Content Provider提供文件时需要的方法


11、表的MIME类型

getType()方法会返回一个MIME格式的String,后者描述Content URI参数返回的数据类型,Uri参数可以是模式,而不是特定Uri,在这种情况下,你应该返回与匹配该模式的Content URI关联的数据类型。

对于文本、jpg等常见数据类型,可在网站查询

对于指向一个或多个表数据行的Content URI,getType()应该返回固定格式的MIME类型:

类型部分:vnd

子类型部分:

单行URI:android.cursor.item/

多行URI:android.cursor.dir/

Content Provider特有部分:vnd.<name>.<type>

你提供的name和type应该具有全局唯一性,type值应在对应的URI模式中具有唯一性,适合选择贵公司的名称或者说你应用名称的Android软件包名称的某个部分作为name,适合选择URI关联表的标识字符串作为type

例如,你的Content Provider的anthority是com.example.app.provider,并且公开了一个名为table1的表,那么table1中多个行的MIME类型是:

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


12、文件的MIME类型

如果你的Content Provider提供文件,请实现getStreamType(),你应该通过MIME类型过滤器参数过滤你提供的MIME类型,以便只返回客户端想处理的MIME类型。

例如,假设Content Provider以.jpg,.png和.gif格式文件形式提供照片图像,如果应用调用ContentResolver.getStream.getStreamTypes()时使用了过滤器字符串image/*(任何是图像的内容),那么Content Provder.getStreamTypes()方法应该返回数组:image/jpeg,impage/png,image/gif

如果只对.jpg感兴趣,则可以在调用ContentResolver.getStream.getStreamTypes()时使用了过滤器字符串*\/jpeg,那么应该只返回image/jpeg

如果你的Content Provider为提供过滤字符串请求的任何MIME类型,则应该返回null


13、实现Content Provider权限

要点如下:

1)默认情况下,储存在设备内部储存上的数据文件是你的应用和Content Provider的私有数据文件

2)你创建的SQLiteDatabase数据库是你的应用和Content Provider的私有数据库

3)默认情况下,你保存到外部储存的的 数据文件是公用并全局可读写的数据文件,你无法使用Content Provider来限制对外部文件的访问,因为其他应用可以调用其他API来执行读写操作

4)用于在你的设备的内部储存上打开或创建文件或SQLite数据库的方法调用可能会为所有的其他应用同时赋予读取和写入权限,如果你将内部文件或数据库用作Content Provider的储存库,并向其授予了可全局读写的访问权限,则你在清单文件中为Content Provider设置的权限不会保护你的数据,内部储存文件和数据库默认的访问权限是私有,对于Content Provider,你不应该更改此权限。

如果你想使用Content Provider权限来控制对数据的访问,则应将数据储存在内部文件、SQLite或者远程服务器上,而且你应该保持文件和数据库为你的应用所私有。


14、实现权限

即使底层数据为私有数据,所有应用仍然可从你的Content Provider读取或写入数据,因为在默认情况下,你的Content Provider未设置权限,要想改变这种情况,请使用属性或者<provider>的子元素在你的清单文件中设置权限。你可以设置适用于整个Content Provider或者特定表甚至特定记录的权限,或者设置同时适用于三者的权限。

你可以通过清单文件中的一个或者多个permission元素为你的Content Provider定义权限。要使权限对你的Content Provider有唯一性,请问android:name属性使用Java风格作用域,例如,将读取权限命名为com.example.app.provider.permission.READ_PROVIDER。

以下列表描述了Content Provider权限的作用域,从适用于整个程序的权限开始,然后逐渐细化,更细化的权限优先于作用域大的权限。

(1)统一读写Content Provider级别权限

一个同时控制整个Content Provider的读写权限,通过<provider>元素的android:permission设置

(2)单独的读写权限

你可以通过<provider>元素中的android:readPermission和android:writePermission

(3)路径级别的权限

针对Content Provier的读写权限,你可以通过path-permission指定你想控制的每个URI,对于你指定的每个Content URI,你都可以指定读取/写入权限。

(4)临时权限

一种权限级别,即使应用不具备所需权限,该级别也能授予对应用的访问权限,临时访问功能可减少应用需要在其清单文件中的权限数量。当你启动临时权限时,只有持续访问所有数据的应用才需要永久性的程序访问权限。

假设你需要实现电子邮件Content Provider的权限,为了在不请求权限的情况下为查看器提供必要的访问权限,可以为照片的Content URI设置权限,其中包含照片的Content URI以及权限标志。

要想启用临时权限,请设置android:grantUriPermission属性,或者向你的<provider>添加一个或者多个<grant-uri-permission>子元素,如果你使用了临时权限,则每当从Content Provider中移除某个Content URI的支持,并且该Content URI关联了临时权限时,都需要调用Context.revokeUriPermission()。

该属性的值决定可访问的Content Provider范围,如果该属性设置为true,则系统会向整个Content Provider授予临时权限,该权限将替代你的Content Provider级别或者路径级别权限所需的任何其他权限。

如果此标志设置为false,则你必须添加<grant-uri-permission>元素,每个元素都指定授予的临时权限所对应的一个或多个Content URI。

要想应用程序申请临时应用权限,Intent必须包含FLAG_GRANT_READ_URI_PERMINNION或者FLAG_GRANT_WRITE_URI_PERMISSION,通过setFlag()设置。

若android:grantUriPermission属性不存在,则假设其为false。


15、<provider>元素

(1)android:authorities:权限,用于在系统内标识整个Content Provider的符号名称。

(2)android:name:实现Content Provider的类名

(3)权限:

     1)android:grantUriPermission:临时权限标志

     2)android:permission:统一提供程序的读写权限

     3)android:read/wiritePermission:提供读/写权限  

(4)启动和控制属性

    1)android:enable:允许系统启动的标志

    2)android:exported:允许其他引用使用本程序

    3)android:initOrder:此Content Provider相对于同一进程中其他Content Provider的启动顺序

    4)android:multiProcess:允许系统在调用客户端相同的进程启动Content Provider的标志

    5)android:process:应在其中运行Content Provider的进程的名称

    6)android:syncable:指示Content Provider的数据将于服务器同步


16、Intent和数据访问

应用可以通过Intent间接访问应用程序。应用不会ContentProvider和ContentResovler的任何方法,而是会发送一个启动Activity的Intent,该Activity通常是Content Provider的一部分,目标Activity负责检索和显示其UI中的数据。视Intent中的操作而定,目标Activity可能还会提示用户对Content Provider的数据进行修改,Intent还可能包含目标Activity在UI中显示的extra数据,用户随后可以选择更改这些数据。



0 0
原创粉丝点击