内容提供者通常需要管理大量的二进制数据,如位图或音乐剪辑。应用的设计中需要对大数据文件的存储有充分考量,否则可能会有严重的性能问题。内容提供者可以通过内容提供者URI提供文件服务,该URI中封装了实际物理文件的位置,因此客户端不知道该信息。使用内容提供者URI来访问文件的客户端不知道这些文件的真正存储方式。这种中间层使得内容提供者管理文件的方式很合理,而且不会把信息暴露给客户端——否则如果内容提供者需要改变物理文件的存储方式,还需要客户端代码也要进行相应修改。通常来说,只改变提供者代码而不需要其所有的客户端修改,这样事情就简单得多。客户端不需要知道一组提供者媒体文件是保存在闪存卡、SD卡还是网络上,只要该提供者支持从一组内容提供者中访问文件即可。对于给定的URI,客户端只需要调用ContentResolver.openInputStream方法并从结果流中读取数据。
此外,因为Android应用不应该读写其他应用创建的文件,所以,当应用之间共享大量数据时也必须使用内容提供者来访问相关的数据。因此,当第一个内容提供者返回文件指针,这个指针必须是content://URI形式,而不是Unix文件名形式。使用content://URI打开文件,并由拥有该文件的读权限的内容提供者执行读操作,而不是客户端应用直接执行(客户端应用不应该有访问文件的权限)。
处理文件系统的I/O操作要比处理SQLite的blob数据快得多,而且功能也更强大,但使用Unix文件系统直接存储二进制数据是更好的方式。此外,把二进制数据放在数据库中没有什么优势,因为你无法查询这些数据。
为了在应用中实现文件读写,Android SDK文档建议使用内容提供者把数据保存到文件中,并在数据库中存储指向该文件的content://URI,如图12-2所示。客户端应用会传递这个字段中的URI给ContentProvider.openStream方法,然后从指定的文件中检索二进制流。
图12-2:Android MVC典型的光标和内容提供者的使用
具体而言,建议使用文件,而不是创建一个类似下面这样的用户表:
CREATE TABLE user ( _id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, password TEXT, picture BLOB );
Google Android文档建议创建如下两张表:
CREATE TABLE user ( _id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, password TEXT, picture TEXT );CREATE TABLE userPicture ( _id INTEGER PRIMARY KEY AUTOINCREMENT, _data TEXT );
user表的picture字段会保存指向userPicture表的一条记录的content://URI。userPicture表的_data字段会指向Android文件系统中的真正文件。
如果该文件的路径直接保存在user表中,则客户端会直接得到这个路径,但是无法打开文件,因为这个文件属于为应用提供服务的内容提供者,用户没有权限访问它。但是,在这里给出的解决方案中,访问权限是由ContentResolver类控制的,稍后将对这个类进行介绍。
当处理请求时,ContentResolver类会查找_data字段中存储的文件,如果找到了相应的文件,提供者的openOutputStream方法会打开这个文件,并返回java.io.OutputStream给客户端。即使客户端能够直接打开文件,返回的仍然是这个对象。ContentResolver类属于内容提供者所在的应用,因此虽然客户端无法打开文件,但ContentResolver类是可以打开文件的。
在本章的后面,将介绍一种内容提供者,其功能是使用内容提供者文件管理工具保存缩略图。