程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 使用JSR-82 API實現OBEX圖像傳輸

使用JSR-82 API實現OBEX圖像傳輸

編輯:關於JAVA

本文的目的是提供關於無線藍牙技術 Java API(即 JSR-82 API)的實踐體驗。如果您不熟悉藍牙語 義,不要擔心。我將在藍牙協議簡介及其用例(稱為藍牙模式)中介紹這些內容。因為本應用程序將展示 如何使用藍牙技術向其他藍牙設備傳輸圖像,所以還將展示如何使用 JSR-75 的 File Connection API 以程序的方式對移動設備進行訪問。在本文結束時,將獲得能夠向遠程藍牙設備傳輸圖像的指導性示例( 及相關文件)。

藍牙協議

關於藍牙的一個鮮為人知的事實是:它即便不是世界上配置最為廣泛並且最成功的 SOA(面向服務架 構)系統,那麼也是其中之一。藍牙技術得到廣泛的安裝采用(部署的設備超過 5 億台),並且當前的 數據估計每周都有另外五百萬台藍牙設備送出。遠在“面向服務架構”成為專門術語之前,藍牙協議就已 經提供了服務注冊、服務發現和服務調用機制。

因此,藍牙協議結合了面向服務架構並采用 HTTP 和 FTP 之類的其他協議中熟悉使用的客戶端/服務 器通信架構:在客戶端發出請求之前,服務器耐心地等待。當前市場上的藍牙設備能夠以 3 Mb/s 的速率 進行通信,並且可以支持立體聲無線音頻。以下圖 1 顯示了藍牙協議棧的各個層。

圖 1:藍牙協議棧及其層

因為本文的重點是 OBEX,所以我沒有講述圖 1 中所有層的細節,但是我確實希望提供關於主要的支 持 OBEX 層的一些詳細信息。如您所見,該棧的主要協議層之一是 L2CAP(邏輯鏈路控制和適配協議)。 L2CAP 用作其他所有上層之間信息包數據的多路復用器。另一方面,RFCOMM 稱為“虛擬串行端口”層。 需要與支持數據流的設備通信時,RFCOMM 用起來不錯。OBEX(代表對象交換)是最適合文件傳輸的協議 層。借助 OBEX,可以創建消息並向包含有效載荷(也就是要發送的文件)的遠程藍牙設備發送消息以及 重要元數據(如文件名稱、文件大小和文件類型)。

藍牙模式

藍牙模式允許各種性能不同的藍牙設備進行交互和協作。每個模式都是一個針對具體目的定義功能的 用例。例如,如果希望通過移動設備向使用打印機,則兩台設備都必須實現基本打印模式。或者例如,如 果要同步台式機和 PDA 的聯系人列表,這兩台設備必須都支持同步模式。下面的表 1 列出使用藍牙棧 OBEX 協議層的模式。

表 1. 當前基於 OBEX 的模式

模式名稱 縮寫 UUID 對象推送模式 OPP 0x1105 文件傳輸模式 FTP 0x1106 同步模式 SYP 0x1104 靜態圖像傳輸模式 BIP 0x111A 電話簿訪問模式 PBAP 0x1130 基本打印模式 BPP 0x1122

根據藍牙 SIG,已定義的藍牙模式有 30 多種,涉及音頻分配到個人網。在本文中,我們將使用 JSR -82 API 實現對象推送模式,並向任意支持 OPP 的藍牙設備發送圖像。

創建 ImageSender Midlet

ImageSender Midlet 是使用 NetBeans 5.0 IDE 的 NetBeans Mobility Pack 創建的。Mobility Pack 包含一個非常方便的 GUI 設計工具,它允許移動開發人員使用拖放技術快速創建移動應用程序。 ImageSender Midlet 包含若干個靜態 GUI 組件(也就是非動態創建的組件),Mobility Pack 在創建 GUI 組件及其之間的工作流方面非常高效。以下圖 2 描述了用於創建 ImageSender Midlet 的 NetBeans 項目。

圖 2:用來創建 ImageSender Midlet 的 NetBeans 項目

為了使 ImageSender 完成其任務(如從文件系統讀取文件,以及向遠程藍牙設備發送數據), ImageSender 所使用的內部類封裝了以下三個重要功能領域:

讀取文件並遍歷文件系統(將由 FileNavigator 處理)

發現遠程藍牙設備(將由 BTUtility 處理)

使用對象推送模式向遠程藍牙設備發送文件(將由 FilePusher 處理)

學習了這些預備知識,下面開始實現!

ImageSender.FileNavigator

以下圖 3 是一個程序表,它顯示了 ImageSender Midlet 和其內部類(FileNavigator)之間的交互 ,FileNavigator 專門用來讀取和遍歷移動設備的文件系統。

圖 3:顯示 FileNavigator 內部類用法的程序圖

首先,ImageSender 獲取一個 FileNavigator 實例並調用 getListofFolder() 方法,該方法返回一 個 javax.microedition.lcdui.List。而 FileNavigator 將使用 JSR-75 File Connection API 的 FileSystemRegsitry 類獲取文件系統“根”的枚舉 ,也就是設備的載入點。如果移動設備包含可移動介 質(如 SD 內存卡),它也將在枚舉中顯示。對於文件系統的每個根,都對其建立一個 FileConnection 以確定它是文件還是文件夾。 這是必需的,因為您肯定希望以不同的方式處理它們(也就是,如果該項 目是文件夾,您希望遍歷該文件夾,但是如果該項目是文件,那麼您將希望打開該文件以獲得其內容)。 枚舉完之後,FileNavigator 內部類將向 ImageSender 返回一個 List,ImageSender 將簡單地在該移動 設備上顯示 List,如以下圖 4 所示。

圖 4:ImageSender 顯示目錄中的文件和文件夾列

因為 FileNavigator 內部類實現了 CommandListener 接口,所以它將處理來自該用戶接口的所有更 改目錄或選擇文件的請求。該方法使得父類 ImageSender 不再負責響應用戶的輸入並了解如何處理該輸 入。內部類已經擁有對 JSR-75 類的引用,這些類允許其連接到文件系統,所以非常適合處理用戶的請求 並處理文件系統。下面是 FileNavigator 的 commandAction() 方法的一部分;當用戶選擇該列表中的項 目時將執行這部分代碼:

    ...
           if(isFolderSelected == true){
             // the user selected a folder, so navigate down it
             String folderUrl = (String)curr_dir_urls.elementAt (selectedIndex);
             getDisplay().setCurrent(fileNavigator.getListofFolder(folderUrl, false));
           } else {
             getDisplay().setCurrent(get_fileSelectedAlert(), displayable);
             // the user has obviously selected a file, so let's read it in
             String file_url = (String)curr_dir_urls.elementAt (selectedIndex);
             ...
               fileConn = (FileConnection) Connector.open(file_url);
               InputStream is = fileConn.openInputStream();
               // now let's read the file in into our byte[]
               file = new byte[(int)fileConn.fileSize()];
               is.read(file);
               is.close();
     ...

可以看到,如果用戶選擇了一個文件夾,FileNavigator 將遍歷該文件夾,返回另一個列表並顯示它 。然後,如果該用戶選擇一個文件,FileNavigator 內部類將打開到該文件的 FileConnection 並以名為 “file”的字節數組讀取其內容。

ImageSender.BTUtility

ImageSender Midlet 使用的第二個輔助類是 BTUtility。您可能會猜到,BTUtility 內部類封裝了其 余代碼所調用的所有 JSR-82 藍牙 API 方法。BTUtility 主要向 ImageSender Midlet 提供兩個領域的 功能:發現附近的遠程藍牙設備,並對這些設備執行服務搜索。圖 5 為顯示 ImageSender 如何使用 BTUtility 的程序圖。

圖 5:描述 ImageSender 和 BTUtility 之間交互的程序圖

如上所示,ImageSender 獲得了 BTUtility 的新實例之後,該實例包含對 LocalDevice 和 DiscoveryAgent 類的引用。為了發現附近的藍牙設備,必須調用 DiscoveryAgent.startInquiry()。該 實現將異步調用在該區域發現的每台藍牙設備的 deviceDiscovered() 方法。最後,如果沒有發現更多藍 牙設備,JVM 將調用 inquiryCompleted() 方法。圖 6 顯示了 BTUtility 類發現的啟用藍牙技術的設備 列表。

圖 6:BTUtility 類所發現的藍牙設備

BTUtility 執行的另一項工作是搜索遠程藍牙設備上的服務。如以下圖 7 所示,搜索服務過程比發現 設備需要占用更多 CPU 時間。這就是為什麼設備發現過程可以從 BTUtility 的構造函數發起,但是服務 搜索部分必須以 Thread 啟動。

圖 7:使用 BTUtility 搜索遠程藍牙設備上的服務。

幸運的是,BTUtility 擴展了 Thread 類,因此可防止該用戶接口掛起。當 BTUtility 調用 DiscoveryAgent.searchServices() 時,如果發現匹配的服務,JVM 將異步調用其 serviceDiscovered() 方法。服務搜索過程完成時,JVM 將調用其 serviceSearchCompleted() 方法。作為備用方式,可以調用 DiscoveryAgent.selectService(),但是根據 JSR-82 規范,它只能返回附近一個服務提供商的 connectionURL。回顧上面的圖 6 可知,在大多數環境中,您不知道文件將發送給誰。下面是完整的 BTUtility 清單:

  /**
   * This is an inner class that is used for finding
   * Bluetooth devices in the vicinity
   *
   */
   class BTUtility extends Thread implements DiscoveryListener {
     Vector remoteDevices = new Vector();
     Vector deviceNames = new Vector();
     DiscoveryAgent discoveryAgent;
     // obviously, 0x1105 is the UUID for
     // the Object Push Profile
     UUID[] uuidSet = {new UUID(0x1105) };
     // 0x0100 is the attrubute for the service name element
     // in the service record
     int[] attrSet = {0x0100};
     public void run(){
       try {
         RemoteDevice remoteDevice = (RemoteDevice)remoteDevices.elementAt (get_devicesList().getSelectedIndex());
         discoveryAgent.searchServices(attrSet, uuidSet, remoteDevice , this);
       } catch(Exception e) {
         e.printStackTrace();
       }
     }
     public BTUtility() {
       // clear the list out, just in case it's not
       get_devicesList().deleteAll();
       try {
         LocalDevice localDevice = LocalDevice.getLocalDevice();
         discoveryAgent = localDevice.getDiscoveryAgent();
         //deviceDiscoveryPanel.updateStatus(" Searching for Bluetooth devices in the vicinity...n");
         discoveryAgent.startInquiry(DiscoveryAgent.GIAC, this);
       } catch(Exception e) {
         e.printStackTrace();
       }
     }
     public void deviceDiscovered(RemoteDevice remoteDevice, DeviceClass cod) {
       try{
         remoteDevices.addElement(remoteDevice);
       } catch(Exception e){
         e.printStackTrace();
       }
     }

     public void inquiryCompleted(int discType) {

       if (remoteDevices.size() > 0) {
         // the discovery process was a success
         // so let's out them in a List and display it to the user
         for (int i=0; i

在 BTUtility 內部類中,您可能會注意到在多處都使用了十六進制值。尤其是:

    UUID[] uuidSet = {new UUID(0x1105) };
     // 0x0100 is the attribute for the service name element
     // in the service record
     int[] attrSet = {0x0100};

現在,如果您記得上面的表 1 中的值,就可以理解為什麼創建 0x1105 UUID 值,因為它是對象推送 模式的 UUID。然而,在 attrSet 中也使用了 0x0100 值,通過它可以了解遠程服務的服務名稱。

ImageSender.FilePusher

到目前為止我們有哪些收獲?首先,我們擁有一個可以使用 JSR-75 FileConnection API 浏覽文件系 統的 Midlet(當然這都是內部類 FileNavigator 實現的)。Midlet 還能夠發現附近的遠程藍牙設備並 確定該設備上可用的服務(這項功能是由另一個內部類 BTUtility 提供的)。所以,現在只需實現使用 BBEX 向遠程藍牙設備發送文件的機制。

FilePusher 通過使用 org.netbeans.microedition.lcdui.WaitScreen(如圖 8 所示)並實現 org.netbeans.microedition.util.CancellableTask(它擴展了 Runnable)輕而易舉地完成了這項任務 。

圖 8:使用 WaitScreen 的 NetBeans 流設計快照

如上圖所示,WaitScreen 將執行 CPU 密集型任務(如執行網絡 I/O)。該任務必須實現 CancellableTask 接口(正是 FilePusher 內部類實現了該功能)。使用 IDE,可以用圖形的方式連接到 成功或失敗屏幕(具體取決於操作結果)。FilePusher 代碼如以下列表所示:

  class FilePusher implements CancellableTask{
     // this is used for the purposes of the Cancellable task
     boolean isOperationFailed = false;
     // this is the failure message used by the Cancellable task
     String failure_message = null;
     // this is the connection object to be used for
     // bluetooth i/o
     Connection connection = null;
     public FilePusher(){
     }
     // this is method 1 of 4 methods needed to be implemented by
     // the CancellableTask interface
     public boolean cancel(){
       // sorry this can't be cancelled
       return false;
     }
     // this is method 2 of 4 needed to be implemented by
     // the CancellableTask interface
     public String getFailureMessage(){
       // of course, if there's no problem
       // then this method should return null as specified by the javadoc
       return failure_message;
     }
     // this is method 3 of 4 needed to be implemented by
     // the CancellableTask interface
     public boolean hasFailed(){
       return isOperationFailed;
     }
     // this is method 4 of 4 needed to be implemented by
     // the CancellableTask interface   
     public void run(){
       try{
         connection = Connector.open(btConnectionURL);
         // connection obtained
         // now, let's create a session and a headerset objects
         ClientSession cs = (ClientSession)connection;
         HeaderSet hs = cs.createHeaderSet();
         // now let's send the connect header
         cs.connect(hs);
         hs.setHeader(HeaderSet.NAME, filename);
         hs.setHeader(HeaderSet.TYPE, "image/jpeg");
         hs.setHeader(HeaderSet.LENGTH, new Long(file.length));
         Operation putOperation = cs.put(hs);
         OutputStream outputStream = putOperation.openOutputStream();
         outputStream.write(file);
         // file push complete
         outputStream.close();
         putOperation.close();
         cs.disconnect(null);
         connection.close();
       } catch (Exception e){
         isOperationFailed = true;
         failure_message = e.toString();
       }

     }

   } 

現在,通過研究 ObjectPusher 內部類的 run() 方法來深入地了解 OBEX 協議。了解 OBEX 協議的最 佳方式是觀察 OBEX 客戶端和服務器之間的交互,如以下圖 9 所示:

圖 9:使用 OBEX 協議的客戶端和服務器交互示例

圖 9 中所示的場景發生在客戶端和服務器建立了物理藍牙連接的情況下,在 FilePusher 中,這是通 過以下代碼實現的:

        connection = Connector.open(btConnectionURL);
         // connection obtained

建立了連接之後,就應該通過將該連接對象更改成 ClientSession 對象來創建客戶端和服務器之間的 會話:

        // now, let's create a session and a headerset object
         ClientSession cs = (ClientSession)connection;

現在,建立會話之後,再看圖 9。請注意客戶端發送 OBEX 操作(如 Connect、Setpath、Get、Push 等),服務器將借助 OBEX Response Code(160、161、193、196等)響應客戶端。以下代碼展示如何發 送 Connect 操作:

        // now let's send the connect operation
         cs.connect(hs);

從以下代碼片斷可以看出,OBEX 是通過藍牙技術傳輸文件的首選方式(相比 RFCOMM 或 L2CAP),因 為可以使用報頭設置關於要傳輸文件的元數據:

        hs.setHeader(HeaderSet.NAME, filename);
         hs.setHeader(HeaderSet.TYPE, "image/jpeg");
         hs.setHeader(HeaderSet.LENGTH, new Long(file.length));

這樣,接受者就能在接收該文件之前了解文件的名稱、文件類型和大小,而這是藍牙棧中其他協議層 不支持的強大功能。現在,設置完合適的報頭之後,將向服務器發送 Put 操作:

    Operation putOperation = cs.put(hs);

該方法返回時,將得到一個 Operation 對象,您可以對其打開 OutputStream 並發送存儲在名為 “file”的字節數組中的文件數據(如果記得的話,可以知道我們是從 FileNavigator 內部類填充 “file”的)。現在如果想了解服務器的響應代碼,可以對 Operation 調用 getResponseCodes() 方法 。

因此,使用從 putOperation 打開的 OutputStream 向目的地發送文件之後,發送最後的 OBEX 操作 來完成程序:Disconnect Operation。

    cs.disconnect(null);

結束語

可以看出,創建使用 JSR-82 和 JSR-75 API 進行無線圖像傳輸的復合應用程序並不需要太多工作。 盡管本文展示如何通過 OBEX 傳輸圖像,但是您會發現可以輕松修改這些代碼來發送文件類型。享受編碼 的樂趣吧!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved