标签:
写在前面的话:说两点。1、很荣幸自己的两篇文章《 Android官方文档之App Components(Intents and Intent Filters)》、《Android官方文档之App Components(Common Intents)》被郭霖老师转载了,一方面说明我的博客内容得到了认可,另一方面也鞭策我继续写出质量更高的博文;2、最近在翻译官方文档,今天翻墙一看,发现页面改版了,而且居然默认显示了中文的文档(其实改版以前也有官方的中文文档,只是默认显示英文而已,另外中文官方文档只翻译了一部分,还有一些未翻译),我就在想,到底还是否继续翻译,我的答案是肯定的:之前已经翻译了10多篇文档,在这期间,我不仅提升了英文的阅读水平,还对Android的知识有了新的收获,所以我将继续翻译下去,当然了,如果遇到不会翻译的地方我就会查看官方中文文档,我争取翻译得比官方文档还好:)
Content Provider管理着应用程序需要访问的数据仓库。这需要您在程序中继承ContentProvider类,并在manifest中注册组件。该类就是其他应用程序与您的应用程序数据库之间的接口(interface between your provider and other applications)。通过ContentProvider,其他应用程序可以对本应用的数据库进行方便的操作,这需要使用ContentResolver,有关这部分的内容,您可以参考我翻译的官方文档:《Android官方文档之Content Providers》;如需访问本文的官方原文,您可以点击这个链接:《Creating a Content Provider》。
如您的程序需要向外界应用提供以下内容之一,那么程序中需要创建ContentProvider:
需要向其他应用程序提供复杂的数据或文件( 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)。
!请注意:若只是应用需要访问自身SQLite数据库,那么无需创建ContentProvider。
以下是创建provider的方法:
为您的应用程序设计原始存储。ContentProvider以如下两种方式提供数据:
文件数据(File data):在应用程序的的私有空间中,数据以不同形式的文件存储,如照片(photos)、音频(audio) 、视频(videos)等。为了能让其他应用程序读取您的程序中的这些文件,需要provider作为媒介。
结构化数据(”Structured” data):数据存储于数据库、数据或其他类似的结构中。存储的数据支持行列表的形式(Store the data in a form that’s compatible with tables of rows and columns)。每行代表一个实体(entity),如个人信息等。每列表示每个实体的不同属性,如人的身高、体重等。这些表结构及其数据信息可以存储于SQLite数据库中。
实现ContentProvider中的方法;
为ContentProvider定义authority字符串,该字符串包含了content URI以及表中列的字段。若您希望ContentProvider可以处理intent,那么还需定义intent的action、data、flag等。并为provider定义其他应用程序需要访问它的权限。
Android中提供了如下存储技术:
Android提供了SQLite数据库API以访问面向表的数据存储(to store table-oriented data),SQLiteOpenHelper可帮助您创建数据库,而SQLiteDatabase类是访问数据库的基础类。provider提供给外界程序的内容表现为一张或多张表。
Android提供了多个面向文件存储的API,关于文件存储,您可以参考这个链接 Data Storage,若您希望创建一个访问媒体文件(如音乐或多媒体)的provider,那么该provider还能提供访问表和其他文件的方法。
使用java.net或android.net包中的API可以访问网络数据,您可以将网络数据同步到本地(如同步到本地的数据库中),并将本地数据以表或文件的形式提供给程序。
BaseColumns._ID作为其字段。因为当需要将表中数据绑定到ListView上时,ListView需要一个列名为_ID的字段。content URI是一个URI对象,它指定了provider中的某个数据(集、表),Content URIs 包含两部分:authority和path,authority用于标识provider的唯一性,path用于指定该provider中的某张表或是某个文件。content URI还有一个id部分是可选的,它指定了表中的特定行。ContentProvider类中每一个访问数据的方法(增删改查)都会回传一个URI参数,用于具体指定您需要访问哪张表(哪一行)或是哪个文件。
一个provider通常只有一个authority,并且遵循android系统内部命名规范。为了防止authority之间的命名冲突,您应当使用公司的Internet域名的倒序来作为authority的前缀,或者使用应用的包名作为前缀。比如,您的应用程序包名为com.example.<appname>,那么应当为provider的authority设置为com.example.<appname>.provider。
path用于指定provider中的某张表。比如,继续按上例来说,若provider中包含两张表:table1、table2,其content URI应分别为com.example.<appname>.provider/table1、com.example.<appname>.provider/table2。您无需为每级path都设置一张表。
通常,在content URI后追加ID可以访问该URI所指向的provider中的某张表的某一行数据信息。该ID将匹配表中名为“_ID”的字段中的值,并访问该值所在的行( providers match the ID value to the table’s _ID column, and perform the requested access against the row that matches)。
其他应用程序访问provider的常见情景是:app通过ContentResolver访问content URI指定的ContentProvider中的某张表(或表中的某几行),并返回一个Cursor对象,再利用CursorAdapter将Cursor对象作为数据源绑定至ListView上。而CursorAdapter要求绑定的Cursor必须包含_ID字段。
用户可以在UI上查询或修改ListView中的项,系统将查询该项对应的Cursor中的行,并将该行的_ID追加至content URI后访问provider中某张表的某一行,以达到查询或修改该行数据的目的。
为了区分不同content URI 的访问请求,系统提供了协助provider的UriMatcher类,并将content URI和特定的整型值一一对应(maps content URI “patterns” to integer values)。您可以使用switch语句处理不同整型值对应的content URI所需查询的内容。content URI使用通配符查询传递来的查询URI:
比如说,您可能会使用如下content URI来查询表中的某张表中的某些数据:
content://com.example.app.provider/table1: 查询table1;
content://com.example.app.provider/table2/dataset1:查询table2中的dataset1;
content://com.example.app.provider/table2/dataset2:查询table2中的dataset2;
content://com.example.app.provider/table3: A table called table3:查询table3;
content://com.example.app.provider/table3/1:查询table3中行ID为1的条目;
在provider中,下面的写法可以匹配第一个URI:
content://com.example.app.provider/*
在provider中,下面的写法可以匹配第二个和第三个URI:
content://com.example.app.provider/table2/*
在provider中,下面的写法可以匹配第五个URI:
content://com.example.app.provider/table3/#
下面演示了UriMatcher的用法,addURI()方法将传入的URI与一个唯一的整型标识对应,match()方法将返回URI对应的整型值,示例如下:
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
}
ContentProvider用于处理其他应用程序的访问请求,而最终将检索结果传给ContentResolver,ContentProvider的主要方法如下:
ContentProvider是抽象类,该类中包含6个抽象方法需要您实现,除了onCreate()方法外,其他应用程序欲访问您定制的ContentProvider时,其余的5个方法均被调用(All of these methods except onCreate() are called by a client application that is attempting to access your content provider)。
query():从provider中查询数据,并将结果以Cursor对象返回;
insert():向provider的某张表中插入一行新的数据,方法回传的Uri参数用于指向需要插入的表,而ContentValue参数用于回传插入的内容,该方法返回新插入行的Uri地址;
update():用于修改某些行,并返回修改的行数;
delete():删除某些行,并返回删除的行数。
getType():返回content URI对应的MIME 类型(Return the MIME type corresponding to a content URI),该方法的具体含义请您参考后续章节。
onCreate():该方法用于初始化ContentProvider,当ContentProvider被实例化后,该方法将被立刻回调。!请注意:只有ContentResolver试图访问您的ContentProvider,该实例才会被创建(Notice that your provider is not created until a ContentResolver object tries to access it)。
在ContentResolver中,包含着与上述同名的方法及方法签名(these methods have the same signature as the identically-named ContentResolver methods)。
为了实现上述方法,您需要考虑这些事:
除了onCreate()方法外,其余方法均可在多个线程中同时调用,这就需要保证线程安全(can be called by multiple threads at once, so they must be thread-safe)。
避免在onCreate()方法中做耗时操作(Avoid doing lengthy operations in onCreate())。当方法中涉及的资源用到时再加载(Defer initialization tasks until they are actually needed)。
尽管您必须实现这些方法,但是您可以不做任何操作。比如说,当您需要向表中插入数据时,若只是在insert()中返回0,则数据插入操作不会成功。
ContentProvider.query()方法返回一个Cursor对象,若返回失败,将方法将抛出一个异常。
若您是使用SQLite数据库存储数据,那么调用SQLiteDatabase的query()方法返回的Cursor对象可以直接作为该方法的返回值(you can simply return the Cursor returned by one of the query() methods of the SQLiteDatabase class)。若未查询到匹配结果,那么返回的Cursor对象中,其getCount()方法应返回0。若出现异常,Cursor对象应返回null。
若您并没有使用SQLite数据库存储数据,那么应根据具体数据的结构,使用系统提供的Cursor子类对象作为返回值。比如,MatrixCursor是Cursor的一个子类,它可以指向每一行都是一个Object对象数组的矩阵结构(each row is an array of Object)。调用该类的addRow()方法可添加一行数据。
!请注意:Android 系统必须能够跨进程边界传播 Exception,Android 可以为以下异常执行此操作,这些异常可能有助于处理查询错误:1、IllegalArgumentException:您可以选择在提供程序收到无效的内容 URI 时引发此异常;2、NullPointerException:空指针异常
insert()方法可为指定的表中添加一行数据,使用回传参数ContentValues配置值。若ContentValues的键并没有表中的任何字段与之对应,您应当设定一个默认值。
删除指定表中的某一行或多行数据。方法返回int值,表示删除的行数。
方法利用ContentValues以键值对的方式修改指定表中的数据。返回int型变量,表示修改的行数。
当初始化provider时,系统将回调onCreate()方法,在该方法中不要执行耗时操作,您可以在resolver访问该provider时,再创建数据库,并延迟数据加载。
比方说,您可以在ContentProvider.onCreate()方法中创建一个SQLiteOpenHelper对象,第一次打开数据库时创建SQL表,实现方式是调用getWritableDatabase()方法,调用该方法会立即触发SQLiteOpenHelper.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 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);
}
}
ContentProvider MIME Types)ContentProvider中包含了两种返回MIME类型的方法:
getType():该方法必须实现(One of the required methods that you must implement for any provider);
getStreamTypes():若您的provider提供了文件访问,需实现该方法(A method that you’re expected to implement if your provider offers files.
)。
getType()方法返回值为String类型,表示传入的Uri对象所对应的MIME类型。回传的Uri参数既可以是带有通配符的Uri,也可以是一个具体的Uri。
常见的MIME类型有 text,、HTML、JPEG 等。如需查看MIME的全部类型及解析,您可以点击这个链接:《IANA MIME Media Types》。
对于getType()方法返回的MIME类型,应遵循以下格式:
Type 部分:vnd
Subtype 部分:
若回传的Uri指向表的某一行,那么格式应为:android.cursor.item/
若回传的Uri指向表的多行(一行以上),那么格式应为:android.cursor.dir/
Provider-specific 部分:vnd.<name>.<type>。其中<name>应是唯一的,<type>能体现指向Uri的唯一性。好的做法是将<name>命名为公司名的倒序或是应用程序的包名,将<type>命名为指定的表名。
如您的provider的authority是com.example.app.provider,若需要访问table1中的多行,那么MIME类型可以这样写:
vnd.android.cursor.dir/vnd.com.example.provider.table1
如需访问table1中的单行,MIME类型可以这样写:
vnd.android.cursor.item/vnd.com.example.provider.table1
若provider可以访问文件,应该实现String[] getStreamTypes (Uri uri, 方法。该方法返回一个
String mimeTypeFilter)String数组,数组中的每一项表示传入的Uri所指向的文件类型(for the files your provider can return for a given content URI),若其他应用程序只对某个特定类型的文件感兴趣,那么应给getStreamTypes()方法的第二个参数传入感兴趣的文件的MIME类型。
比如说,provider提供了 .jpg、.png 和 .gif类型的图像文件,其他应用程序调用ContentResolver.getStreamTypes()方法并传入过滤参数image/*,那么系统将返回如下所示的String数组:
{ "image/jpeg", "image/png", "image/gif"}
若应用程序只对.jpg格式的文件感兴趣,那么可以传入*\/jpeg作为过滤参数,则方法将返回
{"image/jpeg"}
若provider提供的文件类型无法匹配您传入的过滤文件类型,那么方法应返回null(If your provider doesn’t offer any of the MIME types requested in the filter string, getStreamTypes() should return null)。
Contract类是一个用public final关键字修饰的类,存在于provider所在的应用程序中,该类的内部定义了provider中需要使用到的URIs、column names、 MIME types、 meta-data等字符串常量。设置常量的好处是:当需要修改查询的范围或内容时,只需修改这个类中的字符串即可。
设置Contract类的另一个好处是协助开发者对字符串常量的记忆( mnemonic names for its constants)。
默认情况下,存储在设备内部存储的文件对您的应用货provider是私有的;
程序中创建的SQLiteDatabase数据库只有您自己的程序和provider可以操作;
默认情况下,外部存储中的文件是公开的(data files that you save to external storage are public and world-readable),您无需使用provider访问外存中的文件。因为这些文件使用系统提供的API就能访问。
所有应用程序都可以访问您的provider。默认情况下,您的provider没有任何权限。可以在manifest文件的<provider>标签中修改默认属性。您可以设置:其他应用是否可以访问provider提供的所有内容,或仅能访问特定的表,甚至只能访问某个指定的行。
这需要在manifest中设置<permission>标签,并由android:name属性指定,指定的权限应具有唯一性。如为provider添加只读的权限:
com.example.app.provider.permission.READ_PROVIDER
下面描述了provider权限的作用域,作用域越小的权限,优先级越高(More fine-grained permissions take precedence over ones with larger scope):
统一的读写provider级别权限(Single read-write provider-level permission):在<provider>标签中由android:permission属性指定。
单独的读写provider级别权限(Separate read and write provider-level permission):在<provider>标签中由android:readPermission 和android:writePermission属性指定。该权限高于上面的统一读写权限(They take precedence over the permission required by android:permission)。
path级别的权限(Path-level permission):在provider的content URI中设置只读、只写、或可读可写的权限。在<provider>的子标签<path-permission>中设定您需要控制的每一个URI。您可以为每一URI指定只读权限、或只写权限、或可读可写权限,其中只读或只写得权限高于可读可写权限。而path级别权限高于provider级别权限。
<provider> 标签(The <provider> Element)您的程序中定义的ContentProvider需要在manifest中使用<provider> 标签注册。该标签中可配置的内容如下:
Authority:使用android:authorities属性配置。该属性为provider提供一个唯一的标识。
Provider class name:使用android:name属性定义。属性值为定制的ContentProvider的全限定类名。
Permissions:指定了其他应用需要访问该provider所需声明的权限。
android:grantUriPermssions:临时权限;
android:permission:provider范围内的读写权限;
android:readPermission:provider范围的只读权限;
android:writePermission:provider范围的只写权限。
Startup and control attributes:下列属性决定了系统何时以及如何启动provider、provider的特性、以及运行时设置:
android:enabled:是否允许系统启动provider;
android:exported:是否允许其他应用程序访问provider;
android:initOrder:指定同一进程中,相对于其他provider的启动顺序;
android:multiProcess:是否允许在与client端相同的进程中启动provider;
android:process:provider运行的进程名;
android:syncable:provider中的数据与服务器上的数据同步。
Informational attributes:为provider提供可选的图标和标题。
android:icon:指定一个drawable资源,该图标出现在设置 > 应用 > 全部 中应用列表内的provider标签旁;
android:label:描述provider(和)或其数据的信息标签。 该标签出现在设置 > 应用 > 全部中的应用列表内。
Android官方文档之Creating a Content Provider
标签:
原文地址:http://blog.csdn.net/vanpersie_9987/article/details/51455316