實作 IFilePromise 介面

Adobe AIR 2 以及更新的版本

若要為無法使用 URLFilePromise 物件存取的資源提供檔案承諾,您可以在自訂類別中實作 IFilePromise 介面。IFilePromise 介面會定義 AIR 執行階段使用的方法和屬性,用於存取一旦檔案承諾投入之後,會被寫入檔案中的資料。

IFilePromise 實作會將另一個物件傳送至可以提供資料給檔案承諾的 AIR 執行階段。 這個物件必須實作 IDataInput 介面,AIR 執行階段會透過這個介面去讀取資料。 例如,URLFilePromise 類別 (它會實作 IFilePromise) 會使用 URLStream 物件當做資料提供者。

AIR 可以同步或非同步讀取資料。IFilePromise 實作會在 isAsync 屬性傳回適當的值,報告支援的存取模式。如果提供非同步資料存取,資料提供者物件必須實作 IEventDispatcher 介面以及傳送必要的事件,例如 openprogress 以及 complete

您可以使用自訂類別,或者以下其中一個內建的類別,當做檔案承諾的資料提供者。

  • ByteArray (同步)

  • FileStream (同步或非同步)

  • Socket (非同步)

  • URLStream (非同步)

若要實作 IFilePromise 介面,您必須為以下的函數和屬性提供程式碼:

  • open():IDataInput — 傳回資料提供者物件 (也就是從此處讀取檔案承諾所需的資料)。物件必須實作 IDataInput 介面。若非同步提供資料,物件也必須實作 IEventDispatcher 介面以及傳送所需的事件 (請參閱在檔案承諾中使用非同步資料提供者)。

  • get relativePath():String — 提供路徑,其中包括建立之檔案的檔案名稱。路徑會以拖放作業中使用者所選擇的投放位置為基準進行解析。若要確定路徑會針對主機作業系統使用適當的分隔符號字元,請在指定內含目錄的路徑時,使用 File.separator 常數。 您可以新增 setter 函數或者使用建構函式參數,允許在執行階段設定路徑。

  • get isAsync():Boolean — 將資料提供者物件是否同步或非同步提供它的資料這件事,告知 AIR 執行階段。

  • close():void — 當資料完全讀取時由執行階段呼叫 (或者發生錯誤禁止進一步讀取)。您可以將這個函數用於清除資源。

  • reportError( e:ErrorEvent ):void — 當讀取資料發生錯誤時,由執行階段呼叫。

在涉及檔案承諾的拖放作業時,所有的 IFilePromise 方法都是由執行階段呼叫的。一般情況下,您的應用程式邏輯不應該直接呼叫任何這些方法。

在檔案承諾中使用同步資料提供者

實作 IFilePromise 介面最簡單的方法就是使用同步資料提供者物件,例如 ByteArray 或同步的 FileStream。 在以下範例中,會建立 ByteArray 物件、填入資料並在呼叫 open() 方法時傳回。

package 
{ 
    import flash.desktop.IFilePromise; 
    import flash.events.ErrorEvent; 
    import flash.utils.ByteArray; 
    import flash.utils.IDataInput; 
     
    public class SynchronousFilePromise implements IFilePromise 
    { 
        private const fileSize:int = 5000; //size of file data 
        private var filePath:String = "SynchronousFile.txt"; 
         
        public function get relativePath():String 
        { 
            return filePath; 
        } 
         
        public function get isAsync():Boolean 
        { 
            return false; 
        } 
         
        public function open():IDataInput 
        { 
            var fileContents:ByteArray = new ByteArray(); 
             
            //Create some arbitrary data for the file 
            for( var i:int = 0; i < fileSize; i++ ) 
            { 
                fileContents.writeUTFBytes( 'S' ); 
            } 
             
            //Important: the ByteArray is read from the current position             
            fileContents.position = 0; 
            return fileContents; 
        } 
         
        public function close():void 
        { 
            //Nothing needs to be closed in this case. 
        } 
         
        public function reportError(e:ErrorEvent):void 
        { 
            trace("Something went wrong: " + e.errorID + " - " + e.type + ", " + e.text ); 
        } 
    } 
}

實際應用方面,同步檔案承諾應用面不廣。若資料量很少,只要在暫存目錄中建立檔案,然後將一般檔案清單陣列新增至拖放剪貼簿之中即可。在另一方面,若資料量很大或者產生資料時需要進行大量的計算,則需要長時間執行同步程序。長時間執行同步程序會封鎖 UI 更新而且封鎖時間相當長,讓您的應用程式看起來似乎沒有反應。若要避免這種問題,您可以建立一個由計時器驅動的非同步資料提供者。

在檔案承諾中使用非同步資料提供者

當您使用非同步資料提供者物件時,IFilePromise isAsync 屬性必須是 true 而且 open() 方法傳回的物件必須實作 IEventDispatcher 介面。執行階段會偵聽多個替代事件,如此不同的內部物件就可以當做是資料提供者。例如,FileStream 和 URLStream 物件會傳送 progress 事件,而 Socket 物件會傳送 socketData 事件。執行階段會從這些所有的物件偵聽正確的事件。

以下事件會產生從資料提供者物件讀取資料的程序:

  • Event.OPEN — 通知執行階段資料來源已經就緒。

  • ProgressEvent.PROGRESS — 通知執行階段資料可以取得。 執行階段會從資料提供者物件讀取可用的資料量。

  • ProgressEvent.SOCKET_DATA — 通知執行階段資料可以取得。 socketData 事件是由通訊端式物件傳送的。至於其他物件類型,您應該傳送一個 progress 事件。(執行階段會偵聽這兩個事件,以便偵測何時可以讀取資料)。

  • Event.COMPLETE — 通知執行階段資料已經全部讀取了。

  • Event.CLOSE — 通知執行階段資料已經全部讀取了。(執行階段會為了這個目的來偵聽 closecomplete)。

  • IOErrorEvent.IOERROR — 通知執行階段讀取資料時發生錯誤。執行階段會終止建立檔案,然後呼叫 IFilePromise close() 方法。

  • SecurityErrorEvent.SECURITY_ERROR — 通知執行階段發生安全性錯誤。 執行階段會終止建立檔案,然後呼叫 IFilePromise close() 方法。

  • HTTPStatusEvent.HTTP_STATUS — 執行階段會將它與 httpResponseStatus 一起使用,以便確定可用的資料就是想要的內容,而不是錯誤訊息 (例如 404 網頁)。 以 HTTP 通訊協定為基礎的物件應傳送這個事件。

  • HTTPStatusEvent.HTTP_RESPONSE_STATUS — 執行階段會將它與 httpStatus 一起使用,以便確定可用的資料就是想要的內容。以 HTTP 通訊協定為基礎的物件應傳送這個事件。

資料提供者應該按照以下順序傳送這些事件:

  1. open 事件

  2. progresssocketData 事件

  3. completeclose 事件

備註: 內建的物件、FileStream、Socket 以及 URLStream,會自動傳送正確的事件。

以下範例會使用自訂的非同步資料提供者來建立一個檔案承諾。 這種資料提供者類別會擴充 ByteArray (為了支援 IDataInput) 以及實作 IEventDispatcher 介面。在每一個計時器事件中,這個物件會產生一堆資料,然後傳送一個 progress 事件來通知執行階段資料可以取得。產生足夠的資料後,這個物件會傳送一個 complete 事件。

package 
{ 
import flash.events.Event; 
import flash.events.EventDispatcher; 
import flash.events.IEventDispatcher; 
import flash.events.ProgressEvent; 
import flash.events.TimerEvent; 
import flash.utils.ByteArray; 
import flash.utils.Timer; 
 
[Event(name="open", type="flash.events.Event.OPEN")] 
[Event(name="complete",  type="flash.events.Event.COMPLETE")] 
[Event(name="progress", type="flash.events.ProgressEvent")] 
[Event(name="ioError", type="flash.events.IOErrorEvent")] 
[Event(name="securityError", type="flash.events.SecurityErrorEvent")] 
public class AsyncDataProvider extends ByteArray implements IEventDispatcher 
{ 
    private var dispatcher:EventDispatcher = new EventDispatcher(); 
    public var fileSize:int = 0; //The number of characters in the file 
    private const chunkSize:int = 1000; //Amount of data written per event 
    private var dispatchDataTimer:Timer = new Timer( 100 ); 
    private var opened:Boolean = false; 
     
    public function AsyncDataProvider() 
    { 
        super(); 
        dispatchDataTimer.addEventListener( TimerEvent.TIMER, generateData ); 
    } 
 
    public function begin():void{ 
        dispatchDataTimer.start(); 
    } 
     
    public function end():void 
    { 
        dispatchDataTimer.stop(); 
    } 
    private function generateData( event:Event ):void 
    { 
        if( !opened ) 
        { 
            var open:Event = new Event( Event.OPEN ); 
            dispatchEvent( open );     
            opened = true; 
        } 
        else if( position + chunkSize < fileSize ) 
        { 
            for( var i:int = 0; i <= chunkSize; i++ ) 
            { 
                writeUTFBytes( 'A' ); 
            } 
            //Set position back to the start of the new data 
            this.position -= chunkSize; 
            var progress:ProgressEvent = 
                new ProgressEvent( ProgressEvent.PROGRESS, false, false, bytesAvailable, bytesAvailable + chunkSize); 
            dispatchEvent( progress ) 
        } 
        else 
        { 
            var complete:Event = new Event( Event.COMPLETE ); 
            dispatchEvent( complete );         
        } 
    } 
     //IEventDispatcher implementation 
    public function addEventListener(type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void 
    { 
        dispatcher.addEventListener( type, listener, useCapture, priority, useWeakReference ); 
    } 
     
    public function removeEventListener(type:String, listener:Function, useCapture:Boolean=false):void 
    { 
        dispatcher.removeEventListener( type, listener, useCapture ); 
    } 
     
    public function dispatchEvent(event:Event):Boolean 
    { 
        return dispatcher.dispatchEvent( event ); 
    } 
     
    public function hasEventListener(type:String):Boolean 
    { 
        return dispatcher.hasEventListener( type ); 
    } 
     
    public function willTrigger(type:String):Boolean 
    { 
        return dispatcher.willTrigger( type ); 
    } 
} 
}
備註: 因為本範例中的 AsyncDataProvider 類別擴充 ByteArray,所以它無法也擴充 EventDispatcher。 為了實作 IEventDispatcher 介面,這個類別會使用內部的 EventDispatcher 物件,然後將 IEventDispatcher 方法呼叫轉送至該內部物件。您也可以擴充 EventDispatcher 以及實作 IDataInput (或實作這兩個介面)。

非同步 IFilePromise 實作幾乎與同步實作完全相同。主要的差別在於 isAsync 會傳回 trueopen() 方法會傳回非同步資料物件:

package 
{ 
    import flash.desktop.IFilePromise; 
    import flash.events.ErrorEvent; 
    import flash.events.EventDispatcher; 
    import flash.utils.IDataInput; 
     
    public class AsynchronousFilePromise extends EventDispatcher implements IFilePromise 
    { 
        private var fileGenerator:AsyncDataProvider; 
        private const fileSize:int = 5000; //size of file data 
        private var filePath:String = "AsynchronousFile.txt"; 
                 
        public function get relativePath():String 
        { 
            return filePath; 
        } 
         
        public function get isAsync():Boolean 
        { 
            return true; 
        } 
         
        public function open():IDataInput 
        { 
            fileGenerator = new AsyncDataProvider(); 
            fileGenerator.fileSize = fileSize; 
            fileGenerator.begin(); 
            return fileGenerator; 
        } 
         
        public function close():void 
        { 
            fileGenerator.end(); 
        } 
         
        public function reportError(e:ErrorEvent):void 
        { 
            trace("Something went wrong: " + e.errorID + " - " + e.type + ", " + e.text ); 
        } 
    } 
}