内容提供者---进阶

原文译自:http://developer.android.com/intl/zh-CN/guide/topics/providers/content-provider-creating.html


内容目录



  1. 设计数据存储
  2. 设计内容唯一资源标识符(URIs)
  3. 实现ContentProvider类
    1. 所需的方法
    2. 实现query()方法
    3. 实现insert()方法
    4. 实现delete()方法
    5. 实现update()方法
    6. 实现onCreate()方法
  4. 实现内容提供者MIME类型
    1. 表的MIME类型
    2. 文件的MIME类型
  5. 实现契约类
  6. 实现内容提供者权限
  7. <provider>元素
  8. 意图和数据访问




创建内容提供者


内容提供者管理着中央数据的访问。伴随着清单文件内的元素,实现提供者作为android应用里的一个或多个类。实

现的众多类中的一个作为 ContentProvider的子类,它是提供者和其他应用间的接口。尽管内容提供者意图让数据对

其他的应用程序可用,当然,你可以让应用中的活动来允许用户查询和修改由提供者管理的数据。


该主题的其余部分是构建内容提供者的步骤及可使用的API的列表。


开始构建前


开始构建提供者之前,执行如下操作:


     1. 决定是否需要内容提供者。如果需要提供如下特性中的一个或多个时,就需要构建内容提供者:

  •     想要为其他应用通过复杂的数据或文件。
  •     打算允许用户从你的应用里复制复杂数据到其他的应用中。
  •      打算使用搜索矿建提供自定义的搜索建议。

     如果完全是在自己的应用内使用,提供者就不必使用SQLite数据库。


     2. 如果你还没有这么做, 请阅读文章内容提供者---基础来学习更多关于提供者的内容。



接着,按照如下步骤构建提供者:


1.  设计数据的原始存储。内容提供器通过两种方式提供数据:


文件数据

指通常进入文件内的数据,譬如照片,音频或视频等。把这些文件存储在应用的私有空间里。为回应来自其他应用的文件请求,提供者能够提供对文件的处理。


"结构式"数据

指通常进入数据库的数据,如数组或类似的结构。通过与表的行和列兼容的形式来存储数据。一行就代表一个实体,比如,一个人或商品清单里的一项。列代表实体的某些数据 ,比如,人的名字或者商品的价格。存储此类数据的通常方法是通过SQLite数据库,不过你可以任何类型的持久性存储。为了解更多Android系统内可用的存储类型 ,请参阅设计数据存储章节。


2.  定义ContentProvider 类和它的必备方法的具体实现。该类是你的数据和Android系统其余部分间的接口。更多关于该类的信息, 请参阅实现ContentProvider类章节。


3.  定义提供者权限字符串,它的内容URIs及列名。如果打算让提供者应用处理意图,则还要定义意图动作,附加数据及标记。还要定义那些想要访问你的数据的其他应用所需要的权限。应该考虑把所有作为常量的值定义在一个单独的契约类里; 然后, 把这个类公开给其他的开发者。更多关于内容URIs的信息,请参阅设计内容唯一资源标识符(URIs)章节。更多有关意图的信息,参阅意图和数据访问章节。


4.  添加其他的可选件, 比如简单的数据或是AbstractThreadedSyncAdapter类的实现,它能够在提供者和基于云计算的数据间同步。




1.  设计数据存储



内容提供者是按结构化的格式保存的数据接口。创建该接口之前,必须决定如何存储数据。可以用任何喜欢的形式存储数据,然后根据需要设计读和写的接口。


下面是一些Android上可用的数据存储技术:


  • Andrid系统包括SQLite数据库API,系统自己的提供者使用它来存储面向表的数据。SQLiteOpenHelper类可以帮助我们创建数据库,而SQLiteDatabase类则是访问数据库的基类。

记住,没必要一定要使用数据库来实现数据存储。提供者在表面上呈现为表的集合,和关系数据库相似,但是,但这不是提供者内部实现要求。

  • 对于存储文件数据来说,Android拥有多种面向文件的API 。更多关于文件存储的内容,参阅 主题数据存储。如果你正在设计提供者,它可以提供媒体相关数据,比如音乐或视频,则可以使提供者整合表数据和文件。
  • 对基于网络的数据来说,使用java.netandroid.net里的类。还能把基于网络的数据同步到本地的数据存储,比如数据库,接着提供表或文件数据。Sample Sync Adapter示例应用演示了这种类型的同步。



1.1 数据设计注意事项



这有一些设计提供者数据结构的提示:


  • 表数据通常应该有一个“主键”列,提供者维护其作为每行的唯一的数字列。可以使用这个值把该行与其他表(使用该值作为“外部键”)内的相关行关联起来。尽管可以为这一列使用任何名字,但使用BaseColumns._ID是最好的选择,因为把提供者查询结果关联到ListView时,需要被返回的列中的一列名为_ID。


  • 如果想要提供位图图像或其他很大的面向文件的数据块,则把数据存储在文件中,然后间接提供它而非直接地把它存储在表中。如果这样做,需要告诉提供者的使用者,他们需要使用ContentResolver文件方法来访问数据。


  • 使用二进制大对象 (BLOB)数据类型来存储大小不定或结构不同的数据。例如,可以使用BLOB类型列来存储协议缓冲区(protocol buffer) 或 JSON结构(JSON structure)


你还能用二进制大对象来实现独立模式表。在这类表中,定义一个主键列,一个MIME类型列及一个或多个通用的BLOB列。BLOB列内数据的含义由MIME类型列内的值来指明。这就允许你在相同的表内存储不同的行类型。联系人提供者的“data”表ContactsContract.Data就是一个独立模式表的例子。




2.  设计内容唯一资源标示符(URIs)



内容唯一资源标示符是识别提供者数据的唯一资源标示符。内容URIs包括了整个提供者的符号名(它的授权)和一个指向表或文件的名字(路径)。可选的id部分指向表内一单独行。ContentProvider的每个数据访问方法都有一个内容URI的参数;这允许你来决定要访问的表,行或者文件。


内容URIs的基础知识在主题 内容提供者基础里有描述。


2.1 设计授权

提供者通常有一个单独的授权,它作为Android内部名称。为了避免与其他的提供者存在冲突,应该使用互联网域名所有权(反序的)作为提供者授权的基础。因为这一建议对Android包名也是如此, 你可以把提供者的授权定义成包含该提供者包名的扩展。例如,如果Android包名是com.example.<appname>,你应该把授权com.example.<appname>.provider赋给提供者。


2.2 设计路径结构

开发人员一般通过在授权后附加指向单独表的路径来创建内容URIs。例如,如果有两个表table1和table2,结合先前实例的授权进而产生内容URIscom.example.<appname>.provider/table1和com.example.<appname>.provider/table2。路径不限于一个分段,并且路径的每个层级也不必存有表。


2.3 处理内容唯一资源标示符IDs

按照约定,提供者提供对表内的单行访问是依据接受尾部带有行ID值的内容URI。同样是根据约定, 提供者把ID值与表的_ID列进行匹配,并执行对与之匹配的行的请求访问。


这个约定催生了应用程序访问提供者的一个常用设计模式。应用对提供者进行查询并使用CursorAdapterListView内显示作为查询结果的CursorCursorAdapter的定义要求Cursor内列的其中之一是_ID


然后,用户从UI上选取显示行中的一个以便查看或修改数据。应用从ListView后面的Cursor获取获取行,得到改行的_ID值,把附加到内容URI上,然后向提供者发送访问请求。于是提供者对正确的用户选择行进行查询或修改。



2.4 内容URI模型

为了帮助你对于传入的内容URI而采取何种行为,提供者API包括方便类UriMatcher,它把内容“patterns”映射为整型值。可以在switch语句里使用整型值,在此选择期望的内容URI行为或匹配特殊模型的URIs行为。


内容URI模型使用通配符匹配内容URIs:


  • *:匹配任何长度的任何有效的字符串。
  • #:匹配任何长度的任何数字字符串。

作为设计和编码处理内容URI一个例子考虑授权为com.example.app.provider的提供者识别如下内容URI指向的表

  • content://com.example.app.provider/table1: 名为table1的表。
  • content://com.example.app.provider/table2/dataset1:名为dataset1的表。
  • content://com.example.app.provider/table2/dataset2:名为dataset2的表。
  • content://com.example.app.provider/table3:名为table3的表。

如果它们后面附加行ID,提供者也能识别这些内容URIs,例如行content://com.example.app.provider/table3/1在table3中由1来标识。


下面的内容URI模型将有可能:


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

匹配提供者内的任何内容URI。

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

匹配表dataset1表dataset1的内容URI,但不匹配表table1table3的内容URI。

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

匹配表table3里单行内容URI,譬如由6标识的行content://com.example.app.provider/table3/6


接下来的代码段显示了UriMatcher内的方法是如何工作的。这段代码处理整表的URIs不同于单行URIs,通过使用表content://<authority>/<path>,和行content://<authority>/<path>/<id>内容URI模型


方法addURI() 把权限和路径映射为一整型值。方法match() 返回URI整型值。switch 句在整表查询和单行记录查询间进行选择:


public class ExampleProvider extends ContentProvider {
...
    // Creates a UriMatcher object.
    private static final UriMatcher sUriMatcher;
...
    /*
     * The calls to addURI() go here, for all of the content URI patterns that the provider
     * should recognize. For this snippet, only the calls for table 3 are shown.
     */
...
    /*
     * Sets the integer value for multiple rows in table 3 to 1. Notice that no wildcard is used
     * in the path
     */
    sUriMatcher.addURI("com.example.app.provider", "table3", 1);

    /*
     * Sets the code for a single row to 2. In this case, the "#" wildcard is
     * used. "content://com.example.app.provider/table3/3" matches, but
     * "content://com.example.app.provider/table3 doesn't.
     */
    sUriMatcher.addURI("com.example.app.provider", "table3/#", 2);
...
    // Implements ContentProvider.query()
    public Cursor query(
        Uri uri,
        String[] projection,
        String selection,
        String[] selectionArgs,
        String sortOrder) {
...
        /*
         * Choose the table to query and a sort order based on the code returned for the incoming
         * URI. Here, too, only the statements for table 3 are shown.
         */
        switch (sUriMatcher.match(uri)) {


            // If the incoming URI was for all of table3
            case 1:

                if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC";
                break;

            // If the incoming URI was for a single row
            case 2:

                /*
                 * Because this URI was for a single row, the _ID value part is
                 * present. Get the last path segment from the URI; this is the _ID value.
                 * Then, append the value to the WHERE clause for the query
                 */
                selection = selection + "_ID = " uri.getLastPathSegment();
                break;

            default:
            ...
                // If the URI is not recognized, you should do some error handling here.
        }
        // call the code to actually do the query
    }


另一个类,ContentUris,提供了方便使用内容URIs的id部分的方法。类Uri和Uri.Builder则包含解析现有Uri对象和构建新Uri对象的便捷方法。





3. 实现ContentProvider类



ContentProvider实例是通过处理来自其他应用的请求进而来管理结构化的数据集访问。所有形式的访问最终都要调用ContentResolver(内容解析器),然后它调用ContentProvider的具体方法获取访问。   


3.1 所需的方法

抽象类 ContentProvider定义了六个抽象方法,必须实现它们作为自己的具体子类的一部分。除了onCreate() 外,所有的这些方法由试图访问内容提供者的客户端应用来调用


query()
从提供者取出数据。使用参数来指定查询表,返回的行和列,接着是结果的排列顺序。返回的数据作为一个Cursor对象。
insert()
插入一新行到提供者中。使用参数指定目标表及使用的列值(实为一行)。返回刚刚插入行的内容URI。
update()
更新提供者里现有的行。使用参数指定表和要更新的行,以及被更新的列值。返回被更新后了的行数。
delete()
从提供者内删除行。使用参数指定表和要删除的行。返回被删除的行数。
getType()
返回对应内容URI的MIME类型。该方法更详细的描述在章节实现内容提供者MIME类型里。
onCreate()
初始化提供者。Android系统创建提供者后立刻调用这个方法。注意,直到ContentResolver对象试图访问提供者时,它才被创建。


注意,这些方法和同名的ContentResolver方法有相同的签名。


这些方法的实现应该考虑以下方面:

  • onCreate()外,所有这些方法可同时被多个线程调用,所以它们必须是线程安全的。学习更多关于多线程的内容,请参阅进程和线程章节。
  • 避免在onCreate()方法内做长时间操作。推迟初始化工作,直到实际需要它们时。实现onCreate() 方法章节里对此有更详细地讨论。
  • 尽管必须实现这些方法,但代码上不必做任何事情,除了返回期望的数据类型外。例如,你可能要阻止其他应用向表里插入数据。为做到这点,忽略对insert() 的调用然后返回0.


3.2 实现query()方法


ContentProvider.query()方法必须返回Cursor对象, 或者如果它失败了,抛出Exception。如果你正使用SQLite数据库作为数据存储,你可以简单地返回由SQLiteDatabase类的某个query返回的Cursor。如果查询没有匹配任何行,你应该返回一个Cursor实例,而它的getCount()方法返回0。只有在查询过程中发生内部错误时,才返回NULL。


如果没有使用SQLite数据库作为数据存储,而是使用Cursor的某个具体子类。例如,MatrixCursor类实现了Cursor,在该cursor里,每行都是一组对象。通过该类,使用addRow()添加新行。


记住,Android系统必须能够跨进程边界与Exception通信。Android能够下列异常做到这一点,它们可能有助于处理查询错误:

  • IllegalArgumentException (如果提供者到无效内容URI, 可以抛出它。)
  • NullPointerException


3.3 实现insert()方法


insert()方法使用ContentValues参数中的值把新行加入适当的表里。如果列名没有在ContentValues参数里,则你可能想在提供者代码中或数据库框架中为该列提供一默认值。


该方法返回新行的内容URI。为构造该内容URI,使用withAppendedId()方法把新行的_ID(或者其他的主键)值附加到该表的内容URI上。



3.4 实现delete()方法


delete() 方法不必在物理上从数据存储中删除行。如果正在对提供者使用同步适配器,应该考虑通过“delete”标志来标记被删除的行,而不是彻底地删除行。同步适配器能够检查被删除行,并在将其从提供者内删除之前将它们从服务器中移除。



3.5 实现update()方法


update() 方法接受相同的由insert()使用的ContentValues参数, 以及相同的由 delete()ContentProvider.query()使用的selectionselectionArgs参数。 这可以允许你在这些方法间复用代码。



3.6 实现onCreate()方法


当Android系统启动提供者时调用onCreate() 。应该在此方法内只执行快速运行的初始化工作,并且推迟数据库创建和数据加载,直到提供者实际收到数据请求。如果在onCreate()中执行冗长的工作,将减缓提供者的启动。 进而,放慢了提供者对其他应用的响应。


例如,如果你正在使用SQLite数据库,可以在ContentProvider.onCreate()方法里创建一个新的SQLiteOpenHelper对象,然后,第一次打开数据库时创建SQL表。为使此过程变得容易,第一次调用getWritableDatabase()时,它自动地调用了SQLiteOpenHelper.onCreate()方法。


下面两段代码阐述了ContentProvider.onCreate()SQLiteOpenHelper.onCreate()之间的相互作用。第一个片段是ContentProvider.onCreate()的实现:


public class ExampleProvider extends ContentProvider

    /*
     * Defines a handle to the database helper object. The MainDatabaseHelper class is defined
     * in a following snippet.
     */
    private MainDatabaseHelper mOpenHelper;

    // Defines the database name
    private static final String DBNAME = "mydb";

    // Holds the database object
    private SQLiteDatabase db;

    public boolean onCreate() {

        /*
         * Creates a new helper object. This method always returns quickly.
         * Notice that the database itself isn't created or opened
         * until SQLiteOpenHelper.getWritableDatabase is called
         */
        mOpenHelper = new SQLiteOpenHelper(    //此处有误!应该mOpenHelper = new MainDatabaseHelper 
            getContext(),        // the application context
            DBNAME,              // the name of the database)
            null,                // uses the default SQLite cursor
            1                    // the version number
        );

        return true;
    }

    ...

    // Implements the provider's insert method
    public Cursor insert(Uri uri, ContentValues values) {
        // Insert code here to determine which table to open, handle error-checking, and so forth

        ...

        /*
         * Gets a writeable database. This will trigger its creation if it doesn't already exist.
         *
         */
        db = mOpenHelper.getWritableDatabase();
    }
}


接下来的代码段是SQLiteOpenHelper.onCreate()的实现,包括一个助手类:


...
// A string that defines the SQL statement for creating a table
private static final String SQL_CREATE_MAIN = "CREATE TABLE " +
    "main " +                       // Table's name
    "(" +                           // The columns in the table
    " _ID INTEGER PRIMARY KEY, " +
    " WORD TEXT"
    " FREQUENCY INTEGER " +
    " LOCALE TEXT )";
...
/**
 * Helper class that actually creates and manages the provider's underlying data repository.
 */
protected static final class MainDatabaseHelper extends SQLiteOpenHelper {

    /*
     * Instantiates an open helper for the provider's SQLite data repository
     * Do not do database creation and upgrade here.
     */
    MainDatabaseHelper(Context context) {
        super(context, DBNAME, null, 1);
    }

    /*
     * Creates the data repository. This is called when the provider attempts to open the
     * repository and SQLite reports that it doesn't exist.
     */
    public void onCreate(SQLiteDatabase db) {

        // Creates the main table
        db.execSQL(SQL_CREATE_MAIN);
    }
}


4.  实现内容提供者MIME类型



为返回MIME类型,ContentProvider类有两个方法:

getType()
为任何提供者都必须实现的所需方法之一。
getStreamTypes()
一个希望由你实现的方法,如果提供者提供文件。

4.1 表的MIME类型


getType() 方法返回MIME格式字符串,该字符串描述了由内容URI参数返回的数据类型。Uri 参数可以是一个模式而非具体的URI;这种情况下, 应该返回与匹配模式的内容URIs相关的数据类型。


对于常见的数据类型,比如文本,HTML或JPEG,getType()应该返回数据的MIME类型(实为MIME格式的字符串)。这些标准类型的完整列表可在IANA MIME Media Types站点上获得。


对于指向表数据的一列或多列的内容URIs, getType()应该按照Android厂商特定的MIME格式返回MIME类型(实为MIME格式的字符串):

  • 类型部分: vnd
  • 子类型部分:
    • 如果URI模型是单行: android.cursor.item/
    • 如果URI模型多于一行: android.cursor.dir/
  • 提供者指定部分: vnd.<name>.<type>

    由你提供<name><type><name>值应该是全球唯一的, 并且<type>值对于对应的URI模式来说也应该是唯一的。对<name>来说,一个不错的选择是公司名字或者应用的Android包名的一部分。对于<type>, 识别与URI有关的表的字符串是个不错的选择。


例如,如果提供者的授权是com.example.app.provider, 并且公开了名为table1的表,则table1内多行的MIME类型是:

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


对于table1的单行来说,MIME类型是:

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



4.2 文件的MIME类型


如果提供者提供文件,请实现getStreamTypes()。该方法返回提供者针对给定内容URI能够返回的文件的MIME类型字符串数组。你应该通过MIME类型过滤参数来过滤你给出的MIME类型,目的为了仅返回那些客户端想要处理的MIME类型。


例如,考虑一个可以提供jpg,png,和gif格式图片文件的提供者。如果应用使用过滤字符串image/*(一些是“图片”的东西)来调用ContentResolver.getStreamTypes() , 那么ContentProvider.getStreamTypes() 方法应该返回数组:


{ "image/jpeg", "image/png", "image/gif"}

如果应用仅对jpg文件感兴趣,然后使用过滤字符串 *\/jpeg调用ContentResolver.getStreamTypes() , 那么ContentProvider.getStreamTypes()应该返回:


{"image/jpeg"}

如果提供者没有提供任何过滤字符串中请求的MIME类型, getStreamTypes() 应该返回null




5.  实现契约类



契约类是一个public final类,它包含了URIs,列名,MIME类型,以及其他关于提供者的元数据的常量定义。该类为确保提供者可以被正确地访问而在提供者和其他的应用间建立约定,即便是URIs,列名等发生了变化。


契约类对开发者也有帮助,因为它通常有其常量的助记名,所以开发者不大可能使用不正确的列名值或URIs值。由于它是个类,它能够包含Javadoc文档。集成开发环境(IDE),例如Eclipse,能够自动完成源自契约类的常量名并为常量显示Javadoc。


开发者不能从你的应用中访问契约类的class文件,但他们可以从你提供的.jar文件中把它静态地编译进他们的应用里。


ContactsContract 类和它的嵌套类都是契约类的例子。




6.  实现内容提供者权限



Android系统的权限和访问的所有方面在安全和权限主题中有更详细地描述。数据存储主题也阐述了安全和权限对不同存储类型的影响。总之,重点是:


  • 默认情况下存储在设备内部存储器上的数据文件对你的应用和提供者是私有的
  • 你创建的SQLiteDatabase数据库对你的应用和提供者是私有的。
  • 默认情况下保存在外部存储器上的数据文件是公开的和外部可读的(world-readable)。你不能使用内容提供者来限制对外部存储器里的文件访问, 因为其他应用可以使用别的API调用来读或写它们。
  • 在设别内部存储器上对文件或SQLite数据库打的打开或创建的调用能够潜在地把读和写权限给了所有其他的应用。如果你使用内部文件或数据库作为提供者的数据存储,并且给它“外部可读"("world-readable")或“外部可写"("world-writeable")的权限,  那么在它的清单文件中设置的权限将无法保护你的数据。内部存储器上的文件和数据库的默认访问是"私有的"("private"),并且对于你的提供者存储来说,你也不应该改变这一点。


如果想利用内容提供者来控制你的数据访问,那么你应该把数据存储在内部文件,SQLite数据库,或是"云"(例如,在远程服务器上)中,并且应该保持文件和数据库专属于你的应用。



6.1 实现权限

所有应用能够读或者写你的提供者,即便底层数据是私有的,因为默认情况下提供者没有设置权限。为改变这一点,使用属性或 <provider>元素的子元素,在清单文件中为你的提供者设置权限。可以设置适用于整个提供者,或者某个表,甚至某个记录,又或是所有它们三个的权限。


在清单文件中,以一个或多个<permission>元素为提供者定义权限。为使权限独有于提供者,对android:name属性使用Java风格的作用域。例如,命名读权限com.example.app.provider.permission.READ_PROVIDER


接下来的列表描述提供者权限作用域,他们以适用于整个提供者的权限为开始,然后变得更细化。更细化的权限优先于更大作用域的权限:


单独的提供者级读/写权限

一个控制整个提供者读和写的权限,由<provider>元素的android:permission的属性指定。


独立的提供者级读和写权限

整个提供者的读权限和写权限。 通过<provider>元素的android:readPermissionandroid:writePermission属于来指定他们。它们优先于android:permission所要求的权限。


路径级权限

提供者的内容URI读,写,或读/写权限。通过<provider>元素的子元素<path-permission>来指定每个你想控制的权限。对于每个指定的内容URI, 可以指定一个读/写,读或写权限,或全部三个。读权限和写权限优先于读/写权限。同样,路径级权限优先于提供者级权限。


临时权限

授予应用的临时访问权限级别,即使应用没有通常所被要求的权限。临时访问的特性减少了应用必须在清单文件里请求的权限数量。 当打开临时权限时,唯一需要“永久”提供者权限的应用是那些频繁访问你所有数据的应用。


当打算允许一个外部图片浏览器应用显示来自提供者的图片附件时,考虑需要实现邮件提供者和应用的权限。为给图片浏览器需要的访问而不请求权限,对图片内容URIs设置临时权限。设计邮件应用的目的是当用户想显示图片时,应用发送包含有图片内容URI和权限标志的意图给图片浏览器。然后,图片浏览器能够查询邮件提供者来提取图片,即便浏览器没有正常读该提供者的权限。


为打开临时权限,要么设置<provider>元素的android:grantUriPermissions属性,或者为<provider>元素添加一个或更多个<grant-uri-permission>子元素。如果使用临时权限, 每当从提供者解除对和临时权限有关系的内容URI的支持时,必须调用Context.revokeUriPermission()


属性值决定了提供者可被访问的程度。如果属性被设置为true,那么系统将对整个提供者授予临时权限,压倒任何其他的由提供者级和路径级权限所需要的权限。


如果标志被设置为false,那么必须为<provider>元素添加<grant-uri-permission>子元素。每个子元素指定被授予临时访问权限的内容URI或URIs。


为给应用委派临时访问权限,意图必须包含FLAG_GRANT_READ_URI_PERMISSION或 theFLAG_GRANT_WRITE_URI_PERMISSION标志,或者他们两个。他们由setFlags()方法来设置。

如果android:grantUriPermissions属性没有出现,则它被假设为false。



7.  <provider>元素



同ActivityService组件,ContentProvider的子类必须被定义在它的应用清单文件里,通过使用<provider>元素。Android系统从该元素获得如下信息:

授权(android:authorities)

系统内标识整个提供者的符号名。在设计内容URIs章节有这个属性的更详细描述。

提供者类名 (android:name)

实现ContentProvider的类。该类更详细的描述在实现ContentProvider类章节。

权限
指定权限的属性,其他应用为访问提供者的数据必须拥有该权限:
  • android:grantUriPermssions: 临时访问权限标志。
  • android:permission: 单个的提供者级读/写权限。
  • android:readPermission: 提供者级读权限。
  • android:writePermission:提供者级写权限。

权限和它们对应属性的更详细描述在实现内容提供权限章节。

启动和控制属性
这些属性决定了系统如何及何时启动提供者,决定了提供者的进程特点和其他的运行时设置:
  • android:enabled: 允许系统启动提供者的标志。
  • android:exported: 允许其他应用使用该提供者的标志。
  • android:initOrder: 相对于同进程里其他提供者,该提供者被启动的顺序。
  • android:multiProcess: 允许系统在与调用客户端相同的进程里启动提供者的标志。
  • android:process: 进程的名字,提供者应该在该进程中运行。
  • android:syncable: 暗示提供者数据将要与服务器上的数据同步的标志。

这些属性被完全记录在<provider>元素的开发指南主题

信息属性
可选的提供者图标和标签:
  • android:icon: 包含提供者图标的可绘制资源。在Settings >Apps >All的应用列表里,图标挨着提供者标签出现
  • android:label: 描述提供者或其数据,或者它们两个的信息标签。标签出现在Settings > Apps > All中的应用列表中。

这些属性被完全记录在<provider>元素的开发指南主题中。




8.  意图和数据访问



应用可以通过Intent间接地访问内容提供者。应用不必调用ContentResolverContentProvider的任何方法。相反,它发送意图来启动活动,该活动通常是提供者自身应用的一部分。目标活动负责取出并在它的UI里显示数据。依赖于意图内的动作,目标活动也可能提示用户对提供者数据做出修改。意图也可能包含“额外”数据,目标活动在其UI上显示该数据;然后,使用该意图修改提供者数据前,用户可以选择修改该额外数据。


你可能想使用意图访问来促使数据的完整性。提供者可能依赖于根据严格定义的商业逻辑来插入,更新,和删除数据。如果是这样的话,允许其他应用直接修改数据可能导致非法的数据。如果想让开发者使用意图访问,一定要充分地描述它。向他们解释,为什么意图访问使用提供自身应用的UI要好于试图通过他们自己的代码来修改数据。


处理传入的希望修改提供者数据的意图与处理其他的意图是没有区别的。通过阅读主题意图和意图过滤器,可以了解更多关于使用意图的知识。 




                                                                                                              2012年8月15日                  毕




你可能感兴趣的:(android,数据库,sqlite,delete,存储,insert)