1 引言
本文档讲述了FileConnection API [JSR-075],并简要介绍了该包中包含的MIDlet范例以及诺基亚特有的一些实现细节。本文档假定读者熟悉Java™编程,并具有移动信息设备描述 (Mobile Information Device Profile, MIDP)编程的基础,MIDP编程基础可参见诺基亚论坛中的文档MIDP 1.0: Introduction to MIDlet Programming [MIDPPROG]。FileConnection API是一个受限API,例如,它具有安全上的限制。因此,读者还必须熟悉MIDP 2.0安全架构的概念;诺基亚论坛中的文档MIDP 2.0: Tutorial On Signed MIDlets [SIGNMID]介绍了安全模型和签名过程。
FileConnection API 在JSR-75: PDA Optional Packages for the J2ME™ Platform中定义,JSR-75包含两个Java™ 2 Platform, Micro Edition (J2ME™)可选包,用于支持PDA之类的设备的功能。可选包提供了对个人信息管理(PIM API)数据库和本地文件系统(FileConnection API) 的访问。这两个包相互之间完全独立,因此,设备可以包含其中任意一个包,也可以同时包含两个包。
2 FileConnection API
2.1 简介
在J2ME 设备中,可以利用通用连接架构(Generic Connection Framework,GCF),通过各种连接类型特有的Connection接口实现,来处理I/O 操作。构建不同的Connection扩展要使用适合于不同连接类型的URL,如http://,sockets:// 等。原则上讲,GCF通常足以支持文件连接,但是,GCF不是J2ME或MIDP的必选项,大多数实现中都没有包含GCF。即使构建了此类的连接,仍可能不支持文件操作,如重命名文件或删除文件。此外,对本地文件的访问关系到安全、私密和系统稳定性等方面的重要问题,在实现时必须对此给予考虑。
FileConnection API [JSR-075]通过提供对文件系统的访问以及对文件操作的支持,弥补了上述缺陷。该API假定设备中存在一个可定位的文件系统,如可移动的内存卡、闪存或其它类型的永久存储器。该API并不是记录管理系统(Record Management System ,RMS)的替代物,它只是对RMS的补充,从而实现MIDlet与本地应用软件的交互。例如, MIDlet可以访问并处理本地应用软件先前利用内嵌数码照相机拍下的图象。一般情况下,这些图象存储在设备内存中,通过FileConnection API ,可以实现CLDC/CDC应用软件对它们的访问。
该API的最低要求是CLDC 1.0,因此,即使没有用户界面的基本J2ME设备也能实现它。但是,本文档和MIDlet范例都假定FileConnection API是在MIDP 2.0设备上实现的。此外,该API的安全机制还在MIDP 2.0 安全架构 [SIGNMIDP]下。
由于FileConnection API 是可选的扩展项,所以,增加了一个系统属性用于表明该API是否存在。系统属性microedition.io.file.FileConnection.version包含实现的API版本。目前,该属性的值为1.0或null,1.0表明该API的当前状态,null则说明未使用该API。另外一个有用的系统属性是file.separator,它包含用于隔离目录的字符,其典型值为“/”。
该API非常简单,仅包含一个类、两个接口和两个异常。其中FileConnection接口是最为重要的部分,它扩展了Connection接口,提供对目录和单个文件的访问。创建FileConnection 的实现需要使用方法 Connector.open()。方法open()的参数是一个URL,如RFC 1738 [RFC 1738] 和RFC 2396 [RFC 2396]中的定义,URL的格式为file:///,其中,host通常为空,path则以文件系统的根目录开始,并往下扩展到一个特定文件或目录。Symbian设备中,典型的文件URL范例如下所示:
file:///C:/Nokia/Images/Image(001).jpg
文件系统的根目录因设备而异,由于根目录是由设备的操作系统从逻辑上进行定义的,故它们没有必要与物理上的内存单元对应。此外,某些诺基亚设备支持虚拟目录,虚拟目录实际上就是指向某个目录的链接。例如,在内存卡中,拍下的图象可能位于根目录e: 下的路径file:///e:/Nokia/Images中,此外,还有一个虚拟根目录Images/指向实际的物理位置。假设MIDlet具有访问目录Images/的权限,但却无权访问根目录e:/,这种情况下,虚拟根目录将有助于更容易地定位,并可简化安全许可。
类FileSystemRegistry提供了方法listRoots(),该方法的返回值是文件系统中根目录的枚举,其中包括逻辑根目录和虚拟根目录。该API还考虑到了某些设备在运行期间具有添加或删除文件系统的能力。类FileSystemRegistry提供注册FileSystemListener监听器的方法,在修改设备中的根目录时,将调用该方法。建议每个应用软件都注册一个FileSystemListener监听器,在发生变化时,监听器将被告之发生了变化并做出适当响应。
由于FileConnection接口能够扩展Connection,并可利用GFC创建对象,FileConnection与其它常见Connection 实现之间存在着一些显著差异。其中一个最显著的差异是,即使当前文件不存在,也能成功调用Connector.open()。这在创建新文件或新目录时是很有必要的。但是,打开不存在文件的InputStream是非法的。
另一个差异是,在关闭输入或输出流后, FileConnection仍能保持打开状态。因此,在访问文件后调用方法FileConnction.close()是很重要的,这样做可以保证其它应用能访问该文件。相应地,利用OutputStream对文件做出的修改也不会立即对文件系统可见。这取决于实际的实现以及设备的操作系统。方法flush()可以保证缓冲区能够被清空,并且其中的内容可以写入实际文件中。
与其它Connection对象的另外一个差别是,通过方法setFileConnection(),可以实现FileConnection对象的重用。该方法主要用于目录转换。其思想是,如果在特定目录中构建了FileConnection,则可以调用方法list()获得该目录的子文件和子目录的枚举。该枚举值的成员可作为参数被传递给setFileConnection(),之后,原始FileConnection就指向了这个特定的子文件或子目录。通常来说,setFileConnection()的参数是已存在的其它子文件或目录的相对路径,或者是表示上层目录的“..”参数。
另外一个针对所有I/O操作的常见注意事项是,必须在异于GUI线程的其它线程中执行I/O操作。在使用FileConnection API时,这个建议同样适用。由于安全架构的原因,与文件相关的操作可能会发生用户提示,要求用户对操作进行确认,考虑到这一点,在异于GUI线程的其它线程中执行I/O操作尤为重要。如果在GUI线程中执行I/O操作,并且需要使用用户提示,则MIDlet就有可能会死锁。
2.2 安全
在用FileConnection API开发应用软件时,考虑API的安全隐患是非常重要的。为了保护用户的个人数据和整个系统的安全,文件操作是受限制的。只有在获得必须的许可后,才能执行文件操作;否则,将抛出SecurityException异常。因此在适当的时候使用捕获SecurityException的语句非常重要。
MIDP 2.0 MIDlet既可以是不可信的,也可以是可信的[SIGNMID]。在第一种情况下,设备无法确知MIDlet的由来和完整性,因此,在没有显式的用户许可时,不允许调用受限的API。也就是说,如果需要访问一个文件或目录,将会显示用户提示,而用户必须显式地确认该操作。
在MIDlet是可信的情况下,设备可以通过X.509证书判断MIDlet的由来和完整性。这些MIDlet可以根据安装时的安全域设置,自动地获得许可。此外,MIDlet需要在Java 应用描述符(Java Application Descriptor,JAD)文件中的属性MIDlet-Permission中包含文件操作许可。
共定义了下面两个有关FileConnection API的许可:
javax.microedition.io.Connector.file.read
javax.microedition.io.Connector.file.write
如果希望以READ模式打开文件,并获取文件的输入流,则第一个许可是必需的。在用类FileSystemRegistry注册监听器时,也需要第一个许可。如果希望以WRITE模式打开文件,并打开文件的输出流,则第二个许可是必需的。此外,诸如删除、修改目录之类的操作也需要写入许可。如果以READ_WRITE模式打开文件,则同时需要两个许可。这些许可包含在Read User Data Access 和Write User Data Access功能组中。
对许可的授权或否认取决于MIDlet安装到的安全域。某些安全域可以完全地授权许可,而其它域则可能仅在得到显式用户同意的情况下才允许授权。实现时,可以对每个域允许的许可进行定义。但是,仍希望第三方和不可信域按照表1的方式定义许可模式:
功能组 | 可信的第三方域 | 不可信的域 | ||
默认设置 | 允许的设置 | 默认设置 | 允许的设置 | |
Read User Data | Oneshot | Session, Blanket, | Oneshot | Oneshot, No |
Access | Oneshot, No | ||||
Write User Data Access | Oneshot | Session, Blanket, Oneshot, No | No | Oneshot, No |
表1:允许的和默认的许可模式
表1实际上说明,每次创建文件或目录连接时,不可信的MIDlet总会弹出一个提示。此外,如果以READ_WRITE模式打开连接,将会出现两个提示,分别对应于两个许可。可信的第三方MIDlet的情况与此类似,但是用户可以手动地把该设置改为session,这样,在运行MIDlet时,用户就会仅被询问一次。值得注意的另外一点是,许可是以基于文件到文件的方式给出的。也就是说,用户在访问每个文件或目录时都会被提示。该范例中的MIDlet需要在文件系统中遍历,因此会出现多个用户提示,对于此类的MIDlet来说,上述情况尤其值得注意。这种情况能够有力地说明,在使用受限API时,为什么应该对MIDlet签名。
此外,关于文件访问还有另外一个层面上的局限。根据安装时赋给MIDlet的安全域,MIDlet将能够访问文件系统的一个子集。这种设计可以保护用户数据,并可防止对操作系统的损害。特别地,可信的第三方 和 不可信域中的 MIDlet仅能访问一组被指定的公共目录(其中包括存放图象、视频、公共文件的目录)以及每个MIDlet的专用目录。这是推荐使用虚拟目录的原因之一,因为,有可能允许访问根目录Images/,但可能由于MIDlet无法访问e:,从而造成无法从e:/ 切换到目录e:/Nokia/Images/。
某些文件相关操作会检查是否获得了适当的安全许可,但是,在调用方法Connector.open()时,开发人员需要给予特别考虑。在创建FileConnection和授权适当的许可后,对于需要同样许可的其它操作来说,该许可仍然有效。例如,一旦为写操作创建FileConnection,则调用删除(delete)操作也已经被授权。如果创建的FileConnection仅具有对读操作的许可,则在调用方法delete()时,将会要求对写操作的许可,在必要时,还将出现用户提示。
方法setFileConnection()还将根据原始FileConnection的创建模式检查文件许可。这是非常合理的,因为setFileConnection能够改变当前连接,以便指向不同的文件或目录。
2.3 诺基亚特有的目录
在许多诺基亚设备中,一些目录是针对特定任务而设计的。例如,照相机设备在特定的“图像”目录下存储拍下的照片。为了使开发人员更加容易地访问此类目录,实现了FileConnection API的诺基亚设备包含额外的系统属性,以用于定位这些目录。
并不是所有设备都需要这些属性,因此,不要想当然地认为它们存在。开发人员应该注意,如果属性值为null,则需寻找一个替代方法。
表2列举了系统属性。第一列是属性名,它以URL的格式指向特定目录。该URL可以被直接传递给Connection.open()。第二列是一个额外的属性,它包含该目录的本地化名称。建议用第二列中的属性代替目录的通用的非本地化名称,以保持MIDlet与其它设备UI的兼容性。
属性 | 局部属性 | 描述 |
fileconn.dir.photos | fileconn.dir.photos.name | 该属性指向的目录存储集成照相机拍下的相片或其它图象。 |
fileconn.dir.videos | fileconn.dir.videos.name | 与上面类似,但是存储的内容是视频。默认情况下,下载的视频也保存在该目录下。 |
fileconn.dir.tones | fileconn.dir.tones.name | 铃声或其它类似的音频文件存储在该目录下。 |
fileconn.dir.memorycard | fileconn.dir.memorycard.name | 在内存卡可用的情况下,该属性指向内存卡的根目录。 |
fileconn.dir.private | fileconn.dir.private.name | MIDlet套件的专用工作目录。 |
表2:诺基亚特有的目录