引自http://blog.csdn.net/fan178/archive/2008/02/02/2078688.aspx
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:诺基亚特有的目录
FC api是JSR 75, PDA Optional Packages for the J2ME Platform的一部分,用于访问本地文件系统。
FC api通过Generic Connection Framework(GCF)访问文件系统,允许访问包括存储卡在内的文件系统。
包括如下两个接口和三个类:
FileConnection 访问文件和文件夹的接口。
FileSystemListener 添加删除根目录文件系统的状态监听的接口。
FileSystemRegistry 添加删除根目录文件系统的接口注册类。
ConnectionClosedException 当一个文件句柄的操作被调用,而文件已经被关闭时抛出的异常。
IllegalModeException 当操作所对应的模式不被文件打开模式支持时抛出的异常。
[code]
// 判断是否支持FC:
if(System.getProperty("microedition.io.file.FileConnection.version") != null){ // file.separator
// FCOP available
} else {
// FCOP not available
}
// 打开文件:
// CFCard/:
FileConnection fc = (FileConnection) Connector.open("file:///CFCard/");
// SDCard/:
FileConnection fc = (FileConnection) Connector.open("file:///SDCard/");
// MemoryStick/:
FileConnection fc = (FileConnection) Connector.open("file:///MemoryStick/");
// C:/:
FileConnection fc = (FileConnection) Connector.open("file:///C:/");
// / File:
Connection fc = (FileConnection) Connector.open("file:");
// 只读方式打开一个文件:
String url = "file:///data.txt";
InputConnection conn = null;
int mode = Connector.READ_ONLY;
try {
conn =(InputConnection) Connector.open( url, mode );
// Always check whether the file or directory exists.
// Create the file if it doesn't exist.
if(!conn.exists()) {
}
} catch( IOException ioe ){
// no file
}
// 创建一个文件:
String url = "file:///SDCard/data.txt";
FileConnection conn = null;
int mode = Connector.WRITE_ONLY;
try {
conn = (FileConnection)Connector.open(url, mode);
if(filecon.create()){ // create the file
OutputStream out = conn.openOutputStream();
// now write data to the file
}
conn.close();
} catch(IOException e){
// error
} catch(SecurityException e){
// no permission to create/write
}
// 列举一个目录下的文件:
// FileConnection.list(String filter, boolean includeHidden)
String url = "file:///C:/";
FileConnection conn = null;
try {
conn = (FileConnection) Connector.open(url);
if( conn.isDirectory() ) {
Enumeration names = conn.list();
while( names.hasMoreElements() ){
String name = (String) e.nextElement();
// do something
}
} else {
// not a directory!
}
} catch(IOException e) {
// could not access the URL
} catch(SecurityException e) {
// no permission to read the directory
}
// 读取文件内容:
String url = "file:///CFCard/data.txt";
InputConnection conn = null;
int mode = Connector.READ_ONLY;
try {
FileConnection fc = (FileConnection)Connector.open(url, mode);
if(!fc.exists()) {
throw new IOException("File does not exist");
}
InputStream is = fc.openInputStream();
byte b[] = new byte[1024];
int length = is.read(b, 0, 1024);
System.out.println("Content of "+fileName + ": "+ new String(b, 0, length));
} catch (Exception e) {
}[/code]
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/fan178/archive/2008/02/02/2078688.aspx