Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 789|回复: 0

android content provider(一)——使用系统的provider - Android学习

[复制链接]

该用户从未签到

发表于 2011-10-27 08:14:36 | 显示全部楼层 |阅读模式
事先声明:本帖翻译了关于content provider的官方文档的上半部分, 下一篇将继续翻译下半部分——创建自己的content provider。

    Content providers 为所有应用程序提供数据存取的入口,是应用之间分享数据的唯一途径,因为没有为所有Android packages提供公共存储区。
    Android使用许多content providers来传输常见数据类型(如audio,video,images,personal contact information等)。相关信息可查阅android.provider包。可以查询这些providers所包含的数据(当然,有些数据需要适当的读取权限)。
    有两种方法公布自己的数据:创建自己的content provider(ContentProvider的子类)或者将数据添加到已存在的provider(需要管理相关数据类型,并且具有写入权限)。
    该文档介绍如何使用content providers。在简短的关于基础信息的讨论之后,我们来探究一下如何针对content provider进行查询和修改,如何创建自己的content provider。

一、Content Provider基础

    content provider如何在后台存储数据取决于其设计者。所有的content providers都实现了同一个公共接口,该接口可以对数据进行增删改查。我们通常通过ContentResolver对象间接地使用该接口,而ContentResolver对象可以通过在Activity或者其他应用组件的实现子类中调用getContentResolver()方法得到。
        ContentResolver cr = getcontentResolver();
    得到引用之后即可使用Contentresolver的方法和任何感兴趣的content provider进行交互。
    当开始一个查询时,Android系统会为该查询指定一个特定的content provider,并且确保该provider的启动和运行。所有的ContentProvider对象由系统管理,我们无需关心,甚至不必直接处理该对象。通常,每种类型的ContentProvider只有一个实例,但其可以和不同应用不用进程的多种多样的ContentResolver进行交互。这种进程间的交互由ContentResolver和ContentProvider类来完成。

1、数据类型

    Content provider的数据类型以数据库模型中的简单表的形式存在,表的每一行是一条记录,每一列是特定数据类型和含义的数据。例如,用户信息和电话号码的表可能显示如下:

        _ID        NUMBER         NUMBER_KEY         LABEL                NAME                TYPE
        13   (425) 555 6677   425 555 6677   Kirkland office         Bully Pulpit     TYPE_WORK
        44   (212) 555-1234   212 555 1234   NY apartment        Alan Vain        TYPE_HOME
        45   (212) 555-6657   212 555 6657   Downtown office   Alan Vain         TYPE_MOBILE
        53   201.555.4433     201 555 4433   Love Nest               Rex Cars        TYPE_HOME

    每一条记录包括一个数字类型的_ID来唯一标识表中的每一条记录,另外,ID还可以用于匹配相关表中的记录,比如,在A表中查询一个人的电话号码而在B表中查询他的图片。
    每一个查询返回一个Cursor对象,该对象可以再行和列间移动来读取对应的值,并且拥有专门的方法读取每种类型的数据。因此,必须知道每一个域包含数据的类型才能读取(稍后将讲解更多内容)。

2、URIs

    每一个content provider都有一个URI(被包装为Uri对象)来唯一标识其数据集合。为了控制多种多样的数据集合(不同的表),content provider为每个数据集合使用单独的URI。所有provider的URI以“content://”开始,“content”标识该数据被content provider控制。
    如果定义一个content provider,为了简化客户端代码、便于升级更新,最好同时为URI定义一个常量,Android平台为每一个provider定义了CONTENT_URI常量。比如,匹配电话号码表的URI和保存图片表的URI(都由Contacts content provider控制)为:
        android.provider.Contacts.Phones.CONTENT_URI
        android.provider.Contacts.Photos.CONTENT_URI
    URI的常量用于所有与content provider的交互,每一个ContentResolver方法把URI作为其第一个参数,来指示与哪一个provider对话、选择那张表。

二、查询一个Content Provider

    需要三条信息来查询content provider

        #指定某一provider的URI
        #想要得到的数据域的名称
        #数据域的类型

    如果想要查询特定的记录,还需要知道该记录的ID。

1、进行查询

    查询content provider有两种方式:contentResolver.query()和Activity.manageQuery()。两种方法的参数相同,返回值也都是Cursor对象。不过,manageQuery()方法将该Cursor的生命周期交由activity管理,而这种Cursor与另外一种有细微差别:activity pauses 不加载,activity start 重新加载。未被管理的Cursor也可以通过调用Activity.startManagingCursor()来进行管理。
    第一个参数是provider URI——指定一个特定的ContentProvider和数据集合的常量 Content_URI。如果想查询特定ID的记录,可以再URI后添加ID的值,比如,ID=23,URI如下:
        content://..../23
    有一些辅助方法可以很简单想URI中的添加ID值,最主要的方法有ContentUris。withAppendedId()和Uri.withAppendedPath(),这两个都是静态方法,都返回添加了ID的URI对象。例如,如果从通讯录数据库中查询ID为23的记录,应该构造一个如下的查询:

----------------------------------------------------------------------------------------------------------------------
import android.provider.Contacts.People;
import android.content.ContentUris;
import android.net.Uri;
import android.database.Cursor;

// Use the ContentUris method to produce the base URI for the contact with _ID == 23.
Uri myPerson = ContentUris.withAppendedId(People.CONTENT_URI, 23);

// Alternatively, use the Uri method to produce the base URI.
// It takes a string rather than an integer.
Uri myPerson = Uri.withAppendedPath(People.CONTENT_URI, "23");

// Then query for this specific record:
Cursor cur = managedQuery(myPerson, null, null, null, null);
----------------------------------------------------------------------------------------------------------------------

    query()和managedQuery()方法的其他参数用来更详细地界定查新结果,如下:

        #返回数据的列名。若为null则返回所有列。否则只返回被列出的列。所有平台提供的content provider都定义了各个列的常    量,如,android.provider.Contacts.Phones类为phone表的每个列名定义了常量——_ID,NUMBER,NUMBER_KEY,NAME等。
        #详细规定返回哪些行的过滤器,类似于SQL的WHERE子句(不包括“WHERE”)。若为null则返回所有行(除非URI限制了只返回    特定的行)。
        #选择参数。Selection arguments。
        #排序指令,类似于SQL的ORDER BY子句(不包括“ORDER BY”)。若为null则采取默认顺序,可能是无序的。
    下面来看一个选择特定列的例子:

----------------------------------------------------------------------------------------------------------------------
import android.provider.Contacts.People;
import android.database.Cursor;

// Form an array specifying which columns to return.
String[] projection = new String[] {
                             People._ID,
                             People._COUNT,
                             People.NAME,
                             People.NUMBER
                          };

// Get the base URI for the People table in the Contacts content provider.
Uri contacts =  People.CONTENT_URI;

// Make the query.
Cursor managedCursor = managedQuery(contacts,
                         projection, // Which columns to return
                         null,       // Which rows to return (all rows)
                         null,       // Selection arguments (none)
                         // Put the results in ascending order by name
                         People.NAME + " ASC");
----------------------------------------------------------------------------------------------------------------------

    该查询将从Contacts contentprovider的People表中返回数据,包括name,primary phone number和ID,同时也返回了记录的条数(_COUNT)。
    表示列名的常量定义在不同的接口中——_ID和_COUNT在BaseColumns中,NAME 在PeopleColumns中,NUMBER在PhoneColumns中。Contacts.People类实现了这些接口,所以以上代码可以使用People类查询这些列。

2、查询返回的结果

    一个查询将返回零或多条数据库记录。列名、默认排序和数据类型特定于每一个content provider,但每一个provider都有_ID列,来唯一标识每一条记录。每个provider同样可以返回_COUNT列表示记录的条数,该列的值在每一条记录中都相同。

    下面是前一部分查询的返回集合:
        _ID   _COUNT    NAME                  NUMBER
        44        3        Alan Vain        212 555 1234
        13        3        Bully Pulpit        425 555 6677
        53        3        Rex Cars        201 555 4433

    检索到的数据由Cursor对象表示,利用Cursor对象可以向前或者向后迭代结果集。Cursor只能用来读取数据,添加、修改或者删除数据必须用ContentResolver对象来完成。

3、读取检索到的数据

    查询返回的Cursor对象提供了结果集合的入口。如果使用ID查询特定的记录,则结果集中只包含一条记录,否则,可能包含很多条记录(如果不匹配,也可能没有数据)。必须知道某个域的数据类型才可以读取该域,因为Cursor对象为每种数据类型的数据提供了不同的读取方法——例如getString(),getInt()和geetFloat()。(不过,对于多数类型来说,如果调用读取字符串的方法,Cursor将返回数据的字符串表示。)Cursor允许通过列的所以获取列名,反之亦可。
     
    下面的例子承接上面查询的代码,说明了如何读取姓名和电话号码这两列数据。

----------------------------------------------------------------------------------------------------------------------
import android.provider.Contacts.People;

private void getColumnData(Cursor cur){
    if (cur.moveToFirst()) {

        String name;
        String phoneNumber;
        int nameColumn = cur.getColumnIndex(People.NAME);
        int phoneColumn = cur.getColumnIndex(People.NUMBER);
        String imagePath;
     
        do {
            // Get the field values
            name = cur.getString(nameColumn);
            phoneNumber = cur.getString(phoneColumn);
           
            // Do something with the values.
            ...

        } while (cur.moveToNext());

    }
}
----------------------------------------------------------------------------------------------------------------------

    如果查询会返回二进制数据,比如图片或声音,该数据可能直接存储在表中,也可能将指定该资源的content:URI字符串存入表中。通常较小的数据(比如20~50K或者更小)经常直接存入表中,可以调用Cursor.getBlob()读取,放回byte数组。
    如果表的项目是content:URI,不要直接打开读取数据(权限问题可能导致操作失败)。应该调用ContentResolver.openInputStream()获取InputStream对象来读取数据。

三、修改数据

    content provider 持有的数据可以进行如下修改操作:
        # 添加新纪录
        # 在已有记录中添加新值
        # 批量更新已有数据
        # 删除记录

    所有的数据修改操作都由ContentResolver的方法完成。有一些content provider进行写入数据比读取数据需要更多的权限,如果没有权限则会导致ContentResolver写入失败。

1、添加记录

    添加新纪录至content provider,首先要在ContentValues对象中创建一组键值(key-value)对,每一个key对应content provider的一个列名,value对应该列的值。然后调用ContentResolver.insert()并且传入对应provider的URI和ContentValues对象。该方法返回新纪录的full URI——也就是该provider的URI+新纪录的ID。可以用返回的URI查询新纪录,进一步修改记录。以下是一个例子:
----------------------------------------------------------------------------------------------------------------------
import android.provider.Contacts.People;
import android.content.ContentResolver;
import android.content.ContentValues;

ContentValues values = new ContentValues();

// Add Abraham Lincoln to contacts and make him a favorite.
values.put(People.NAME, "Abraham Lincoln");
// 1 = the new contact is added to favorites
// 0 = the new contact is not added to favorites
values.put(People.STARRED, 1);

Uri uri = getContentResolver().insert(People.CONTENT_URI, values);
----------------------------------------------------------------------------------------------------------------------

2、添加新值

    如果某一条记录已经存在,就可以向其中添加新的信息或者修改已有信息。例如,上例的下一步将向新纪录中添加联系人信息——像电话号码、IM或者e-mail地址。
    向联系人数据库(Contacts database)中的记录添加信息最好的方式是将表名附加到被操作记录的URI上,然后使用附加后的URI添加新数据。每一个联系人表(Contacts table)为之中添加信息的方式提供了一个表示表名的常量——CONTENT_DIRECTORY。下面的代码承接上例,像新创建的记录中添加电话号码和邮件地址:
----------------------------------------------------------------------------------------------------------------------
Uri phoneUri = null;
Uri emailUri = null;

// Add a phone number for Abraham Lincoln.  Begin with the URI for
// the new record just returned by insert(); it ends with the _ID
// of the new record, so we don't have to add the ID ourselves.
// Then append the designation for the phone table to this URI,
// and use the resulting URI to insert the phone number.
phoneUri = Uri.withAppendedPath(uri, People.Phones.CONTENT_DIRECTORY);

values.clear();
values.put(People.Phones.TYPE, People.Phones.TYPE_MOBILE);
values.put(People.Phones.NUMBER, "1233214567");
getContentResolver().insert(phoneUri, values);

// Now add an email address in the same way.
emailUri = Uri.withAppendedPath(uri, People.ContactMethods.CONTENT_DIRECTORY);

values.clear();
// ContactMethods.KIND is used to distinguish different kinds of
// contact methods, such as email, IM, etc.
values.put(People.ContactMethods.KIND, Contacts.KIND_EMAIL);
values.put(People.ContactMethods.DATA, "test@example.com");
values.put(People.ContactMethods.TYPE, People.ContactMethods.TYPE_HOME);
getContentResolver().insert(emailUri, values);
----------------------------------------------------------------------------------------------------------------------
    译者按:上例中的变量uri是前面例子中insert()方法返回的URI对象,已包含该记录的ID。

    数据库表中还可以存储少量的二进制数据,这可以通过调用ContentValues.put()方法,参数是byte数组。这对于存储类似icon的图片或者简短的音频很有用。然而,如果要添加大量的二进制数据,比如一张照片或者一整首歌,就要将该数据的content:URI存到表中,并且调用关联该文件URI的ContentResolver.openOutputStream()方法。(这将使得content provider将该数据存至文件中,并利用该记录的隐藏域存储文件路径。)

    在这方面,MediaStore content provider(用于操作图片、音频和视频数据的主要provider)采用了一种特殊的方式:使用query()或者managedQurey()方法获取二进制数据相关的元信息(如图片的说明或存入的日期),使用openInputStream()方法获取二进制数据本身,但已上每种获取的方法都要使用相同的URI。类似的,使用insert()返回的URI对象来添加元信息和二进制信息。下面的代码说明了这种方式:

----------------------------------------------------------------------------------------------------------------------
import android.provider.MediaStore.Images.Media;
import android.content.ContentValues;
import java.io.OutputStream;

// Save the name and description of an image in a ContentValues map.   
ContentValues values = new ContentValues(3);
values.put(Media.DISPLAY_NAME, "road_trip_1");
values.put(Media.DESCRIPTION, "Day 1, trip to Los Angeles");
values.put(Media.MIME_TYPE, "image/jpeg");

// Add a new record without the bitmap, but with the values just set.
// insert() returns the URI of the new record.
Uri uri = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, values);

// Now get a handle to the file for that record, and save the data into it.
// Here, sourceBitmap is a Bitmap object representing the file to save to the database.
try {
    OutputStream outStream = getContentResolver().openOutputStream(uri);
    sourceBitmap.compress(Bitmap.CompressFormat.JPEG, 50, outStream);
    outStream.closeandroid 首选项(Sh();
} catch (Exception e) {
    Log.e(TAG, "exception while writing image", e);
}
----------------------------------------------------------------------------------------------------------------------

3、批量更新

    批量更新一组记录(例如,将所有记录中的“NY”改成“New York”),可以调用ContentResolver.update()方法来更新数据。

4、删除一条记录

    删除一条记录,调用ContentResolver.delete(),此时使用指定记录的URI。

    删除不同的记录,调用ContentResolver.delete(),此时使用要删除数据类型的URI(如android.provider.Contacts.People.CONTENT_URI),同时要使用SQL的WHERE子句来定义删除那些行。
    注意:如果删除通用类型(general type)确定要包含WHERE子句,否则可能删除更多的记录!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

GMT+8, 2025-1-11 21:05 , Processed in 0.301179 second(s), 36 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表