正文  数据库 > Content Provider >

解读Content Provider之二

修改数据 可以通过如下方法修改被content provider保存的数据: 1.添加新的记录; 2.为已经存在的数据添加新值; 3.批量更新已经存在的记录; 4...

修改数据
   可以通过如下方法修改被content provider保存的数据:
   1.添加新的记录;
   2.为已经存在的数据添加新值;
   3.批量更新已经存在的记录;
   4.删除记录
   所有的数据修改操作都可以通过ContentResolver的方法来完成。一些content provider在修改数据的时候要求拥有比读取数据拥有更多的权限。如果没有修改content provider数据的权限,那么ContentResolver的方法就会失效。
  添加记录
   为了添加新的记录到content provider中,首先在ContentValues对象中设置键-值对,设置的键和content provider中的列名对应,并且值的内容就是我们想为新记录设置值。然后,通过调用ContentResolver.insert()方法,并且将要处理的content provider和设置好的ContentValues键-值对作为参数传递给这个方法。这个方法将会返回新纪录的完整的URI——也就是,要操作的provider的URI加上为新记录追加的ID号。然后,你可以用这个完整的URI来进行查询操作,获得一个指向这个新记录的Cursor,并且利用这个得到的Cursor进行后续的修改记录的操作。如下例:
    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); 
    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);  添加新的值
    如果一条记录已经存在,你可以添加新的信息到其中或者是修改已经存在的信息。例如:上面例子的下一步就是添加新的联系信息——例如一个电话号码或者是一个IM帐号再或者是邮箱的地址——到新的条目中。
    向Contacts数据库中添加新纪录的最好的方法就是追加表名到存储新纪录的URI后,然后用修改过的URI来添加新的数据值。每一个Contacts表都会暴露出一个名字作为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 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);

    你可以通过调用ContentValues.put()方法通过一个byte类型的数组将少量的二进制数据加入到表中。例如:这种方法对小的图标类型的图片和短小的音频文件是十分有效的。然而,如果你有比较大的二进制数据要添加,例如一个图片文件和一首完整的歌曲,为这个要添加的数据这是一个content:URI,然后调用ContentResolver.openOutputStream()方法,并且这个文件的URI作为这个方法的参数。(这将使得content provider使用文件来存储这些数据,并且将文件文件的路径记录在这条记录的一个隐含的域中)
    在这方面,MediaStore content provider独立于图片、音频和视频文件的主要的provider,这就提供了一个便利:我们可以通过query()和managedQuery()使用一样的URI来获取二进制数据(例如:捕获到的图片)的信息,我们可以通过openInputStream()方法和刚刚获得的二进制数据的信息来获取这些二进制数据,下面的代码就显示了这个便利之处:
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.close(); 
} catch (Exception e) { 
    Log.e(TAG, "exception while writing image", e); 
}     
 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.close();
 } catch (Exception e) {
     Log.e(TAG, "exception while writing image", e);
 }    批量更新数据
    要批量更新一组记录(例如:要将所有域的“New York”都改变为“NY”)。调用ContentResolver.update()方法,将要修改的列和数值作为参数。
删除一条记录
    要删除单个记录,使用特殊行的URI作为参数,调用ContentResolver.delete()进行删除
    要删除多个行(多条记录),使用要删除的记录的数据类型的URI和一条定义哪个行要被删除的SQL WHERE语句作为参数调用ContentResolver.delete()方法(例如:android.provider.Contacts.People.CONTENT_URI)。(注意:如果你要删除一般的类型,那就要确保包含了一条正确的WHERE语句,否则你就会删除比自己设想的要删除的记录更多的记录)。
创建一个Content Provider
    要创建一个content provider,你必须:
    1.设置一个存储数据的系统。大多数的content providers都会使用Android系统的文件存储方法或者是SQLite数据库来存储这些数据,但是你可以以任何方式存储你自己的数据。Android提供了SQLiteOpenHelper类来帮助你创建数据库和SQLiteDatabase来管理数据库。
    2.扩展ContentProvider类提供存储数据的方法
    3.在你的应用程序的ANndroidManifest.xml文件中声明自己的content provider。
扩展ContentProvider类
    你可以定义一个ContentProvider子类将自己的数据暴露给那些通过ContentResolver和Cursor对象使用这些数据的对象。原则上,这也意味着要实现6个定义在ContentProvider类中的抽象方法:
    query()
    insert()
    update()
    delete()
    getType()
    onCreate()
    query()方法必须返回一个Cursor对象,通过它可以操作请求的数据。Cursor是一个接口,但是Android提供了一些定义好的Cursor对象,可以供直接使用。例如:SQLiteCursor能够操作那些存储在SQLite数据库中数据。你可以通过SQLiteDatabase类的query()方法获得Cursor对象。这里有一些其他的Cursor实现——例如:MatrixCursor——对于那些不存储再数据库中的数据。
    因为这些ContentProvider方法可以被处于多个进程和多个线程中的ContentResolver对象调用,因此他必须以一种线程安全的方式实现。
    出于礼节,你可能希望在数据被修改的时候通过调用ContentResolver.notifyChange()来通知监听者或者用户。
    除了定义一个子类外,你仍然做如下几步来简化客户端的编程,并且使得这个类更加有效:
      1.定义一个命名为CONTENT_URI的public static final Uri。这就是代表你的content provider要处理的数据的完整的content:URI字符串。
        你必须为这个字符串常量定义一个唯一的值。最好的方式就是使用content provider完成的类名(小写).因此,对于一个Transpotation类,我们可以如下面一样定义:       public static final Uri CONTENT_URI = Uri.parse("content://com.example.codelab.transportationprovider"); 
public static final Uri CONTENT_URI = Uri.parse("content://com.example.codelab.transportationprovider");        如果这个provider包含子表,那么同样需要为每一个子表定义CONTENT_URI常量,这些子表拥有相同的权限(也就是content provider的标示符),并且这些子表仅仅由他们的路径来区分,如下面示例:
       content://com.example.codelab.transportationprovider/train  
content://com.example.codelab.transportationprovider/air/domestic  
content://com.example.codelab.transportationprovider/air/international 
        content://com.example.codelab.transportationprovider/train
 content://com.example.codelab.transportationprovider/air/domestic
 content://com.example.codelab.transportationprovider/air/international 对于content:URI的整体介绍,详看本文最后的Content URI总结部分。
      2.定义content provider要返回给客户端的列名。
      3.小心的记录每一列数据的数据类型,客户端需要这些信息来读取数据。
      4.如果你要处理一个新的数据类型,你必须定义一个新的MIME类型作为你的COntentProvider.getType()方法的返回值。
      5.如果你想添加到表中的数据太大了——一个代表大位图的文件——那么暴露数据给客户端的域必须包含一个content:URI字符串。
定义一个content provider。
    要让系统知道你自己定义的content provider,那么你应该在AndroidManifest文件中使用<provider>标签进行定义。未在这里定义的content provider对于系统来说都是不可见的。
    <provider>标签的name属性就是Conprovider子类的全名。authotities属性就是标示provider的content:URI的权限部分。例如,如果ContentProvider子类的名称是AutoInfoProvider,那么<provider>元素可能如下面这样定义:
<provider android:name="com.example.autos.AutoInfoProvider" 
     android:authorities="com.example.autos.autoinfoprovider"  
     . . . /> 
/provider> 
     <provider android:name="com.example.autos.AutoInfoProvider"
          android:authorities="com.example.autos.autoinfoprovider"
          . . . />
 </provider>    注意authorities属性忽略了content:URI的路径。例如:如果AutoINfoProvider对不同类型的autos或者不同的制造商拥有子表,   content://com.example.autos.autoinfoprovider/honda  
    content://com.example.autos.autoinfoprovider/gm/compact  
    content://com.example.autos.autoinfoprovider/gm/suv 
content://com.example.autos.autoinfoprovider/honda
 content://com.example.autos.autoinfoprovider/gm/compact
 content://com.example.autos.autoinfoprovider/gm/suv    这些路径不应该在AndroidManifest文件中声明。因为权限是对provider而言的,而不是对于路径;你的provider应该可以以自己选择的方式来解释URI的路径信息部分。
    <provider>的其他一些属性可以设置读取和改写数据的权限,提供一个展示给用户的图标和一条信息,使能和禁止provider等等。如果content provider中的数据不需要在多个进程中进行同步,那么就设置multiprocess属性为“true”。这就允许每一个客户端进程都实例化这个provider,建立必要的IPC。
Content URI总结
    下面是对于一个content URI的重要部分的概要介绍:\

 

A.标准的前缀,表明这些数据是被content provider持有的。这个永远不需要修改。
    B.URI所属的部分,它是用来鉴别一个content provider的。对于第三方的应用程序,这必须是类的全名(必须是小写)来确保它是唯一的。所属是定义再<provider>元素的authorities属性的:  <provider android:name=".TransportationProvider" 
          android:authorities="com.example.transportationprovider" 
          . . .  > 
<provider android:name=".TransportationProvider"
          android:authorities="com.example.transportationprovider"
          . . .  >    C.这部分是content provider用来决定是哪些类型的数据被请求。这个可以为空,也可以有几个段那么长。如果一个content provider仅仅暴露一种类型的数据(例如:仅仅是trains类型),那么他就可以为空。如果一个content provider暴露几种类型的数据,包括子类型,那么它就有可能有几段那么长——例如:“land/bus”、“labd/train”、"sea/ship"和“sea/submarine”这四种可能的类型。

    D.被查询的特殊记录的ID。如果有,那么这个就是被请求记录的_ID值。如果请求不是对单个记录,那么这个段和斜杠都会被忽略:     


content://com.example.transportationprovider/trains 
    content://com.example.transportationprovider/trains

摘自  chenlong12580的专栏