AIR APIs详解 - 文件访问系统

作者:雨辰 发布于:2013-3-25 10:13 Monday 分类:AS3

AIR APIs详解 - 文件访问系统 part1
AIR APIs详解 - 文件访问系统 part2
AIR APIs详解 - 文件访问系统 part3
AIR APIs详解 - 文件访问系统 part4
AIR APIs详解 - 文件访问系统 part5
AIR APIs详解 - 文件访问系统 part6


AIR最大的特点是利用开发人员掌握的Web技术来开发桌面应用。桌面应用有一个最为显著的特点,就是可以访问操作用户的文件系统,AIR也内置了访问用 户文件系统的API集合。在我开始讲解这部分API前,你需要先知道一些创作应用的原则,这样可以保证你开发的AIR应用不被大多数用户反感。

原则1:AIR访问文件系统API具备了操作用户文件系统的强大能力,是一把双刃剑,请谨慎使用,不是每个用户都会因为你的AIR应用可以操作他们的本地文件系统而去体验下载和安装它,恰恰相反,他们极有可能对你的应用避而远之。
原则2:在你的应用文档和各方面描述中加入对于用户文件系统访问的具体描述,个人免费应用是为了确保用户知道这些功能的存在,而商业AIR应用更应该将这部分写入EULA中,否则极有可能在某一天收到法院的传票。
原则3:在应用中,当有事件触发访问用户文件系统的操作时,先给与用户必要的信息提示,待用户确认操作后再执行文件系统访问的功能,这样会非常友善,不至于你的应用遭到某些用户的强烈反感。

好了,原则说完了,开始解析技术的部分。
AIR访问文件系统的功能类全部来自一个扩展的API集合,flash.filesytem,其中包含了3个类File,FileStream和FileMode。这个文章没有对于这3个类的详细描述,你可以从下面的链接找到中文版本的类库参考:

AIR中文语言参考-访问文件系统部分

该文档是一个Language Reference,更加适合做为语言手册,随时查询。

File,FileStream 和FileMode这三个类提供了用户通过AIR应用执行对于文件系统(文件和文件夹)的读,写,移动,拷贝和删除等任务,注意,AIR API中没有专门针对文件夹访问操作的类。为什么?很简单,我们对于文件系统的访问操作过程里,对于文件的操作,无时无刻都在牵扯到与之相关的文件夹操作 (开发人员经常称为Directory Path或者Content Path)。你的操作可以使用File类的只读属性isDirectory来判断File对象实例是否指向文件夹还是文件。

在我们这个系 列的API详解的文章中,我本人不会把Language Reference中与这三个类有关的所有属性和方法都讲一遍,实在是没有那个必要,所以在此再次强调Language Reference的重要性,那就是当你看懂我的文章后,你动手还是需要像Language Reference这样的帮助,我认为没有任何文档可以替代它。

File类的衍生关系相当清 晰:File->FileReference->EventDispatcher-> Object,注意,FileReference就不是AIR独有的类了,而是来自于Flash Player的flash.net包。回到文件访问的API上来,有了上述的三个类,你可以通过他们实现在Windows,MACOSX和Linux上同 样的操作,这就是跨平台。 文件类File有几个public标识符的属性非常重要,也是作为开发人员经常用到的,比如:

1. 只读属性exist,表示文件或文件夹是否存在,返回值是true或者false
2. 文件对象的路径url,字符串格式
3. 文件对象是否隐藏isHidden,布尔类型,返回值是true或者false
4. 文件对象是否是文件夹isDirectory,布尔类型,返回值是true或者false,对于这个属性,各位一定要牢记,以我的经验,用到次数最多的就是它。

此外,File类还有一些其他有用的静态属性,也值得提一下:


语 言参考对于上面的属性解释相当详细,而且很容易懂。文件类此外还有一些方法,比如copyTo,moveTo,deleteDirectory等等,我们 以后的文章会单独进行解释。FileMode类和FileStream类,我们也在以后的文章中陆续讲解。一句话,分来了慢慢讲,贪多嚼不烂,:)

现在将告诉各位如何创建一个文件夹,借此来直接激发刚刚接触此部分API开发人员的积极性。简单而言,基于文件系统创建一个文件夹非常简单,一般来说分成2步:

1. 创建一个File对象,赋予一个不存在的文件夹路径
2. 基于该对象使用createDirectory()方法

//----------------jeff添加---------- 

Android

iOS

File.applicationDirectory

通过 URL 只读(非本机路径)

只读

File.applicationStorageDirectory

可用

可用

File.desktopDirectory

SDCard 的根目录

不可用

File.documentsDirectory

SDCard 的根目录

可用

File.userDirectory

SDCard 的根目录

不可用

File.createTempDirectory()

可用

可用

File.createTempFile()

可用

可用

//------------------------------

通过上面的2个步骤,我们直接来看一段代码:

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
<mx:Script>
<![CDATA[
import flash.filesystem.File;
private function createDirectory():void{
var newDirectory:File =File.desktopDirectory.resolvePath("我的AIR文件夹");
newDirectory.createDirectory();
}
]]>
</mx:Script>
<mx:Button label="创建一个新文件夹" click="createDirectory()"/>
</mx:WindowedApplication>

你 尝试在FlexBuilder3里创建一个Flex Project,类型是AIR Desktop,之后,将上面的代码粘贴到你的MainApplication中,运行,看看你的桌面上是否多了一个叫做“我的AIR文件夹”的目录。这 个例子中的中文文件夹名称完全没问题,只是建议你最好使用英文和数字来合理命名你的文件夹,以确保可以运行在其他语言的操作系统上,这里仅仅是为了测试直 观方便才使用中文的。这里使用了desktopDirectory来将要创建的文件夹对象指向了你的桌面,请不要担心是windows还是MACOS,只 管记住这个属性就好了。resolvePath这个方法将一个当前叫做“我的AIR文件夹”的命名附加到File对象上去来决定文件夹的命名,然后使用 createDirectory方法来创建。

安卓系统不可用中文名!!!!!


从part1你了解了如何快速通过AIR API的File类创建一个全新的文件夹,part2将会对此做进一步说明。Part1中,我们用到了File类的desktopDirectory这个 内置属性,听这个名字,就知道是直接将AIR应用的File对象路径指向用户桌面。我们在part1中就轻轻地提及了其他几个静态属性:

我认为Part2有必要进一步做个解释了,因为有人可能会在这里有疑问。且看下面:

开发者疑问:为什么File类就要带这几个静态属性,如果我的AIR应用是要访问用户C盘或者D盘路径下的某个文件夹呢,是不是不允许?
我的回答是,AIR在File类提供这几个静态属性,是为了保证开发人员使用这些静态属性的值去访问用户文件系统时,能够高度保持应用跨平台的一致性。就像part1中的例子里的这部分代码:

var newDirectory:File =File.desktopDirectory.resolvePath("我的AIR文件夹");
newDirectory.createDirectory();

当 Windows用户执行AIR应用时,desktopDirectory会自动帮你映射到Windows系统的桌面路径;而同样的应用,当被MACOSX 用户执行时,desktopDirectory也同样会自动帮你映射到MACOSX的桌面上去,这就通过了desktopDirectory保证了跨平台 应用访问路径的一致性。再深究一下,desktopDirectory属性在我的Windows下指的是:

C:\Documents and Settings\zma\Desktop\

而在我的MACOSX下则指向:

/Users/zma/Desktop/

如果按照开发者说的,随意指定一个系统路径来访问文件系统,未尝不可。假定part1的随意路径就是指向我windows系统的桌面,我就可以这样去定义:

var newDirectory:File =new File("C:\\Documents and Settings\\zma\\Desktop\\我的AIR文件夹");
//或者你使用正斜杠
//var newDirectory:File=new File("C:/Documents and Settings/zma/Desktop/我的AIR文件夹");
newDirectory.createDirectory();

结 果一样,仍然会在Windows桌面上创建一个叫做我的AIR文件夹的目录,但是局限性就来了,因为你用这种方式只能工作在Windows操作系统里,假 如你的应用中创建文件夹和文件是一个重要的工作(比如存储一个文件到某文件夹),你的应用在MACOSX系统里就完全不工作。有没有办法改正?有,用 Flash Player API判断用户系统,然后用判断的结果值分开来写访问用户文件系统的逻辑,Win一套,MAC一套,Linux一套...乃至到未来的 smartphone上一套,优点是灵活,缺点是代码工作量骤然提升。所以,推荐各位将访问文件系统的File对象尽量绑定在几个内置属性的映射路径上, 既方便,又不用考虑目标系统,而且还友善,因为没有用户愿意你的AIR应用打开人家C盘windows文件夹去生成一个目录或新文件,MACOSX就好很 多,因为要sudo才能获得某些文件夹的读写和创建删除权限。所以,AIR应用推荐使用File的几个默认内置静态属性,你好我好大家好,皆大欢喜。



第三部分,咱们接着上两部分继续。上两部分介绍了如何通过AIR创建文件夹,今天这部分是讲解一下文件夹的Move和Copy的部分,Move和Copy,两者非常相似,但有所不同,而且Move和Copy有时候会产生一些困惑,这里也一并解释一下。

我 想,moveTo和copyTo两个方法都在帮助上描述的非常规矩,大多数人(尤其是做开发的)都应该可以看懂,那么我们先从困惑和要点着手,直切要害。 我们现在要实现把一个文件夹移动到另外一个文件夹下去,被移动的那个文件夹,下面有文件存在,如果按照刚刚参考的API的开发方式来做,我们应该是按照下 面的逻辑:

1. 找到源文件夹(被移动的)路径和名称
2. 找到目标文件夹的路径和名称
3. 用moveTo方法将源文件夹移动到目标文件夹

很好理解,那么对应的代码片段就是:

private function moveDirectory():void{
var source:File=File.desktopDirectory.resolvePath("originalDirectory");
var destination:File=File.desktopDirectory.resolvePath("movedDirectory");
try{
source.moveTo(destination);
}catch(error:Error){
trace(error.message);
}
}

有人叫喊了..."错了!!!!",我知道错了,相信有实战经验的开发人员知道上面的代码有错,但对于刚接触此部分API的朋友而言,上述代码似乎看上去没 有问题,将桌面上originalDirectory的文件夹移动到movedDirectory文件夹中去,代码貌似是应该这样。错在哪里?我们通过执 行来验证一下:
错误类型一,你的桌面上如果没有originalDirectory,会有错误,错误描述是:Error #3003: File or directory does not exist。Flex会在Debugging状态下告知你上述错误。很好理解是不是,因为你不能移动一个不能存在的文件夹。
错误类型二,你桌面上 有originalDirectory,没有movedDirectory,程序执行了,庆祝一下。结果很糟糕,不是我们想要的结果(将 originalDirectory移动到名称为movedDirectory文件夹中去),而是originalDirectory消失了,剩下的是一 个新建立的movedDirectory文件夹。
错误类型三,你桌面上有originalDirectory,有movedDirectory,程序执行出错,很糟糕,错误信息为Error #3012: Cannot delete file or directory,不能删除文件或文件夹。
看到这里,很多人困惑了,将originalDirectory目录移动到movedDirectory中的正确方式是怎样的?看下面这行针对上面改过的代码:

var destination:File=File.desktopDirectory.resolvePath("movedDirectory/originalDirectory");

运行结果,originalDirectory文件夹被移动到了movedDirectory,很难理解是不是,但是这就是正确的方式。我们必须指明originalDirectory被移动后的详细路径,包括originalDirectory名字本身。
进 一步讨论,将originalDirectory从movedDirectory中重新手动移动回桌面(2个文件夹都在桌面),在 movedDirectory文件夹中再创建一个文件夹,也叫做originalDirectory,运行程序,发生什么?出现错误,File or directory does not exist。看来我们需要多做一点在文件夹向目标文件夹内移动时,需要允许覆盖,否则也会报错。再对代码做略微改动:

source.moveTo(destination,true);

moveTo方法有一个overwrite属性,默认是false,不允许文件夹在移动过程中出现同名覆盖的现象,改为true,将会把目标同名文件夹覆盖,这样,moveTo的问题基本已经解决。
但是我们还想再探究一下,如果文件夹里有文件,怎么办,我们现在先将originalDirectory移动回桌面,再在originalDirectory文件夹内建立一个测试文本,我这里叫做1.txt。我们仍然使用这段代码:

private function moveDirectory():void{
var source:File=File.desktopDirectory.resolvePath("originalDirectory");
var destination:File=File.desktopDirectory.resolvePath("movedDirectory/originalDirectory");
try{
source.moveTo(destination);
}catch(error:Error){
trace(error.message);
}
}

没 有问题,连同1.txt文件一起,originalDirectory被移动到了movedDirectory下。如果movedDirectory文件 夹下有一个同名的originalDirectory,那么同样需要打开moveTo的overwrite属性。看来,这个moveTo的问题已经搞清 楚,那就是moveTo需要全部指明被移动文件夹在目标文件夹的全部路径,包括它的自己的名称。而moveTo的操作只要在对被移动和目标文件夹的设置操 作得当,所有被移动的子文件(夹)都会被一并操作,这时为了避免出错,就要打开覆盖。如果你不想这样,就不能使用moveTo,转而应该使用 copyTo,就是文件夹的拷贝功能。



上一部分我们详细说了moveTo这个功能,说实话,这真是一个不讨人喜欢的方法,但是在AIR里,我们要去移动一个文件夹,就必须用到它。另外一个我们 还经常用到的功能是copyTo,我想一百个玩电脑的应该有99个人知道什么叫拷贝,这里就不对这个方法做详解了,你可以把第三部分的代码全部保留,把 moveTo换成copyTo尝试一下通过API来拷贝文件夹或文件是什么样子(其实这功能一点都不兴奋)。

今天第4部分的文件API详 解,给各位说说删除和放入回收站的部分,同样这2个部分也是API文件访问API中跨平台的公用API。在开始讲解这个部分前,我想有个小提示,各位从这 个部分和前三个部分的讲解中,看到的文件操作模式,全部是同步(sync)模式,关于异步(Async)模式,放到后面再集中解释。

删除文件夹和删除文件是两个独立的方法,删除文件夹是deleteDirectory(),删除文件是deleteFile()。我们先来看deleteDirectory()方法,删除一个文件夹,最简单的代码是:

var delDirectory:File=File.desktopDirectory.resolvePath("myAIRDirectory");
delDirectory.deleteDirectory()

你把上述代码放在一个函数里,通过一个Button的click来运行查看一下结果。我反正是给了一些假设来测试delDirectory()方法。
假设一,myAIRDirectory文件夹不存在,结果是Error #3003: File or directory does not exist。文件夹不存在,太白痴的假设一,开发者千万别犯这种错误。
假 设二,myAIRDirectory文件夹存在,是个空文件夹。结果文件夹被成功删除。修改deleteDirectory()中的参数为true,再执 行一次操作(恢复被删掉的myAIRDirectory),结果仍然是成功。证明false和true的参数值对空文件家没有作用。
假设 三,myAIRDirectory文件夹存在,该文件夹内有一个子文件夹和若干文件,去掉deleteDirectory()方法中的true参数值,执 行。结果是Error #3010: Directory is not empty。文件夹不能为空,可见默认deleteDirectory()的参数false,是不允许直接删除非空文件夹
假设四,将假设三中的deleteDirectory()参数改成true,执行。结果是myAIRDirectory连同其内部所有子文件夹和文件一并被删除。
所以,请慎重使用deleteDirectory(true)的方式,避免误操作。
删除文件夹还有一个比较安全的操作,叫做删除到回收站,使用起来也比较简单,我推荐你这么干,因为保险一些。这个方法命名很有趣,不是deleteToTrash,而是moveToTrash。原因很简单,因为deleteToTrash是中国式英语的理解,呵呵。

接下来,我们聊聊删除文件。这个方法是deleteFile,代码如下:

var delFile:File=File.desktopDirectory.resolvePath("1.txt");
delFile.deleteFile();

同 样,删除文件的操作,其目标文件必须存在,否则会有Error #3003: File or directory does not exist 提示。另外,删除文件也最好谨慎操作,有一个更好的方法是就是同样使用删除到回收站(可以删除文件夹和文件,方法一样)moveToTrash,安全又简单。



第五部分,我们讲解一下通过AIR文件访问API来获取文件夹列表。当你的应用有文件系统访问的操作时,除了拷贝,移动,删除,建立之外,用的最多的操作 就是浏览,浏览是基于我们使用习惯的一种本质操作,不论你想干什么,你都需要先浏览。而显示文件夹列表给用户则是浏览行为必须的一步,具体在系统操作上, 从ls命令到资源管理器窗口的文件夹GUI都是体现浏览的途径而已。

在AIR中,getDiretctoryListing()是File类中将文件夹内部的资源组织形式进行数组排列的操作方法,那么对应在此方法之上的,开发者需要使用循环的方式来将Array数组中的内容逐一显示出来。第一个例子很简单,假设我桌面上有个文件夹叫做myFirstAIRDirectory,而文件夹里有3个文件,如下图所示:

我们现在要将这个文件夹里的内容列出来,代码如下:

private function listDirectory():void{
var goalDirectory:File=File.desktopDirectory.resolvePath("myFirstAIRDirectory");
var dirContents:Array=goalDirectory.getDirectoryListing();
for(var i:int=0;i<dirContents.length;i++){
contentTextArea.text+=dirContents[i].name+""+dirContents[i].size+" 字节\n";
}
}

对应的显示组件是TextArea,id是contentTextArea,而触发listDirectory函数的是一个Button的click事件。
你实际上可以用任何一个list类型的控件来输出对于文件夹的扫描结果,这样可以让你的这部分显示起来就好像文件夹浏览器一样,比较专业。



是时候讨论一下同步和异步的问题了。同步(synchronous)和异步(asynchronous)编程不单单是AIR开发中才会遇到的问题,而是所 有编程中都很重要的一部分。往通俗了说,同步已经可以解决很多问题,但是同步解决不了特定环境下的问题。而异步处理用好了就是幸福,用不好就是灾难。单纯 的从功能角度而言,只要逻辑执行效率够高,速度够快(我们期待的快),很多问题用同步就可以解决,比如文件的操作。但是事实不是这样,我们AIR APIs连载的前五篇中的范例,全部是同步操作,这是基于这些范例的操作逻辑足够简单,操作的目标对象被处理的速度非常快,对于使用Flex(Flash Player单线程机制)而言,处于文件(夹)操作之后的逻辑完全可以等待这些小型的文件操作完成再顺序执行,这就是同步,后面的操作要等前面的逻辑执行 完毕后才会执行。但是如果我们操作的是一个1GB或者更大的文件目录体系呢?用同步的方式,后面的逻辑都要等这个操作完成,才能开始执行,显然这个不是同 步可以解决的问题。这时候需要在AIR里引入异步处理,很高兴,Adobe在AIR的很多APIs操作中都有内置的异步方式支持。
异步操作的最大 特点是没有必要非要在一个逻辑执行完毕后才去执行后续的逻辑,这个逻辑初次听起来还有点困惑,但是AIR中异步的实用价值在于当一个操作花费很长时间的时 候,后面的其他逻辑没必要等待,比如大文件夹的浏览和操作,大数据量文件的读写。不仅仅在AIR中,连平时Flash操作XML文件都可能要根据网络速度 和XML数据的大小来决定是否正确的使用异步操作。我们现在就来看一个异步执行语句逻辑的AS范例(你可以贴在Fla文件的timeline的第一帧上执 行,确认你的sandbox在publish setting是local file access only):

//命名一个URLLoader对象
var myloader:URLLoader;
myloader=new URLLoader();
myloader.addEventListener(Event.COMPLETE,completeLoadingHandler);
//从我的站点可以下载rss.xml,就在首页部分右下角的xml logo上,将rss.xml存储到与swf同一目录
myloader.load(new URLRequest("rss.xml"));
trace(myloader.data);

private function completeLoadingHandler(event:Event):void{
trace(myloader.data);
}

上 面的代码你在执行后,会发现第一个trace语句的执行永远都是在myloader.load之前,而且是undefined,而真正有load数据的则 是completeLoadingHandler里面的trace部分,这就说明URLLoader类的load()方法本身就是异步的,所以,第一个 trace语句会先于load执行,所以结果是undefined。

那么使用同步和异步的规则很简单,当逻辑执行可以非常快的完成时,你就可以使用同步操作的方式了。而在AIR的文件访问API中,很多文件类的操作方法兼具同步和异步两个,具体使用哪个,可以根据情况而定,而今后的连载,你也将会看到有异步方法的操作了。

Part7的部分,我们开始讨论AIR的flash.filesystem中的FileStream类,由名字我们就能猜出来这个类是用来操作文件流的,比如,读,写,更新等操作。对于AIR应用读,写,更新文件中的内容,不能单独靠FileStream来完成,还需要使用File和FileMode两个类来协同完成。这个方式在很多编程的环节都类似,比如操作一个Stream Video,你需要使用NetConnection,NetStream和Video三个类来共同完成视频的请求,加载和关闭等逻辑。在我们开始着重讨论FileStream前,我们先来简单看一下FileMode类,这个类很简单,我们经常用到这个类的四个静态常量,分别是APPEND,READ,UPDATE和WRITE。关于这个四个静态常量的解释,我偷个懒,直接把文档描述搬来给各位看:
APPEND
用于要在写入模式下打开的文件,并将所有写入的数据附加到文件末尾。打开文件时,会创建任何不存在的文件。
READ
用于要在只读模式中打开的文件。文件必须存在(不创建缺少的文件)。
UPDATE
用于要在读/写模式中打开的文件。打开文件时,会创建任何不存在的文件。
WRITE
用于要在只写模式中打开的文件。打开文件时,会创建任何不存在的文件,并截断任何现有的文件(删除其数据)。
我们在FileStream的open()和openAsync()方法中使用FileMode的四个静态常量来作为参数,以此来告知FileStream操作文件时应该使用的具体功能。
下面是一段Flex代码,是打开一个文本文件,并向里面写入信息:

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
<mx:Script>
<![CDATA[
private function createFile():void{
var newFile:File=File.desktopDirectory.resolvePath("MyNewFile.txt");
var fileStream:FileStream=new FileStream();
fileStream.openAsync(newFile,FileMode.WRITE);
fileStream.writeUTFBytes("这是写入的文本信息.\n这是写入的文本信息.\n这是写入的文本信息.\n");
//fileStream.writeMultiByte("这是写入的文本信息.\n这是写入的文本信息.\n这是写入的文本信息.\n,"cn-gb");
fileStream.close();
}
]]>
</mx:Script>
<mx:Button label="写入文件" click="createFile()" horizontalCenter="0"/>
</mx:WindowedApplication>

你使用Flex Builder中的AIR项目运行这个Flex文件,你会发现桌面上的一个MyNewFile.txt被创建,打开后,其中是三行同样的文本信息。
上面代码运行的结果,会依照你的操作系统而变的不同。
在win环境下,用任意文本工具打开MyNewFile.txt文本,都可以正常显示结果。
在MAC环境下,文件被生成,如果你用第三方文本编辑器或者Pico打开该文件,你会发现这个Unicode文本文件的信息一切正常。

但是如果你用MACOS系统自身的文本编辑或者Office等软件去打开它,会发现,不论你如何调整这些软件的编码(即使你设置为unicode),打开这个文本文件全部是乱码,而不是正常的中文。目前,我的解决办法是使用writeMultiByte(注释掉writeUTFBytes这一行),然后专门指定其中的编码是cn-gb,这样就可以使用MACOS自带的软件或者Office正常打开查看了。

我猜想这是MACOS自带文本编辑和Office软件上一个默认设置所致,这些软件的默认设置全部是简体中文(MACOS)的方式,所以通过Unicode创建的带有中文的文本便无法被正确处理。总而言之,我们开发的AIR应用如果在使用FileStream进行文件写入的时候,不得不考虑跨平台的兼容方法(很庆幸AIR的确有),而对于WriteUTFBytes方法,如果你希望MAC系统的用户也正常看到信息,就最好谨慎考虑使用。毕竟生成文件后,让MAC用户修改软件配置或者通过命令行Pico查看文本信息显的不太符合简便操作的道理。如果你的应用只针对Win用户,请大胆的使用WriteUTFBytes方法。

Part 7就到这里,Part8将讲解读取,更新和后续添加文件信息的部分。

标签: AS3 -Flash

发表评论:

雨辰 joyimp|@2011-2017 京ICP备16030765号