Android Content Provider Tutorial--安卓内容提供者系列4--如何创建内容提供者

上一篇链接:Android Content Provider Tutorial--安卓内容提供者系列3--操作安卓联系人


Creating a Content Provider(如何创建一个ContentProvider)

Content Provider Overview(ContentProvider概述)

A content provider manages access to a central repository of data.

  • You implement a provider as a subclass ContentProvider, which is the interface between your provider and other applications.
  • You can define other related classes, such as a contract class to define public constants such as field names to help other applications access the data from your content provider.

一个Content Provider管理着对一个中央数据仓库的访问。

  • 你要创建一个ContentProvider只要写个子类实现ContentProvider接口就可以了,这是你的ContentProvider和其他应用程序之间的接口。
  • 你可以定义其他相关类,比如合约类来定义诸如字段名称这样的公共常量,从而使其他应用程序能访问到你的ContentProvider里的数据。

In addition to the content provider, you can implement activities in your application that allow the user to query and modify the data managed by your provider.

除了content provider,你还可以在你的应用程序中创建activity,使用户能够查询和修改由你的ContentProvider管理的数据。

  • You can expose these activities to other applications by registering implicit intent filters in your application manifest to launch appropriate activities.
  • 通过在你的应用程序清单文件中注册隐式意图过滤器,你可以把这些activity暴露给其他应用程序去启动。
  • Some examples of implicit intent actions you might want to support are:
  • 这是一些你可能需要支持的隐式意图的action:
    Intent.ACTION_VIEW

    Launch an activity to view a single record(启动一个activity去查看单个记录)

    Intent.ACTION_EDIT

    Launch an activity to edit a single record(启动一个activity去编辑单个记录)

    Intent.ACTION_PICK

    Launch an activity to select a record from the collection stored in the provider

    (启动一个activity去从ContentProvider中的集合中挑出一个记录)

  • In addition to these actions, your intent filters should typically include a MIME type to identify the type of data managed by your provider. The data MIME types are discussed later in this section.
  • 除了这些action,你的意图过滤器通常还应该包括一个MIME类型去识别由你的ContentProvider管理的数据类型。数据的MIME类型将在本节稍后讨论。

Why Implement a Content Provider?(为什么要实现一个ContentProvider?)

You should implement a content provider if you want to:

  • Offer complex data or files to other applications
  • Allow users to copy complex data from your app into other apps
  • Provide custom search suggestions using the search framework
  • Expose data collections for use in application widgets

如果你想达到下面的目的,你就应该实现一个ContentProvider:

  • 为其他应用程序提供复杂的数据或文件
  • 允许用户将复杂的数据从你的应用程序复制到其他应用程序
  • 使用搜索框架提供自定义搜索建议
  • 在应用程序的小部件中公开数据集合供使用

You don’t need a content provider to manage access to an SQLite database if the use is entirely within your own application. However, even for use within a single application, you might consider implementing a content provider to:

  • Encapsulate access to a data source
  • Use the CursorLoader class to take advantage of the loader framework added in Honeycomb, which automatically uses a worker thread to query a content provider and return a Cursor

如果是完全在自己的应用程序中管理对SQLite数据库的访问,那你并不需要content provider。但是,即使是在单个应用程序中,出于以下目的考虑,你应该试试去实现一个ContentProvider:

  • 封装对一个数据源的访问
  • 使用CursorLoader类去充分利用Android3.0中添加的加载器框架,该框架会自动使用一个工作线程去查询ContentProvider并返回一个游标。

Steps to Implementing a Content Provider(实现ContentProvider的步骤)

Follow these steps to build your provider:(跟着下面的步骤走)

  1. Design the raw storage for your data. A content provider can offer data in two ways:
    "Structured" data

    Data that normally goes into a database, array, or similar structure.

    • Store the data in a form that’s compatible with tables of rows and columns.
    • A row represents an entity, such as a person or an item in inventory.
    • A column represents some data for the entity, such a person’s name or an item’s price.
    • A common way to store this type of data is in an SQLite database, but you can use any type of persistent storage.
    为你的数据设计原始存储。content provider可以有两种方式提供数据:
    “结构化”数据—— 通常是存在数据库,数组,或类似结构的数据。
    • 以一种类似表那样的行和列的形式将数据存储起来。
    • 一行代表一个实体,正如一个人或库存中的一个项目。
    • 一列代表该实体的一些数据,比如一个人的名字或一个项目的价格。
    • 存储这类数据一个常见方式是存在一个SQLite数据库中,当然你也可以使用任何其他类型的持久化存储方式。
    File data

    Data that normally goes into files, such as photos, audio, or videos.

    • Store the files in your application’s private space.
    • In response to a request for a file from another application, your provider can offer a handle to the file.

    文件数据——指通常写入到文件中的数据,如照片、音频或视频等

    • 将文件存储在应用程序的私有空间。
    • 为了响应其他应用程序向你发起的文件请求,你的ContentProvider需要提供一个该文件的句柄
  2. Define the provider’s external interface.
    • This includes its authority string, its content URIs, and column names.
    • Also define the permissions that you will require for applications that want to access your data.
    • Consider defining all of these values as constants in a separate contract class that you can expose to other developers.
  3. 定义ContentProvider的外部接口。
    • 这包括其授权字符串,及其内容uri,还有列名。
    • 为了能让其他应用程序可以访问你的数据,你也需要定义相应的权限。
    • 另一个要考虑的是:你要在一个单独的合约类里定义所有这些值并暴露出来,这样可以使其他开发人员也能使用。
  4. Define a concrete implementation of the ContentProvider class and its required methods.
    • This class is the interface between your data and the rest of the Android system.
  5. 定义一个ContentProvider的具体实现类及其所需的方法。
    • 这个类是你的数据和Android系统的其他部分之间的接口。

Designing Data Storage(设计数据存储方式)

You can store the data in any form you like, and then design the interface to read and write the data as necessary. Options include:

  • For table-oriented data, Android includes an SQLite database API that Android’s own providers use to store table-oriented data. The SQLiteDatabase class is the base class for accessing databases, the SQLiteOpenHelper class helps you create databases and handle upgrade scenarios.

您可以以任何你喜欢的方式来存储数据,然后在必要的时候设计读取和写入数据的接口。有下面这些选项可选:

  • 对于面向表的数据,Android包含一个SQLite数据库API,Android自己的ContentProvider就是使用它来存储面向表的数据的。SQLiteDatabase类是访问数据库的基类,SQLiteOpenHelper类可以帮你创建数据库和处理数据库升级场景。
    Remember that you don’t have to use a database to implement your repository. A provider appears externally as a set of tables, similar to a relational database, but this is not a requirement for the provider’s internal implementation.
  • 记住,你不需要使用数据库来实现仓库。一个ContentProvider外部表现为一组表,类似于关系数据库,但对于ContentProvider的内部实现来说并不需要这样的要求。
  • For storing file data, Android has a variety of file-oriented APIs. For example, if you’re designing a provider that offers media-related data such as music or videos, you can have a provider that combines table data and files.
  • Android有很多面向文件操作的api,可用于存储文件数据。例如,如果你正在设计一个ContentProvider用来提供像音乐视频等媒体数据,你可以搞一个将表数据和文件结合起来的ContentProvider。
  • For working with network-based data, use classes in java.net and android.net. You can also synchronize network-based data to a local data store such as a database, and then offer the data as tables or files.
  • 对于处理基于网络的数据,可以使用java.net和android.net包中的类。还可以将基于网络的数据同步到本地存储,比如数据库中,然后提供以表或者文件的形式来提供数据。

Implementing SQLiteOpenHelper(实现SQLiteOpenHelper

An example of a SQLiteOpenHelper implementation(举个栗子)
public class DbHelper extends SQLiteOpenHelper {
    
    
        public static final String DB_NAME = "timeline.db";
        public static final int DB_VERSION = 1;
        public static final String TABLE = "status";

        public DbHelper(Context context) {
    
    
                super(context, DB_NAME, null, DB_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
    
    
                String sql = String.format("create table %s ( %s INT PRIMARY KEY,"
                                + "%s INT, %s TEXT, %s TEXT);", TABLE,
                                StatusContract.Columns._ID, StatusContract.Columns.CREATED_AT,
                                StatusContract.Columns.USER, StatusContract.Columns.MESSAGE);
                Log.d("DbHelper", "sql: "+sql);
                db.execSQL(sql);
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    
    
                // Temporary for development purposes
                db.execSQL("drop table if exists "+ TABLE);
                onCreate(db);
        }
}

Data Design Considerations(数据设计注意事项)

Table data should always have a primary key column that the provider maintains as a unique numeric value for each row.

  • You can use this value to link the row to related rows in other tables (using it as a foreign key).
  • Although you can use any name for this column, using BaseColumns._ID is the best choice, because linking the results of a provider query to a ListView requires one of the retrieved columns to have the name _ID.

表数据应该总是要有一个主键列,ContentProvider用来作为每一行的一个独特的数字值。

  • 您可以使用这个值去将本行关联到其他表中的使用该值作为一个外键的行。
  • 尽管您可以给这一列定任何名称,但最好还是用BaseColumns._ID吧,因为把ContentProvider查询的结果显示到ListView上,需要一个检索为列名为_ID的列。

If you want to provide bitmap images or other large pieces of file-oriented data, store the data in a file and then provide it indirectly rather than storing it directly in a table.

  • If you do this, you need to tell users of your provider that they need to use a ContentResolver file method to access the data.

如果你想提供位图图像或其他大块文件数据,与其将数据直接存储在一个表里,不如将数据先存在一个文件中然后间接地提供出去。

  • 如果你这样做的话,你需要告诉用户你的ContentProvider的使用者,他们需要使用ContentResolver的文件操作方法去访问数据。

Use the Binary Large OBject (BLOB) data type to store data that varies in size or has a varying structure.

  • For example, you can use a BLOB column to store a small icon or a JSON structure.
  • You can also use a BLOB to implement a schema-independent table.
    • In this type of table, you define a primary key column, a MIME type column, and one or more generic columns as BLOB data.
    • The meaning of the data in the BLOB columns is indicated by the value in the MIME type column.
    • This allows you to store different row types in the same table.
    • The contacts provider’s "data" table, ContactsContract.Data, is an example of a schema-independent table.

使用二进制大对象(BLOB)数据类型去存储不同大小或不同结构的数据。

  • 例如,你可以使用一个BLOB列来存储一个小图标或一个JSON型数据。
  • 您还可以使用一个BLOB去实现一个不依赖模式的表。
    • 在这种类型的表中,你定义了一个主键列,一个MIME类型列,和一个或多个通用列用来表示BLOB数据。
    • BLOB列中的数据的含义是由MIME类型列中的值表示的。
    • 这样的话,你就可以在同一个表中存储不同的行类型了。
    • 联系人ContentProvider的“数据”表——ContactsContract.Data,就是一个不依赖模式的表的例子。

Designing Content URIs(设计内容URI)

content URI is a URI that identifies data in a provider, consisting of:

内容URI是一种标识ContentProvider中的数据的URI,它包括:

An  authority

The symbolic name of the entire provider

整个ContentProvider的符号名称

A path

A name pointing to a table or file

指向一个表或文件的路径名称

An ID (optional)

The last path component, pointing to an individual row in a table

路径中最后一个组成部分,指向一个表中的单独的一行

Every data access method of ContentProvider receives a content URI as an argument.

  • This allows you to determine the table, row, or file to access.

ContentProvider的每个数据访问方法都接收一个内容URI作为参数。

  • 这样就可以确定你要访问的那个表、行或文件。

Content URIs: Selecting an Authority(内容URI:选择一个授权)

A provider usually has a single authority, which is a unique string that serves as its Android-internal name.

  • To avoid conflicts with other providers, you should use Internet domain ownership (in reverse) as the basis of your provider authority.
  • Because this recommendation is also true for Android package names, you can define your provider authority as an extension of the name of the package containing the provider.
  • For example, if your Android package name is com.example.<appname>, you should give your provider the authority com.example.<appname>.provider.

一个ContentProvider通常有一个单独的授权,这是一个独一无二的字符串作为其在安卓系统内的名称。

  • 为了避免与其他ContentProvider的冲突,您应该使用因特网域名所有权(反过来拼写)作为你的ContenProvider授权的基础。
  • 因为这个建议也适用于Android的包名,你可以定义你的ContentProvider的授权为一个包名基础上包含provider的扩展名称。
  • 例如,如果您的安卓包名是com.example.<appname>,你应该给你的ContentProvider的授权命名为com.example.<appname>.provider。

Content URIs: Designing the Path Structure(内容 URI:设计路径结构)

Typically you should create content URIs from the authority by appending paths that point to individual tables.

  • For example, if you have two tables table1 and table2, you combine the authority from the previous example to yield the content URIs com.example.<appname>.provider/table1 and com.example.<appname>.provider/table2.

Paths aren’t limited to a single segment, and you don’t need to have a table for each level of the path.

通常情况下你应该从授权中通过附加指向单个表的路径来创建内容uri。

  • 例如,如果你有两个表:table1和table2,你把上面例子中的授权和表名绑定起来就产生了内容URI:com.example.<appname>.provider/table1和com.example.<appname>.provider/table2

路径并不局限于一个单一的部分,你不需要为每个级别的路径到要有一个表。

Content URIs: Handling IDs(内容 URI:处理ID)

By convention, providers offer access to a single row in a table by accepting a content URI with an ID value for the row at the end of the URI.

  • Also by convention, providers match the ID value to the table’s _ID column, and perform the requested access against the row that matches.

按照惯例,ContentProvider通过接受一个结尾是行ID值的内容URI的方式,提供访问表中的一行的方法

  • 同样按照惯例,ContentProvider根据内容URI中的ID值来匹配表的_ID列,然后对匹配的行执行请求的访问。

This convention facilitates a common design pattern for apps accessing a provider.

  • The app does a query against the provider and displays the resulting Cursor in a ListView using a CursorAdapter.
  • The definition of CursorAdapter requires one of the columns in the Cursor to be _ID

本公约使应用程序访问ContentProvider的通用设计模式更方便。

  • 应用程序对ContentProvider做查询操作,然后通过使用CursorAdapter在ListView控件中显示查询结果
  • CursorAdapter的定义需要游标中的一个叫_ID的列

The user then picks one of the displayed rows from the UI in order to look at or modify the data.

  • The app gets the corresponding row from the Cursor backing the ListView, gets the _ID value for this row, appends it to the content URI, and sends the access request to the provider.
  • The provider can then do the query or modification against the exact row the user picked.

然后用户从UI显示的行中选择一行以查看或修改数据。

  • 应用程序从游标中得到了相应的行,得到了这一行的_ID值,并将该_ID值添加到内容URI结尾,然后向ContentProvider发送访问请求。
  • ContentProvider就可以对用户选择的那一行做查询或修改操作了。

Content URI Pattern Matching with UriMatcher(用UriMatcher来匹配内容URI模式)

The UriMatcher can simplify the common task of validating and parsing URIs.

  • After instantiating a UriMatcher object, you can register URI patterns to map to integer values.
  • Then you can test URIs against the registered patterns, and switch on the corresponding integer value returned.

UriMatcher可以简化验证和解析uri的常见任务流程。

  • 实例化一个UriMatcher对象后,你可以注册URI模式映射到整数值上。
  • 然后你就可以根据注册的模式来测试URI,并在相应的整数返回值上做切换选择。

When you instantiate a UriMatcher, you provide an integer value to return if a given URI matches none of the registered patterns.

  • Typically, you should use UriMatcher.NO_MATCH for this values.

在实例化一个UriMatcher时,如果给定URI不能匹配任何已注册的模式,那你就需要提供一个整数返回值。

  • 通常来说,您应该使用UriMatcher.NO_MATCH这个值。

Use the method UriMatcher.addURI(String authority, String path, int code) to map URI patterns to return values.

  • In addition to static paths, the UriMatcher supports two wildcard characters in patterns:

使用UriMatcher.addURI(String authority, String path, int code)方法将URI模式映射到返回值中。

除了静态路径之外,UriMatcher还支持两个通配符模式:

The ContactsContract class and its nested classes are examples of contract classes.

ContactsContract类及其嵌套类都是合约类的例子。

Example Contract Class(合约类示例)

  • public final class StatusContract {
          
          
    
            private StatusContract() {}
    
        /** The authority for the contacts provider */
            public static final String AUTHORITY = "com.marakana.android.yamba.provider";
    
        /** A content:// style uri to the authority for this table */
            public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/status");
    
        /** The MIME type of {@link #CONTENT_URI} providing a directory of status messages. */
        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.marakana.status";
    
        /** The MIME type of a {@link #CONTENT_URI} a single status message. */
        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.marakana.status";
    
            /**
             * Column definitions for status information.
             */
            public final static class Columns implements BaseColumns {
          
          
                    private Columns() {}
    
                    /**
             * The name of the user who posted the status message
             * <P>Type: TEXT</P>
                     */
                    public static final String USER = "user";
    
                    /**
             * The status message content
             * <P>Type: TEXT</P>
                     */
                    public static final String MESSAGE = "message";
    
                    /**
             * The date the message was posted, in milliseconds since the epoch
             * <P>Type: INTEGER (long)</P>
                     */
                    public static final String CREATED_AT = "createdAt";
    
            /**
             * The default sort order for this table
             */
            public static final String DEFAULT_SORT_ORDER = CREATED_AT + " DESC";
    
            }
    }

    Implementing permissions(实现权限)

  • By default, all applications can read from or write to your provider — even if the underlying data is private — because by default your provider does not have permissions set.
    • To change this, set permissions for your provider in your manifest file, using attributes or child elements of the <provider> element.
    • You can set permissions that apply to the entire provider, or to certain tables, or even to certain records, or all three.
    默认情况下,所有应用程序都可以读取或者写入到你的ContentProvider——即使底层数据是私有的——因为默认情况下你的ContentProvider没有权限。
    • 为了改变这一状况,你要在清单文件中为你的ContentProvider设置权限,使用<provider>元素的属性或者其子元素。
    • 你可以设置权限为适用于整个ContentProvider,或某些表,甚至某些记录,或者以上三者。

    You define permissions for your provider with one or more <permission> elements in your manifest file. * To make the permission unique to your provider, use Java-style scoping for the android:name attribute. * For example, name the read permission com.example.app.provider.permission.READ_PROVIDER.

    你在你的清单文件中以一个或多个<permission>元素来为ContentProvider定义权限。*为了使你的ContentProvider保持唯一,使用java风格来为android:name 属性值命名。例如,将读取权限命名为: com.example.app.provider.permission.READ_PROVIDER。

    You can specify several types of provider permissions, with more fine-grained permissions taking precedence over ones with larger scope:

    您可以指定几种类型的ContentProvider权限,更细化的权限将优先于那些较大范围的权限:

    Single read-write provider-level permission(单一的读写ContentProvider级权限)

    One permission that controls both read and write access to the entire provider, specified with the android:permission attribute of the <provider> element.

    一个控制读写整个ContentProvider的权限,由<provider>元素的android:permission 属性值来标识。

    Separate read and write provider-level permission(区分ContentProvider级的读和写的权限)

    A read permission and a write permission for the entire provider.

    • You specify them with the android:readPermission and android:writePermission attributes of the <provider> element.
    • They take precedence over the permission required by android:permission.

    整个提供者的读权限和写权限。

    • 你通过在<provider>元素的属性值android:readPermission和android:writePermission来指定他们 。
    • 他们优先于android:permission中定义的权限。
    Path-level permission(路径级权限)

    Read, write, or read/write permission for a content URI in your provider.

    • You specify each URI you want to control with a <path-permission> child element of the <provider> element.
    • For each content URI you specify, you can specify a read/write permission, a read permission, or a write permission, or all three.
    • The read and write permissions take precedence over the read/write permission.
    • Also, path-level permission takes precedence over provider-level permissions.

    ContentProvider中内容URI的读、写或读/写权限。

    • 你通过设置<provider>的子元素< path-permission >来指定每个你想要控制的URI
    • 对于每个你指定的内容URI,你可以指定一个读/写权限,读权限,写权限,或者以上三者。
    • 读和写的权限要优先于读/写权限。
    • 同时,路径级权限要优先于ContentProvider级权限。

    Implementing the ContentProvider Class (实现ContentProvider类)

    The abstract class ContentProvider defines six abstract methods that you must implement as part of your own concrete subclass. All of these methods except onCreate() are called by a client application that is attempting to access your content provider:

    抽象类ContentProvider定义了六个抽象方法,你必须实现他们作为具体子类的一部分。所有这些方法(除了onCreate()方法外)都会被客户端应用程序调用,以访问你的ContentProvider:

    onCreate()

    Initialize your provider. The Android system calls this method immediately after it creates your provider. Note that the system doesn’t create your provider until a client tries to access it through a ContentResolver.

    该方法用来初始化你的ContentProvider。安卓系统在创建完你的ContentProvider之后会马上调用该方法。注意,只有在客户端程序视图通过ContentResolver来访问的时候,系统才会创建您ContentProvider。

    query()

    Retrieve data from your provider. Use the arguments to select the table to query, the rows and columns to return, and the sort order of the result. Return the data as a Cursor object.

    检索你的ContentProvider中的数据。使用参数来选择你要查询的表,和要返回的行和列,以及对结果的排序顺序。然后以一个游标对象作为数据返回。

    insert()

    Insert a new row into your provider. Use the arguments to select the destination table and to get the column values to use. Return a content URI for the newly-inserted row.

    插入一个新行到你的ContentProvider中。使用参数去选择目标表和和要使用的列。返回一个新插入的行的内容URI。

    update()

    Update existing rows in your provider. Use the arguments to select the table and rows to update and to get the updated column values. Return the number of rows updated.

    更新你的ContentProvider中的现有行。使用参数去选择表和要更新的行,还有获取更新的列值。返回更新的行的数量。

    delete()

    Delete rows from your provider. Use the arguments to select the table and the rows to delete. Return the number of rows deleted.

    从你的ContentProvider中删除行。使用参数去选择表和要删除的行。返回删除的行的数量。

    getType()

    Return the MIME type corresponding to a content URI.

    返回一个内容URI相应的MIME类型。

    Content Provider Implementation Notes(ContentProvider实现中需要注意的)

    • All of these methods except onCreate() can be called by multiple threads at once, so they must be thread-safe.

      • When a client in a different application invokes a provider method through a ContentResolver instance, the system automatically selects a thread from an interprocess thread pool to execute the method in the provider.
      • If a client component in the same process as the content provider invokes one of these methods through a ContentResolver instance, the system invokes the provider method in the same thread as the client component method call.

    所有这些方法(除了onCreate())都可以由多个线程调用,所以他们必须是线程安全的。

    • 当不同的应用程序的客户端通过一个ContentResolver实例来调用你的ContentProvider的方法时,系统会自动从一个进程间线程池中选择一个线程去执行ContentProvider的方法。
    • 如果一个客户端组件在和ContentProvider同一进程中通过ContentResolver实例来调用这些方法中的一个,系统会在和客户端组件方法调用的同一线程中调用你的ContentProvider的方法。
    • Avoid doing lengthy operations in onCreate().

      • The system automatically invokes onCreate() in the application process’s main thread when in needs to initialize the provider.
      • Therefore, defer initialization tasks until they are actually needed to avoid blocking your application’s main thread.

    避免在onCreate()方法中做冗长的操作。

    • 当需要初始化ContentProvider的时候,系统会在应用程序进程的主线程中自动调用onCreate()。
    • 因此,尽量去延迟初始化任务除非实际上必须这么做,这样可以避免程序的主线程被阻塞。
      • Although you must implement these methods, your code does not have to do anything except return the expected data type.

        • For example, you may want to prevent other applications from inserting data into some tables. To do this, you can ignore the call to insert() and return null.

      尽管你必须实现这些方法,你的代码除了要返回想要的的数据类型之外什么都不用做。

      • 例如,您可能想要阻止其他应用程序将数据插入一些表。要做到这一点,您可以忽略对insert()的调用和返回null。

    Implementing the onCreate() method(实现OnCreate()方法)

    • The Android system calls onCreate() when it starts up the provider.

      • You should perform only fast-running initialization tasks in this method, and defer database creation and data loading until the provider actually receives a request for the data.
      • If you do lengthy tasks in onCreate(), you block your application’s main thread, potentially causing an Application Not Responding (ANR) condition if your application contains other components.

    Android系统在启动ContentProvider时会调用onCreate()方法。

    • 在这个方法中你应该只执行快速初始化的任务,而把数据库的创建和数据的加载延迟到ContentProvider实际上接收到请求的数据的时候。
    • 如果你在onCreate()方法中执行太多的任务的话,将会阻塞应用程序的主线程,可能会导致“应用程序没有响应”(ANR)的情况,如果您的应用程序还包含其他组件的话。
    • For example, if you are using an SQLite database:

      • You can create a new SQLiteOpenHelper object in ContentProvider.onCreate().
      • You can place the calls to getWritableDatabase() or getReadableDatabase() in the implementation of the provider’s data access methods
      • As a result, any actual database creation or upgrade is deferred until the first data access, and that creation or upgrade takes place in the interprocess communication thread selected to process the call.

    比如您正在使用SQLite数据库:

    • 你可以ContentProvider.onCreate()方法中创建一个新的SQLiteOpenHelper对象。
    • 您可以将getWritableDatabase()或getReadableDatabase()的调用放在ContentProvider的数据访问方法实现的地方。
    • 因此,任何实际的数据库创建或升级都被延迟到第一个数据访问,并且这个数据库的创建或更新操作发生在专门用来处理这种调用的进程间通信线程中。

    Assuming that you are using a SQLiteOpenHelper subclass, the following could be sufficient to initialize your provider:

    假设你正在使用SQLiteOpenHelper的子类,那么对于初始化你的ContentProvider来说,下面这些就足够了:

    public boolean onCreate() {
          
          
            Context context = getContext();
            dbHelper = new TimelineHelper(context);
            return (dbHelper == null) ? false : true;
    }


    Implementing the query() Method(实现查询方法)

    • The ContentProvider.query() method must return a Cursor object, or if it fails, throw an Exception.

      • If you are using an SQLite database as your data storage, you can simply return the Cursor returned by one of the query() methods of the SQLiteDatabase class.
      • If the query does not match any rows, return a Cursor instance whose getCount() method returns 0.
      • Return null only if an internal error occurred during the query process.

    ContentProvider.query()方法必须返回一个游标对象,如果查询失败的话会抛出异常。

    • 如果你正在使用SQLite数据库来存储数据,您可以简单地返回一个由SQLiteDatabase类的query()方法返回的游标对象。
    • 如果查询不到任何匹配的行,那就返回一个游标实例,该实例的getCount()方法返回0。
    • 只有在查询过程中发生内部错误才返回null
    • If you aren’t using an SQLite database as your data storage, use one of the concrete subclasses of Cursor.

      • For example, the MatrixCursor class implements a cursor in which each row is an array of Object. With this class, use addRow() to add a new row.

    如果你没有在用SQLite数据库存储数据的话,使用游标的一个具体子类。

    • 例如,MatrixCursor类实现了一个游标,它的每一行都是一个数组对象。这个类可以调用addRow()方法来添加一个新行。
    • Remember that the Android system must be able to communicate the Exception across process boundaries. Android can do this for the following exceptions that may be useful in handling query errors:

      + IllegalArgumentException:: You might choose to throw this if your provider receives an invalid content URI UnsupportedOperationException:: You might choose to throw this is you decide not to support a method, such as delete() NullPointerException:: This type of runtime exception will be reflected to the client if not caught and handled in the provider

    记住,Android系统一定能够跨进程沟通异常。对于下面这些异常,Android系统是可以做些有用操作的,在处理查询错误的时候:

    IllegalArgumentException::如果你的ContentProvider接收了一个无效的内容URI参数,你可以抛这个异常;

    UnsupportedOperationException:如果你决定不支持某个方法比如delete(),那可以抛这个异常;

    NullPointerException:这种运行时异常将会反映给客户端,如果在CotentProvider中没有捕获并处理的话。


    The SQLiteQueryBuilder Helper Class

    • Implementing the query() method can be particularly complex.

      • The method supports several arguments, almost all of which are optional.
      • You often need to parse the Uri argument to include path components such as the ID in the query.
      • You might have default query parameters to use if the client doesn’t specify them.
      • You might have additional query parameters "hard-coded" into your provider implementation that you need to merge with arguments provided by the client.
      • You might need to perform joins or other manipulation of the underlying data to present a synthesized view to the client.

    实现查询()方法可以特别复杂。

    • 该方法支持多种参数,几乎所有的参数是可选的。
    • 你经常需要把Uri参数解析成包含路径成分比如查询中的ID
    • 你可能会有默认的查询参数使用,如果客户端不指定它们的话。
    • 你可能有额外的查询参数以“硬编码”的形式存在于你的ContentProvider的实现中,那样你会需要将客户端提供的参数合并。
    • 您可能需要对底层数据进行连接或其他操作来合成视图呈现给客户端。
    • To simplify the creation of a complex query, Android provides the SQLiteQueryBuilder class. It supports features such as:

      • Assembling query parameters incrementally
      • Merging multiple query parameters, such as several portion of a WHERE clause
      • Performing joins
      • Performing projection mapping, to map column names that the caller passes into query to database column names

      The example implementation of a query() shown next provides an example of using SQLiteQueryBuilder.

    为了简化一个复杂查询的创建,Android系统提供了SQLiteQueryBuilder类。它支持的功能有:

    • 逐步组装查询参数
    • 合并多个查询参数,比如一个WHERE子句的几部分
    • 执行连接
    • 执行projection映射,将调用者传来的列名映射到数据库中的列名

    查询的示例实现()使用SQLiteQueryBuilder下提供了一个示例所示。

    Example of Implementing a query() Method(查询方法的实现示例)

    public Cursor query(Uri uri, String[] projection,
                                            String selection, String[] selectionArgs,
                                            String sort) {
          
          
    
            SQLiteDatabase db = dbHelper.getWritableDatabase();
    
            // A convenience class to help build the query
            SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
    
            qb.setTables(T_TIMELINE);
    
            switch (uriMatcher.match(uri)) {
          
          
            case STATUS_DIR:
                    break;
            case STATUS_ITEM:
                    // If this is a request for an individual status, limit the result set to that ID
                    qb.appendWhere(StatusContract.Columns._ID + "=" + uri.getLastPathSegment());
                    break;
            default:
                    throw new IllegalArgumentException("Unsupported URI: " + uri);
            }
    
            // Use our default sort order if none was specified
            String orderBy = TextUtils.isEmpty(sort)
                                       ? StatusContract.Columns.DEFAULT_SORT_ORDER
                                       : sort;
    
            // Query the underlying database
            Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);
    
            // Notify the context's ContentResolver if the cursor result set changes
            c.setNotificationUri(getContext().getContentResolver(), uri);
    
            // Return the cursor to the result set
            return c;
    }


    Implementing the insert() Method(实现插入方法)

    • The insert() method adds a new row to the appropriate table, using the values in the ContentValues argument.

      • If a particular column name is not provide in the ContentValues argument, you might decide to provide a default value for it either in your provider code or in your database schema.

    insert()方法可以添加一个新行到适当的表中,它使用ContentValues参数中的值。

    • 如果一个特定的列名没有在ContentValues参数中提供,你可能需要为它提供一个默认值,不管是在你的ContentProvider代码中还是在你的数据库模式中。
    • The insert() method should return the content URI for the new row.

      • To construct this, append the new row’s _ID (or other primary key) value to the table’s content URI, using withAppendedId().
      • You can return null if the insertion fails, or if you decide not to support this operation.

    insert()方法应该返回新插入行的内容URI。

    • 为了构造这个,可以使用withAppendedId()方法来将新行的_ID值(或其他主键)添加到内容URI结尾处。
    • 如果插入失败或者你想不支持此类操作的话,你都可以返回null

    Example of Implementing an insert() Method(插入方法的实现示例)

    public Uri insert(Uri uri, ContentValues initialValues) {
          
          
            Uri result = null;
    
            // Validate the requested Uri
        if (uriMatcher.match(uri) != STATUS_DIR) {
          
          
            throw new IllegalArgumentException("Unsupported URI: " + uri);
        }
    
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        long rowID = db.insert(T_TIMELINE, null, initialValues);
    
        if (rowID > 0) {
          
          
            // Return a URI to the newly created row on success
            result = ContentUris.withAppendedId(StatusContract.CONTENT_URI, rowID);
    
            // Notify the Context's ContentResolver of the change
            getContext().getContentResolver().notifyChange(result, null);
        }
        return result;
    }


    Implementing the delete() method(实现删除方法)

    • The delete() method should delete rows from your provider.

      • Use the selection arguments to select the table and the rows to delete.
      • Return the number of rows deleted.
      • You might decide always to return 0 if you choose not to support this operation, or throw an UnsupportedOperationException.

    使用delete()方法可以从你的ContentProvider中删除行。

    • 使用selection参数去选择表和要删除的行。
    • 返回被删除的行的数量。
    • 如果你不想支持该操作的话,你可以总是返回0,或者直接抛出UnsupportedOperationException异常。
    public int delete(Uri uri, String where, String[] whereArgs) {
          
          
        SQLiteDatabase db = dbHelper.getWritableDatabase();
            int count;
    
            switch (uriMatcher.match(uri)) {
          
          
            case STATUS_DIR:
                    count = db.delete(T_TIMELINE, where, whereArgs);
                    break;
            case STATUS_ITEM:
                    String segment = uri.getLastPathSegment();
                    String whereClause = StatusContract.Columns._ID + "=" + segment
                                                       + (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : "");
                    count = db.delete(T_TIMELINE, whereClause, whereArgs);
                    break;
            default:
                    throw new IllegalArgumentException("Unsupported URI: " + uri);
            }
    
            if (count > 0) {
          
          
            // Notify the Context's ContentResolver of the change
            getContext().getContentResolver().notifyChange(uri, null);
            }
            return count;
    }


    Implementing the update() method(实现更新方法)

    • The update() method updates existing rows in your provider.

      • It accepts the same ContentValues argument used by insert(), and the same selection arguments used by delete() and query().
      • This might allow you to re-use code between these methods.
      • Return the number of rows deleted.
      • You might decide always to return 0 if you choose not to support this operation, or throw an UnsupportedOperationException.

    使用update()方法可以更新你的ContentProvider中现有的行。

    • 它接受和insert()方法使用的相同的ContentValues参数,以及和delete() 、query()方法使用的相同的selection参数。
    • 这样你就可以在这些方法之间重用一些代码。
    • 返回被修改的行的数量。
    • 如果你不想支持该操作的话,你可以总是返回0,或者直接抛出UnsupportedOperationException异常。
    public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
          
          
        SQLiteDatabase db = dbHelper.getWritableDatabase();
            int count;
    
            switch (uriMatcher.match(uri)) {
          
          
            case STATUS_DIR:
                    count = db.update(T_TIMELINE, values, where, whereArgs);
                    break;
            case STATUS_ITEM:
                    String segment = uri.getLastPathSegment();
                    String whereClause = StatusContract.Columns._ID + "=" + segment
                                                       + (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : "");
                    count = db.update(T_TIMELINE, values, whereClause, whereArgs);
                    break;
            default:
                    throw new IllegalArgumentException("Unsupported URI: " + uri);
            }
    
            if (count > 0) {
          
          
            // Notify the Context's ContentResolver of the change
            getContext().getContentResolver().notifyChange(uri, null);
            }
            return count;
    }

    Registering the Provider in the Application Manifest(在清单文件中注册ContentProvider)

    Your content provider must be registered in your application’s manifest by including a <provider> element within the <application> element.

    你的CotentProvider必须在清单文件中注册,在 <application>节点下用<provider>表示

    android:name

    Required. The class name implementing your provider

    这个属性是必须的,你的ContentProvider的实现类

    android:authorities

    Required. The URI authorities identifying your provider. Typically, you provide only one, but you can provide a semicolon-separated list of authorities.

    这个属性是必须的,URI授权可以识别你的ContentProvider。通常情况下你只需提供一个,但是你也可以提供一系列授权,中间以分号分隔即可

    android:permission

    Optional. The name of a permission that clients must have to read or write the content provider’s data. If provided, android:readPermission or android:writePermission take precedence over this attribute.

    可选。这里要写客户端读写ContentProvider中的数据所需权限的名称。如果提供了这个权限的话,android:readPermission或android:writePermission属性定义的权限要优先于这个属性定义的权限。

    android:readPermission

    Optional. The name of a permission that clients must have to read the content provider’s data.

    可选。这里要写客户读取ContentProvider中的数据所需权限的名称。

    android:writePermission

    Optional. The name of a permission that clients must have to write the content provider’s data.

    可选。这里要写客户端写入数据到ContentProvider中所需权限的名称。

    For example:(举个栗子)

    <provider
            android:name="StatusProvider"
            android:authorities="com.marakana.android.yamba.provider"
            android:writePermission="com.marakana.android.yamba.permission.MODIFY_STATUS_DATA"
            />


下一篇链接:Android Content Provider Tutorial--安卓内容提供者系列5--Loader用法

猜你喜欢

转载自blog.csdn.net/woshiwangbiao/article/details/52575250
今日推荐