EXmobi官方文檔javascript
ExMobi門戶:http://www.exmobi.cnphp
ExMobi論壇:http://bbs.exmobi.cncss
支撐電話:400-110-1111 025-6677-7333html
官方微博(新浪):@ExMobi前端
南京烽火星空通訊發展有限公司java
2014年node
第1篇ExMobi基礎篇mysql
ExMobi是烽火星空公司推出的跨平臺移動應用開發中間件產品。ExMobi經過全面的數據集成技術和豐富的跨平臺客戶端展示能力,將業務系統快速、安全、高效的移植於移動終端,並從開發(IDE環境)、集成(IT系統對接、雲服務)、打包(各個操做系統的應用打包)、發佈(應用的運行)、管理(日誌管理,更新管理)上提供了一整套的解決方案。jquery
ExMobi包含了一系列的技術和產品,主要包括:ExMobi客戶端、ExMobi服務端、MBuilder集成開發工具以及ExMobi產品門戶。android
ExMobi產品門戶網址爲www.exmobi.cn ,它包括ExMobi產品中心、EDN開發者門戶、BBS論壇(bbs.exmobi.cn)以及ExMobi開放平臺。
ExMobi產品中心能夠了解到ExMobi產品的功能和最新動態;EDN開發者門戶爲開發者提供應用的開發、發佈和管理等一站式服務;BBS論壇爲開發者提供可持續的學習和交流空間;ExMobi開放平臺提供廣闊的基於ExMobi的原生插件開發資源,開放平臺開發者開發的插件能夠做爲ExMobi客戶端引擎的一部分。
EDN開發者門戶、ExMobi開放平臺、BBS論壇和MBuilder均須要在ExMobi產品門戶上進行註冊成功後獲得帳號和密碼方能登錄使用。
ExMobi客戶端負責應用在移動終端的展現和交互,以及與ExMobi服務端的通訊。它主要包含:PC模擬器客戶端、Android客戶端、IOS客戶端、Windows8客戶端等。
ExMobi客戶端實現跨平臺的原理,是在不一樣移動終端上將一樣的功能和交互封裝成統一的接口,如:XHTML、JavaScript、CSS、主題、Native插件接口等。對於移動應用開發者來講實際上就像WEB開發同樣開發一套XHTML的應用便可進行跨平臺的數據展示和交互。而可以執行這種特殊應用的引擎咱們稱爲「基座」。因此,對於一個完整的ExMobi客戶端應該包含基座和應用。
而爲了方便開發調試,ExMobi客戶端存在兩種狀態,一種是基座狀態,一種是打包狀態。
基座狀態主要在開發調試時使用,安裝基座客戶端的時候,裏面是沒有應用的。客戶端安裝好以後,打開基座客戶端首先看到的就是基座,在基座的「設置」功能中配置好開發調試環境的IP和端口便可方便的安裝和卸載應用,並對應用進行開發調試,而不是像Native原生開發同樣每次都要編譯,方便了調試也節省了編譯的時間。
打包狀態爲應用開發完畢後將基座和應用一塊兒打包生成最後發佈安裝包的狀態。打包客戶端實際上就是在打開客戶端的時候把基座隱藏起來直接看到應用。打包客戶端能夠使用ExMobi開發者門戶的雲打包服務進行在線打包。
下圖爲ExMobi基座客戶端效果圖。
ExMobi服務端負責對ExMobi客戶端請求過來的數據進行處理,並把處理結果響應給客戶端進行操做。因此,它主要的功能就是對數據的集成能力。
ExMobi服務端主要包含4大組件:ExMobi管理平臺(EMP)、基本核心引擎(BCS)、統一推送引擎(PNS)、統一文檔轉換引擎(DCS)。
EMP爲ExMobi的管理平臺,對ExMobi應用和客戶端的管理、終端用戶使用受權、統計報表展示、其餘引擎和服務的管理等。
BCS爲數據集成的服務引擎,主要包括:HTTP請求的模擬、Web Service集成、數據庫集成、標準接口集成、接口發佈等。
PNS爲統一推送引擎,實現與BCS的對接,經過UDP/TCP Push、二進制短信push、APNS/C2DM等通道實現應用的統一推送。
DCS爲統一文檔轉換引擎,能夠對標準OFFICE文檔、壓縮包、圖片等格式進行支持。也支持對書生SEP、方正CEB、點聚AIP等特殊格式文檔進行轉換在客戶端展現。
下圖爲部署了ExMobi服務端後在瀏覽器上看到的EMP管理平臺的效果圖:
MBuilde是爲了方便開發者開發移動應用而研發的一款基於Eclipse的ExMobi移動應用集成開發工具。
MBuilder包括ExMobi的Eclipse插件、ExMobi的PC模擬器基座客戶端以及ExMobi服務端的DEV開發版本(BCS-mini版本+PNS-mini版本+DCS-mini版本,不包含EMP)。
使用MBuilder就像使用Eclipse或者其餘開發工具同樣,可以方便的建立、編輯、調試和發佈應用。
ExMobi的應用包含客戶端資源和服務端資源。客戶端資源運行在ExMobi客戶端;服務端資源運行在服務端。
應用的運行原理以下圖所示:
ExMobi應用的運行原理其實是ExMobi服務端將第三方系統數據源轉換爲ExMobi客戶端識別的語言在不一樣的終端進行展現和交互的一個過程。而服務端轉換的依據是經過客戶端發起的請求指令通過MAPP路由傳遞到服務端的。
MBuilder集成開發工具爲開發者提供了簡單易操做的開發環境。正確使用MBuilder能夠極大的提升開發的效率。
安裝操做系統要求:支持XP、WIN7(32bit、64bit)。
JDK版本要求:JDK1.6.0_20。
MBuilder版本要求:2.X.X版本。
JDK和MBuilder安裝包能夠經過下載和安裝指引頁面下載:
http://www.exmobi.cn/sdkdownload.jsp
運行安裝文件,以下圖所示:
注:文件名根據實際版本狀況而定,其中2.3.6是版本號。
點擊「下一步」後選擇「安裝目錄」
安裝目錄後點擊「安裝」便可執行安裝操做。
安裝過程要求安裝的組件也須要安裝。
模擬器依賴組件:
WebKit組件庫:
所有安裝完畢,點擊「完成」按鈕便可完成MBuilder的安裝:
安裝完成後,會在桌面有MBuilder的快捷圖標:
點擊便可運行MBuilder,運行前須要選擇工做空間,以下圖所示:
因爲卸載MBuilder的時候整個MBuilder安裝目錄的內容都會被刪掉,爲了保險起見,請在MBuilder外部建立工做空間,而後選擇該目錄。
選擇完畢後,點擊「OK」按鈕進到MBuilder工做界面,因爲首次使用須要進行進行登錄,在登錄前須要在ExMobi產品門戶進行註冊(www.exmobi.cn),使用註冊的帳號和密碼進行登錄,以下圖所示:
若帳號、密碼正確,便可進到MBuilder的工做界面:
MBuilder提供了在線升級的功能,點擊主菜單-「help」-「about MBuilder」便可看到界面,以下圖所示:
點擊「check for updates」便可進行在線升級。
在MBuilder安裝根目錄下,找到「uninst.exe」文件,點擊執行便可進入卸載嚮導。
在MBuilder的安裝目錄下,存在以下目錄:
MBuilder
|---eclipse 存放eclipse開發平臺相關文件
| |--- dropins 存放MBuilder 插件目錄
|---env 依賴組件的安裝包(ExMobi/Webkit組件庫)
|---ExMobi ExMobiPC模擬器基座客戶端
|---ExMobi-server ExMobi服務端DEV版
|---apache-tomcat-7.0.22 自帶服務依賴的tomcat
|---uninst.exe 卸載程序
MBuilder的ExMobi客戶端和服務端是能夠單獨升級的。能夠到ExMobi產品門戶下載PC模擬器安裝包和ExMobi服務端的壓縮包,安裝或者解壓相應的目錄到MBuilder的客戶端和服務端的安裝目錄下便可。
首選項裏面主要對配置文件編碼、Tomcat容器(自動配置)、服務端及客戶端配置(自動配置)、第三方服務引擎配置、文件模板配置。
首選項位於主菜單-「window」-「preferences」選項,以下圖所示:
選擇後會進入到首選項的配置頁面,以下圖所示:
本節的配置均在首選項中進行。
ExMobi中全部文件的編碼要求爲「UTF-8」編碼,若是編碼不對可能會形成亂碼和提交錯誤等問題。
在首選項中通常須要配置的編碼的文件爲:HTML、CSS、JS、JAVA、JSP等,以下圖所示:
Tomcat默認已是自動配置好的,此處只須要對其進行驗證一下便可,以下圖所示:
服務端及客戶端的配置也是默認配好的,此處也只須要進行確認便可,以下圖所示:
服務引擎指的是ExMobi服務端的PNS、DCS、BCS引擎,因爲ExMobi服務端支持分佈式部署,因此這些引擎是相對獨立部署在不一樣的服務器上的。
若是使用不一樣服務器上服務,則須要在此處進行配置。
而通常開發者只須要使用本機的服務便可,配置本機服務請看下圖:
MBuilder中提供了對全部可編輯文件格式的模板配置,在建立相應格式文件的時候就能夠經過模板快速建立出帶必定結構的文件。
本小節以XSL文件爲例,說明文件模板的配置。
在首選項中找到XSL文件的templates選項,以下圖所示:
點擊「edit」按鈕後,便可對相應的模板進行修改,也能夠點擊「new」按鈕新建一個模板,以下圖所示:
編輯好的模板能夠在MBuilder中使用,以下圖所示:
MBuilder工做界面以下:
主菜單:MBuilder的全部功能操做菜單。
快捷菜單:經常使用的MBuilder功能。
應用程序目錄結構:應用的完整結構,對應用資源的管理。
代碼編輯區域:應用中可編輯資源的編輯區域,可對資源內容進行修改。
控制檯和工具區域:主要是開發輔助調試的工具展現區域。
Tomcat是ExMobi服務端的運行容器,Tomcat的啓動、中止和重啓就是ExMobi服務端的啓動、中止和重啓。
Tomcat的啓動、中止、重啓操做依次在快捷菜單中,以下圖所示:
啓動 中止 重啓
點擊第一個按鈕便可啓動Tomcat,啓動成功會在控制檯中看到成功日誌,以下圖所示:
若是有客戶端要接入該服務端,須要配置對應的IP和端口,好比:
這裏的IP和端口就是TOMCAT所在服務器的IP和TOMCAT啓動的端口。若是是同一臺機器,好比PC模擬器能夠使用127.0.0.1的IP,固然,使用實際的IP也能夠,從MBuilder的控制檯能夠看出普通端口是8001,加密端口是8443,若是開啓「使用數據加密」,須要填寫加密端口;若是是用真機設備上的客戶端,須要確保設備和MBuilder的網絡是通的,而且填寫的IP是同一網段的實際IP,端口仍然使用TOMCAT啓動的端口。
MBuilder的新建和導入嚮導位於快捷菜單中,以下圖所示:
點擊該按鈕便可新建一個應用或者導入一個已經存在的應用,以下圖所示:
繼續每個「next」或有相應的配置,最後一個配置是在應用中使用模板和皮膚主題,以下圖所示:
配置完成後點擊「finish」便可完成應用的新建或者導入。
ExMobi應用包含了客戶端和服務端資源,而客戶端資源是運行在客戶端的,服務端資源是運行在服務端的。
在建立應用的時候應用的資源僅僅存在MBuilder的工做空間中,還沒有部署到客戶端和服務端,因此須要進行代碼同步。
MBuilder中提供了自動代碼同步的功能,能夠把應用的客戶端和服務端資源分別同步到PC模擬器基座客戶端和ExMobi的DEV開發版中,這樣才能在基座中進行開發調試。
自動同步按鈕的圖標位於快捷菜單中,圖標有兩種狀態:啓動和關閉。
以下圖所示的狀態爲自動同步關閉狀態,在該狀態下應用不會自動同步:
以下圖所示的狀態爲自動同步啓動狀態,在該狀態下應用纔會自動同步:
要同步哪一個應用須要先選中該應用,而後再點擊自動同步按鈕,以下圖所示:
應用開發完成只是運行在MBuilder的開發環境中,要投入生產還須要將完整應用包部署到ExMobi的工程環境中,而且須要在EDN開發者門戶中將應用的客戶端資源包和基座打成打包客戶端。
這裏涉及兩個包的導出:一個是完整應用包的導出,一個是客戶端資源包的導出。
1、完整應用包的導出功能在MBuilder的快捷菜單中,以下圖所示:
點擊按鈕前須要先選中要導出的應用,選中後點擊該按鈕,會彈出以下導出嚮導界面:
點擊「finish」便可導出指定應用的完整應用包到指定的目錄,格式爲zip。
2、客戶端資源包的導出也是位於快捷菜單,以下圖所示:
點擊該按鈕,便可進入導出客戶端資源包的導航頁面,以下圖所示:
第一步:選擇應用的版本:
第二步:選擇要生成客戶端資源的應用,以及要生成的分辨率、存放目錄等,以下圖所示:
點擊「finish」便可完成應用客戶端資源的打包,包的格式爲zip。
PC模擬器基座客戶端是爲了進行開發調試方便而研發的一款產品。它能夠最大限度的模擬移動設備終端的效果,使開發達到所見即所得的目的。
打開PC模擬器基座客戶端的按鈕也位於快捷菜單中,以下圖所示:
他主要分爲「菜單欄」、「模擬手機客戶端界面」、「模擬手機按鍵」三部分。
菜單欄包含:設置、編輯、工具、性能測試和幫助功能
1、 「設置」部分主要進行三類設置
1) 模擬器模擬的終端平臺和型號等,好比:android的三星手機、iOS的iPad2平板等。
2) 界面縮放。有時候模擬pad的設備,在PC機中會顯示比較大,能夠經過縮放功能對顯示的界面進行等比縮放,方便開發查看和切換。
3) 橫豎屏切換。橫豎屏切換是開發中常遇到的問題,對模擬器進行相應的切換設置能夠及時看到效果。
2、「編輯」部分能夠對開發展現的界面進行截屏處理。
3、「工具」部分能夠啓動簡易抓包工具、JSON格式數據、打開當前顯示頁面的源碼、複製當前頁面地址以及打開PC模擬器的程序目錄。
4、「性能測試」部分能夠對整個應用的運行性能進行測試,讓開發人員瞭解運行的狀況以進行應用的優化。
5、「幫助」部分能夠查看模擬器的版本號等信息。
手機客戶端界面主要分四部分:演示中心、參數設置、進入基座和關於咱們。
1、演示中心:包含烽火星空公司的一系列開發模板、行業應用和成功案例,能夠方便開發者或者項目經理對開發進行參考以及項目前期的演示等使用。
2、參數設置:能夠設置應用訪問相關的一些信息,如:ExMobi服務端的IP、端口、頁面動畫效果的開關、緩存的設置等。MBuilder中的PC模擬器默認設置的IP和端口使用的是本機的環境。
3、進入基座:該界面與開發者息息相關,在MBuilder中建立的應用通過同步或者應用的安裝等操做後便可在該界面實時看到應用運行的效果,方便開發者開發調試。
4、關於咱們:在該界面中,主要能夠查看模擬器的版本號、IMSI、ESN、模擬的分辨率等信息。
手機按鍵主要有:菜單鍵、home鍵、返回鍵。下圖是模擬鍵和手機按鍵的對應關係:
第一個應用很容易就讓人想起「Hello World」,沒錯咱們如今就經過建立一個「Hello World」的應用來展現一個「Hello World」的頁面。
在MBuilder中點擊新建應用按鈕,以下圖所示:
點擊後就會進到建立應用的導航頁面。
在建立應用的導航頁面,填寫應用信息,咱們的應用名稱爲「helloworld」以下圖所示:
其中:
· Project name 項目名;(必填項)
· Application ID 應用ID,必須和項目名一致;(必填項)
· Application Name 應用名。(必填項)
· Application Version 版本號。(必填項)
· Scope 應用支持的表現形式,是客戶端方式的仍是wap方式的,仍是都支持;
· Access 該應用是否須要網絡(network)、gps定位(gps)、拍照(camera);
· HomePage 該應用訪問的第一個頁面,能夠是本地頁面,也能夠是網絡地址。Res開頭的爲本地頁面,http的地址是網絡頁面。這裏使用的是本地頁面。
點擊「next」按鈕進到「設置應用圖片」面板
在開發者信息設置面板能夠填寫一些開發者的基本信息,以下圖所示:
此步驟通常能夠直接跳過。
繼續點擊「next」按鈕,能夠進入皮膚模板選擇面板。
進入「皮膚模板選擇面板」後,能夠選擇使用某個模板的某個皮膚,以下圖所示:
本次建立不使用模板,後面的章節中咱們會單獨對模板的開發和使用進行介紹。
因此該面板咱們選擇結果以下:
不選擇模板則全部的模板選項均不可選擇。
點擊「finish」便可完成應用的建立。
應用建立完成後,打開PC模擬器基座客戶端,點擊「進入基座」,這時候會發現建立的應用並無在客戶端中顯示,以下圖所示:
能夠看到界面中左上角和右上角都有一個操做按鈕。
其中左上角的按鈕爲「應用管理」,點擊進去能夠看到存在的應用列表;右上角的按鈕爲「更多選項」,點擊進入能夠對基座的一些信息和參數進行設置和讀取。
點擊左上角的按鈕,能夠看到提示無應用,以下圖所示:
爲何咱們建立好了應用,可是在應用列表中卻看不到應用?
這是由於應用管理是客戶端向服務端拉取應用列表。而應用建立於MBuilder的工做空間中,並無同步部署到ExMobi服務端,因此這時候在客戶端是請求不到應用的。
這時候咱們就須要作一件事情,那就是「應用自動同步」。
在MBuilder中首先選中要同步的應用,這裏選擇「helloworld」應用跟目錄,而後點擊快捷菜單中的「自動同步」按鈕,以下圖所示:
將「不一樣步」狀態改爲「自動同步」狀態,以下圖所示:
退出基座界面到客戶端首頁,從新點擊「進入基座」,這時候就能夠看到新建立的應用已經同步到客戶端,以下圖所示:
若是繼續點擊左上角的「應用管理」按鈕,能夠對已經同步的應用進行卸載和安裝,以下圖所示:
而後點擊「啓動Tomcat」的圖標把Tomcat啓動好。
當點擊應用的「進入」按鈕的時候,會提示「連接文件不存在」,以下圖所示:
圖2-6-6-1
爲何會報這個錯誤,這裏先賣一個關子。咱們將經過下一節2.7的內容進行講解。
在上一節中,咱們看到圖2-6-6-1中的報錯,那是由於點擊「進入」按鈕,其實是請求應用的首個頁面,這個提示的意思就是說首個頁面的地址文件不存在。
這就須要先來了解應用的結構。以下圖所示是一個應用的基本結構:
應用根目錄下有一個文件(config.xml)和兩個文件夾(server、client)。
config.xml文件爲應用的基本信息配置文件,使用MBuilder默認生成的配置內容和說明以下:
這裏須要注意的是,homepage項即爲應用的入口節點,它支持本地文件(res:開頭)也支持網絡地址(http://開頭)。當點擊應用圖標,也就是圖2-6-6-1中點擊」進入」按鈕的時候會觸發該首頁地址。頁面上提示「連接文件不存在」意思就是說咱們設置的「res:page/index.xhtml」本地頁面不存在,由於咱們尚未建立該文件。
除此以外還有幾個特殊配置項:
配置項 |
功能 |
說明 |
access.land |
橫豎屏切換 (重力感應) |
取值爲boolean值,false(默認值)表明強制豎屏,true表明橫豎屏自動切換。 |
access.orientation |
默認顯示方向 |
爲應用默認顯示的方向,有兩個取值——port:設置應用爲豎屏模式;land:設置應用爲橫屏模式;padland_phoneport:設置PAD爲橫屏模式,phone爲豎屏模式;padport_phoneland:設置PAD爲豎屏模式;phone爲橫屏模式。 |
config.theme |
皮膚設置 |
取值爲對應皮膚包的id。 |
homepage.defaultsrc |
網絡鏈接錯誤的處理頁面 |
只支持本地靜態頁面,若是不設置,則跳轉到基座的設置頁面。 |
client文件夾下包含page、css、script、theme等子目錄,存放的都是客戶端資源。所謂客戶端資源就是運行在客戶端的文件。
因爲不一樣終端存在分辨率不一樣的問題,因此在client的子目錄下都區分了phone和pad目錄,再往下又分爲default(默認資源)、ldpi(低分辨率資源)、mdpi(中分辨率資源)、hdpi(高分辨率資源)、xhdpi(超高分辨率資源),以image目錄爲例,以下圖所示:
對於DPI的劃分,能夠參考附錄14.1。
這時候客戶端讀取的資源就是client/image/phone/hdpi目錄下的資源。到ExMobi的PC模擬器的apps目錄,找到應用image的目錄地址,好比:C:\developer\MBuilder\ExMobi\apps\helloworld\image,能夠看到以下圖片:
能夠看到,同步到客戶端的資源都沒有了分辨率的信息。
其實,當打開PC模擬器的時候,在模擬器標題欄能夠看到以下信息:
從右往左看,它代表的是當前模擬器模擬的是終端類型爲phone,DPI爲hdpi。
客戶端使用什麼資源就是根據客戶端的信息來讀取相應的資源的。
若是客戶端要使用logo.png這個圖片,中間的分辨率信息是不須要寫的,也就是說正確的引用方法爲res:image/logo.png便可引用logo.png圖片。
須要特別說明default目錄。該目錄是公共文件目錄,也就是說無論是哪一個DPI的客戶端均可以使用default目錄的資源。Default目錄的資源跟DPI下的資源同樣也會同步到image目錄下,不含有default目錄自己。因此若是default目錄和DPI目錄若有有相同名字的資源,那麼DPI下的資源會覆蓋default下的資源。
因此能夠總結客戶端資源使用的規則爲:客戶端使用的資源根據客戶端的類型(phone、pad)和DPI信息(ldpi、mdpi、hdpi、xhdpi)找到對應的dpi資源,同時也會去找default下的資源,若是DPI下有和default下同名的資源,那麼使用的將會是DPI目錄下的資源。
根據本節前面的提示信息「連接文件不存在」肯定是由於還沒有建立首頁地址「res:page/index.xhtml」,因此接下來咱們能夠在client/page/phone目錄下的default目錄或者hdpi目錄下建立index.xhtml文件。在default目錄點擊右鍵,以下圖所示:
便可打開建立新XHTML頁面的面板,以下圖所示:
修改文件名爲「index.xhtml」,而後點擊「finish」便可建立完畢頁面,並在MBuilder中打開默認頁面,以下圖所示:
再次點擊「進入」應用就不會報錯,而且顯示該頁面信息,以下圖所示:
在開發的過程當中,能夠使用這些能夠容許的客戶端資源來進行展現和腳本操做。
server文件夾用於存放在服務端執行的文件。其目錄結構以下:
其中,JSP文件夾用於存放JSP文件;xsl文件夾用於存放xsl文件;mapp.xml文件爲服務端的規則配置文件,是一個統一的全局配置。
mapp.xml配置文件其功能主要有:
1) 配置處理第三方系統的JSP文件。
2) 設置第三方系統的僞域名。
3) Push推送頻道的配置。可執行Push消息的推送。
4) 數據庫資源配置。能夠指定多個數據源。可在JSP中使用配置好的數據源。
5) 定時器配置(定時執行某個請求)。
6) 多媒體類型映射(爲特殊contenttype指定文檔格式,如doc、ppt等)。
7) Session類型設置(默認是有session限制,若是爲新浪網等新聞類系統能夠設置無session限制)。
8) 文檔預覽和下載緩存配置。能夠設置緩存的天數。
其經常使用配置以下表:
英文名稱 |
中文名稱 |
描述 |
用於標識符合MAXML規範的xml文件 |
||
用來代表路由節點信息元素, 其baseaddr屬性爲第三方系統的域名/ip地址,該域名能夠爲僞域名 |
||
_PAGE元素轉發元素 |
用來對每一個請求處理進行轉發元素,經過pattern屬性正則匹配第三方系統的地址,而後轉向path屬性設置的JSP進行處理。 |
|
配置元素 |
應用相關配置元素 |
|
應用僞域名元素 |
用於應用僞域名配置,address屬性指定第三方系統的真實域名/ip,name屬性指定僞域名。經過配置,整個應用無論是xhtml、jsp仍是mapp.xml的route配置中均可以使用僞域名。 |
|
<database/> |
數據源元素 |
數據庫的數據源配置,用於在JSP中做爲數據源直接調用。 |
推送元素 |
用來代表多組推送頻道元素,其authpage屬性 |
|
<pushchannel/> |
推送頻道元素 |
用來設置一個推送頻道的處理策略,必須包含一個id做爲頻道的惟一標識,path爲輪循推送必須的處理JSP,而且needsubscribed指明是否爲訂閱頻道,同時須要設置corn子標籤指明推送的週期間隔。 |
<services/> |
接口元素 |
用來代表多組接口服務元素。 |
<http-service/> |
http接口服務元素 |
提供第三方系統調用的http接口服務,必須配置pattern屬性指明訪問路徑,配置path路徑指明中間件對接收到的數據的處理JSP頁面。 |
一個mapp.xml最基本的內容通常爲:
<maxml version="2.0" xmlns="http://www.nj.fiberhome.com.cn/map" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.nj.fiberhome.com.cn/map maxml-2.0.xsd"> <route baseaddr="http://domain"> <!-- 登錄頁面get --> <forward pattern="/app/template/login.jsp" path="login.jsp"/> <!-- 登錄校驗post帶鍵值對 --> <forward pattern="/app/template/checkLogin.jsp" path="checkLogin.jsp"/> <!-- 新建任務get --> <forward pattern="/app/template/jsp/addTask.jsp" path="addTask.jsp"/> <!-- 保存任務post帶附件 --> <forward pattern="/template/action/taskManagerAction.jsp\?handler=save" path="taskManager.jsp"/> <!-- 列表展現post帶XML請求體 --> <forward pattern="/app/template/action/taskManagerAction.jsp\?handler=list&dataType=xml.*" path="taskManagerListXML.jsp"/> <!-- 列表展現post帶JSON請求體 --> <forward pattern="/app/template/action/taskManagerAction.jsp\?handler=list&dataType=json.*" path="taskManagerListJSON.jsp"/> </route> <config> <!-- 爲第三方系統的實際訪問地址配置一個簡寫域名domain,之後全部請求前面部分均可以使用domain代替,route的baseaddr也能夠寫爲domain --> <domain address="miap.cc:1001" name="domain"/> </config> </maxml> |
裏面最經常使用的就是route請求路由配置。實際上,客戶端的全部http請求並非當即觸發的,而僅僅是告訴服務端要觸發http請求,真實的請求是在服務端的JSP裏面觸發的。那麼客戶端的http請求如何知道是哪一個服務端的JSP來處理本身的請求?route配置其實就起到了橋樑的做用,經過route配置,能夠爲客戶端的請求配置處理的JSP文件進行邏輯處理。
在helloworld應用的index.xhtml中增長一個「ExMobi門戶網站」的超連接:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.nj.fiberhome.com.cn/exmobi.dtd"> <html> <head> <meta charset="UTF-8"/> <title>Hello World</title> <script> <![CDATA[
]]> </script> </head> <body> <a href="http://www.exmobi.cn/">ExMobi門戶網站</a> </body> </html> |
其效果以下:
當點擊該超連接的時候,並無實際的去請求「ExMobi門戶網站」,而是先在mapp的route裏面去找是否有該請求對應的處理JSP。由於尚未配置,頁面會報錯,以下圖所示:
該錯誤信息能夠在MBuilder的響應碼查詢中進行查看報錯緣由,以下圖所示:
途中提到的「須要配置添加相應的應用處理頁面」指的就是服務端的處理JSP。也就是說還沒在route中配置處理的JSP文件。
如今能夠在mapp.xml中配置route以下:
<?xml version="1.0" encoding="UTF-8" ?> <maxml version="2.0" xmlns="http://www.nj.fiberhome.com.cn/map" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.nj.fiberhome.com.cn/map maxml-2.0.xsd"> <config> <htmlformat wellformat="true" /> </config>
<route baseaddr="http://www.exmobi.cn"> <forward pattern="/" path="index.jsp"/> </route> </maxml> |
這就指明瞭「http://www.exmobi.cn/」這個請求處理的JSP名爲「index.jsp」。因此還須要在sever目錄的JSP子目錄中新建index.jsp,以下圖所示:
在彈出的新建面板中輸入文件名,以下圖所示:
便可建立默認的JSP文件,以下圖所示:
<%@ page language="java" import="java.util.*,com.fiberhome.bcs.appprocess.common.util.*" contentType="application/uixml+xml; charset=UTF-8" pageEncoding="UTF-8"%> <%@ include file="/client/adapt.jsp"%> <!DOCTYPE html SYSTEM "http://www.nj.fiberhome.com.cn/exmobi.dtd"> <html> <head> <meta charset="UTF-8"/> <title>Hello World</title> <script> <![CDATA[
]]> </script> </head> <body> This is the page content. </body> </html> |
從新點擊客戶端的「ExMobi門戶網站」連接就能夠看到一個新的頁面,以下圖所示:
特別的,若是爲第三方地址配置的JSP的文件名和第三方地址的文件名恰好同樣,這時候是能夠不用配置MAPP路由的,它會自動智能匹配處理的JSP文件。
好比客戶端發起以下幾種格式的URL請求,他們的特色是名稱都爲action:
http://domain/action.jsp
http://domain/action.jsp?type=1
http://domain/action.jsp?type=2
http://domain/action.do?type=1
http://domain/action.php?type=2
http://domain/action?type=3
若是在jsp目錄新建一個文件action.jsp,因爲其名稱也是action,在不給上述URL配置MAPP路由的狀況下,服務端會自動匹配該action.jsp進行處理。
因此對於basic@fiberhome應用裏的幾個配置項:
<!-- 登錄頁面get -->
<forward pattern="/app/template/login.jsp" path="login.jsp"/>
<!-- 登錄校驗post帶鍵值對 -->
<forward pattern="/app/template/checkLogin.jsp" path="checkLogin.jsp"/>
<!-- 新建任務get -->
<forward pattern="/app/template/jsp/addTask.jsp" path="addTask.jsp"/>
實際上是能夠不用配置的,由於第三方系統的URL和處理的JSP名稱一致。
因此對於一些名稱同樣,參數不同的第三方系統URL,最好配置的PATH不要以該URL的名稱命名MAPP路由的JSP文件,這樣可能致使邏輯混亂。
如下各項請逐個檢查:
n 檢查ExMobi集成開發環境MBuilder是否已經安裝好。
n 運行Tomcat看是否能正常啓動。
n 啓動客戶端PC模擬器,在「系統設置」中配置爲本機IP和Tomcat運行的端口(8001),並點擊「應用管理」菜單看是否能正常進入。
注:沒有安裝、配置或者出現運行異常的請參照《MBuilder安裝手冊》。
輔助開發工具主要是幫助開發人員在不寫代碼的狀況來分析第三方系統的交互狀況,並進行模擬和取值,方便在寫代碼前理順思路。
名稱 |
做用 |
下載方式 |
HTTPAnalyzerStdV2 |
抓包和網絡請求分析工具 |
|
Regex Util |
正則校驗工具 |
已經集成在MBuilder中 |
XMLSpy |
XPATH校驗工具,SOAP模擬請求工具 |
|
JMeter |
模擬http請求 |
|
二次開發手冊在MBuilder菜單中的「Help-》Help Contents」中,以下圖所示:
點擊後的幫助界面有一些二次開發的相關文檔,其中就包含二次開發手冊:
其中的「ExMobi二次開發手冊」爲客戶端和服務端的API手冊,開發者能夠方便的查找API來實現不一樣的業務場景功能。
使用ExMobi進行開發須要大體瞭解:HTML、JavaScript、CSS的基本語法;正則表達式的用法;XML的文檔結構以及XPATH的基本語法;TCP/IP網絡協議的原理和報文分析。
第2篇ExMobi編程基礎
XHTML是ExMobi客戶端的標記語言。與W3C規範的XHTML不一樣的是,ExMobi的XHTML是對標準HTML的集成和擴展——繼承適合在移動終端使用的控件,並擴展更多新的控件更方便移動應用的開發和移動終端的展現。
後續提到的XHTML均指代ExMobi的XHTML。
移動應用開發涉及多平臺開發,不一樣平臺開發語言不一樣,因此可以統1、簡單、快捷的進行開發是選擇使用XHTML的最大緣由。
由於使用XHTML有諸多好處:
1) 開發語言統一。XHTML只是一種描述性標記語言,不一樣平臺的ExMobi的客戶端均可以解析正確語法的XHTML。因此開發者只須要編寫一套XHTML代碼便可在不一樣平臺上運行。
2) 代碼結構簡單。XHTML代碼結構採用的是XML結構,井井有條。
3) 代碼簡潔,表達豐富。XHTML只須要給指定的標籤設置不一樣的屬性和樣式便可展示豐富的界面效果。
4) 可重用性好。XHTML因爲其XML特性,故能夠很方便的進行模板化以達到最大化的可重用性。
5) 可擴展性好。XHTML繼承自ExMobi強大的組件內核,能夠很方便的進行擴展出更多本來沒有的控件,根據不一樣的使用場景進行插件的定製開發。
6) 糾錯方便。XHTML代碼必須符合ExMobi的規範,此規範是驗證XHTML代碼正確性的標準,在XHTML代碼中只要聲明使用此規範便可進行快速糾錯。
XHTML遵循ExMobi的語法定義,並嚴格符合XML的語法規範。
其基本結構以下:
其中:
1) 第1-2行是文檔規範聲明,意思是文檔內容必須符合ExMobi語法規範。
2) 第3行表示XHTML內容的開始,第18行表示XHTML內容的結束。
3) 第4-12行是head頭信息聲明區域,能夠設置字符編碼、title標題信息、JS腳本和CSS樣式表等。
4) 第5行是聲明頁面編碼。
5) 第6行是設置title標題頭的信息。
6) 第7-11行是JS代碼塊,其中第9行爲JS中的代碼註釋寫法。
7) 第13-17行爲body區域,用於顯示頁面的主體內容,其中第14行是XHTML中的代碼註釋寫法。
除此以外,在XHTML中能夠添加不一樣功能的控件、JS和CSS,後續章節中會有詳細介紹。
XHTML是一種特殊的XML。因此,XML中的關鍵字須要被轉義。XML中已經定義的轉義字符包括:
實體 |
字符 |
含義 |
< |
< |
小於號 |
> |
> |
大於號 |
& |
& |
和 |
' |
’ |
單引號 |
" |
" |
雙引號 |
修改helloworld應用的index.xhtml頁面以下所示:
因爲該頁面中含有&沒有轉義爲&因此致使頁面不符合XML,因此打開此頁面會報錯,以下圖所示:
他會明確告知是第14行錯誤,將&字符轉義爲&後再看頁面效果:
CDATA塊是XML中的特殊區塊,CDATA中的內容不會被解析器解析,而是原樣輸出。因此通常JS的內容都是很不肯定的,而且JS語法常用引號、大於小於號等XML的關鍵字,因此JS內容一般包在CDATA中,這些內容就不須要進行轉義。
可是JS中有一個很經常使用的功能——innerHTML,改屬性是給控件設置內部HTML內容。在ExMobi中,做爲顯示的HTML片斷若是包含XML的關鍵字也是須要轉義的。
而JS中若是是給某個控件賦值,好比obj.value = str;這樣的賦值,若是value中包含XML的關鍵字是不須要轉義的。
因此,符合XML規範的XHTML應該具有以下特徵:
1) xml中使用
a) text內
' 與 ' 均表明 '
" 與 " 均表明 "
> 與 > 均表明 >
& 表明 &,不支持直接放置 &
< 表明 <,不支持直接放置 <
b) 屬性內
' 與 ' 均表明 '
> 與 > 均表明 >
" 表明 "
" 當屬性經過 ‘ ‘ 包裹時,可直接放置 「,當屬性經過 」 「包裹時,必須用"
& 表明 &,不支持直接放置 &
< 表明 <,不支持直接放置 <
2) js中使用 注:爲防止xml轉義引發歧義,js語句必須被<![CDATA[ ]]>包裹
a) 經過innerHTML構建控件,控件屬性或者text中
< 表明 <
> 表明 >
& 表明 &
' 表明 '
" 表明 "
如:
function change(){ var ctrl = document.getElementById("mydiv1"); ctrl.innerHTML = "<textarea id=\"mytextarea1\"><>'"&</textarea><div id=\"mydiv2\" href=\"<>'"&\"><>'"&</div>"; var ctrl2 = document.getElementById("mydiv2"); alert(ctrl2.href); } |
結果爲:<>/"&
b) 經過js設置控件屬性
< 表明 <
> 表明 >
& 表明 &
' 表明 '
" 表明 "
如:
function change(){ var ctrl = document.getElementById("mytextarea1"); ctrl.value = "<>'"&"; } |
設置後結果爲:<>'"&
\" 表明 "
\' 表明 ‘
& 表明 &
< 表明 <
> 表明 >
如:
function change(){ var ctrl = document.getElementById("mytextarea1"); ctrl.value = "\"\'&<>"; } |
設置後結果爲:"'&<>
XHTML是一種語法規範,符合XHTML規範的文檔稱爲XHTML文檔。
有兩種方式聲明XHTML文檔類型:
1) 靜態文件後綴名爲xhtml。該種方式僅針對靜態文件,就是建立一個本地的頁面,其後綴爲xhtml格式。Helloworld應用中的首頁地址index.xhtml就是一個xhtml文檔。
2) 動態文件聲明響應content type爲「application/uixml+xml」。動態文件是指JSP、PHP、.NET等語言開發的頁面,而content-type是http協議中規定文檔格式的頭信息。若是這些頁面是要在ExMobi客戶端中進行顯示,必須聲明響應的content- type爲「application/uixml+xml」。好比helloworld應用的index.jsp就聲明瞭content-type,以下所示:
<%@ page language="java" import="java.util.*,com.fiberhome.bcs.appprocess.common.util.*" contentType="application/uixml+xml; charset=UTF-8" pageEncoding="UTF-8"%> |
可是不管是哪一種方式,一旦聲明瞭文檔類型爲XHTML,內容則必須符合XHTML的規範,不然在ExMobi客戶端中將沒法進行正確解析和展現。
JS是JavaScript的簡稱,是一種客戶端腳本語言,ExMobi客戶端能夠執行XHTML頁面中的JS;CSS是層級樣式表,能夠渲染XHTML頁面的控件顯示效果。
JS有兩種使用方式,一種是直接在XHTML中的script代碼塊中編寫JS代碼;另外一種是把JS代碼寫在本地的JS文件中,經過script標籤引用本地的JS文件便可使用。以下所示:
注意:XHTML裏的JS沒法引用網絡側的JS文件。
好比下面的引用是錯誤的:
<script src="http://domain/script/index.js"></script>
|
CSS有三種使用方式,第一種是直接在XHTML中的style代碼塊中編寫CSS代碼;第二種是把CSS代碼直接寫在某個控件的style屬性中;第三種把CSS代碼寫在本地的CSS文件中,而後經過link控件引用本地的CSS文件。以下所示:
經過方法一和方法三使用CSS,只能經過類選擇器和派生選擇器進行引用。並且派生選擇器只支持一級。
上圖方法一的就是類選擇器的用法,即給控件設置class樣式名,而後在建立一個以該名爲引用的樣式。
派生選擇器的用法爲控件的名稱即爲樣式的引用,好比要給一個a控件寫樣式能夠這麼寫CSS:
a{ font-weight:bold; } |
JSP是一種動態語言,屬於服務端語言中的一種,並不直接運行在客戶端。因此JSP的運行是在依賴於運行容器的,好比Tomcat。容器運行後會獲得一個執行結果,執行結果能夠返回給客戶端進行操做。
因此ExMobi中的JSP是運行在ExMobi的服務端的,其響應的內容文檔能夠是XHTML,也能夠是XML、JSON等一些比較經常使用的數據格式,還能夠是doc、ppt、jpg等office文檔格式或者圖片格式。
一般,若是JSP響應結果直接做爲頁面展示,則須要輸出爲XHTML文檔,這時候須要聲明JSP的content-type爲「application/uixml+xml」;若是隻是做爲數據須要進一步的處理,好比頁面中發起的AJAX請求的JSP響應能夠輸出爲XML或者JSON等方便JS操做的格式,同時也須要把content-type設置爲相應數據格式的類型,好比JSON數據的頭信息爲「application/json」。
XHTML是由不一樣控件組合而成,這些控件主要分爲6大類:語義控件、頂級容器控件、佈局控件、導航控件、表單控件和多媒體控件。
所謂語義控件,就是指只有必定的含義,可是不會在客戶端中進行展示的控件。好比:html、script、link、meta、style、head等控件都是語義控件。
其中html控件爲XHTML文檔的根容器控件,一個XHTML文檔以html控件爲開始。
頂級容器控件是XHTML中的主容器控件,這些控件都只能位於html根容器之下,而且互相不能嵌套。
頂級容器控件包括:body(內容主體容器)、dialog(對話框容器)、fix(絕對定位容器)、footer(固定底部容器)、header(固定頂部容器)、leftcontainer(左側區域容器)、rightcontainer(右側區域容器)、title(標題區域容器)。
其中,dialog和fix容器是浮動在其餘容器之上的,不會佔用其餘容器的區域;而剩下的其餘6個容器的區域共同鋪滿整個XHTML的屏幕頁面,而且若是其中一個不使用或者不顯示,其所佔區域會讓渡給其餘的容器使用,其餘容器仍然鋪滿整個XHTML的屏幕頁面。
能夠經過以下代碼:
<html> <head> <meta charset="UTF-8"/> <title>title區域</title> </head> <header id="header" style="background-color:#aaaaaa"> header是固定在頭部的容器,不出現滾動條 </header>
<leftcontainer id="lefter" style="background-color:#bbbbbb;width:70;"> <div style="height:1001;"> leftcontainer是固定在左邊的容器,內容超出會顯示滾動條 </div> 下面沒有內容了 </leftcontainer>
<body style="background-color:#cccccc"> <div style="height:1001;"> body中的內容,超過會有滾動條。必須顯示,不能隱藏。 <a href="document.getElementById('header').style.display='none'">隱藏header</a> <br/> <a href="document.getElementById('footer').style.display='none'">隱藏footer</a> <br/> <a href="document.getElementById('lefter').style.display='none'">隱藏leftcontainer</a> <br/> <a href="document.getElementById('righter').style.display='none'">隱藏rightcontainer</a> <br/> <a href="document.getElementById('fixer').style.display='none'">隱藏fix</a> <br/> 能夠看到,除了header、footer、leftcontainer、rightcontainer之外剩下的區域都屬於body,而且不受fix的影響,因此,若是這些容器不寫,則body的空間就會更大。 </div> 下面沒有內容了 </body>
<fixset> <fix id="fixer" style="top:70%;left:0;background-color:#ffffff;"> fixset能夠包含多組fix容器,fix是絕對定位的容器,懸浮於窗口之上,因此能夠看到其內容能夠橫跨<br/> header、footer、body、leftcontainer、rightcontainer<br/> 能夠設置其left或者right之一以及top或者bottom之一來實現絕對定位 </fix> </fixset>
<rightcontainer id="righter" style="background-color:#dddddd;width:70;"> <div style="height:1001;"> rightcontainer是固定在右邊的容器,內容超出會顯示滾動條 </div> 下面沒有內容了 </rightcontainer>
<footer id="footer" style="background-color:#eeeeee;"> footer是固定在底部的容器,不出現滾動條 </footer> </html> |
其效果以下:
將header、footer、leftcontainer隱藏後的效果:
繼續隱藏rightcontainer和fixset後的效果:
能夠看到原來header、footer、leftcontainer、rightcontainer的區域都給了body,而fixset只是隱藏對body沒有影響。
title區域在頁面中不能改變顯隱狀態,默認是顯示的,若是須要隱藏能夠給title控件加屬性show="false"來隱藏。上面代碼設置後的顯示效果以下:
佈局控件提供不一樣粒度的控件來對頁面佈局提供強有力的保證。
獨立佈局控件爲最小粒度的佈局控件,它們只能對自身的佈局進行控制。如:a、artfont(藝術字)、br、font、hr等。
總體佈局控件自己是具備固定結構的一個總體,它能夠對內部的特定元素進行佈局。如:table、tree(樹控件)、htmlgroup(頁面組)等。
局部佈局控件能夠針對部分的其餘控件進行佈局調整,它不想總體佈局控件那樣有固定結構。好比:div、page(頁容器)、scroll、slidingcontainer、h(橫向佈局)、v(縱向佈局)等。
實際上,除了佈局控件外,其餘控件也均可以對自身進行佈局。只是佈局控件的做用是以佈局爲主。
導航控件顧名思義就是作引導用的,方便用戶可以知道如何進行操做、引導用戶操做等。
導航類控件主要經過簡單的標題和圖標指示用戶的操做。好比:list(動態列表)、listitem(單行、雙行列表)、titlebar(標題欄,可代替title控件)等。
菜單類控件一般是提供一組類似功能的菜單給用戶選擇使用。好比:grid(九宮格)、animationmenu(動畫菜單)、contextmenu(彈出菜單)、menubar(底部導航菜單)、tabbar(頂部導航菜單)等。
引導類控件經過一些簡單的動做引導用戶操做。好比:dragrefresh(拉拽刷新)、marquee(跑馬燈)、toggle(顯隱組)等。
表單控件主要用於表單頁面的展示以及數據的提交,能夠提交值的控件都有value屬性。
要做爲表單提交的兩個必要條件爲:
1) 要做爲表單提交的表單控件必須放置於form控件中;
2) form控件的enctype屬性必須爲: application/x-www-form-urlencode(普通提交方式)或者multipart/form-data(帶有附件的提交方式),不寫則默認爲application/x-www-form-urlencode。
部分控件在form表單中的提交值以下:
控件標籤 |
控件提交值 |
<eselect>可編輯選擇框 |
編輯框value值 |
<handsign>手寫簽名 |
手寫生成文件路徑(sys 開頭) |
<input:autocompletetext>記憶框 |
編輯框value值 |
<input:camera>拍照攝像 |
拍照生成文件路徑(sys: 開頭) |
<input:checkbox>複選框 |
相同name值value組成字符串,以&鏈接 如 奧迪&奔馳 |
<input:decode>動態解碼 |
編輯框內解碼結果 |
<input:file>文件選擇框 |
已選中文件路徑(sys: 開頭) |
<input:password>密碼框 |
編輯框內密碼值 |
<input:radio>單選框 |
單選框控件value值 |
<input:text>輸入框 |
編輯框內輸入值 |
<object:date>日期選擇框 |
已選中日期值 |
<object:time>時間選擇框 |
已選中時間值 |
<select>選擇框 |
已選中option項value值 |
<textarea>多行文本域 |
textarea輸入值 |
<handwriting>加強手寫 |
手寫完成後生成圖片文件的完整路徑(sys:開頭) |
<photoupload>加強型拍照控件 |
手寫完成後生成圖片文件的完整路徑(sys:開頭),多個文件以&鏈接 |
<input:record>錄音 |
錄音完成後生成的錄音文件路徑(sys:開頭) |
<switch>開關 |
value值 |
能夠看出來,部分表單控件具有了調用本地能力的功能,好比:input:camera能夠調用攝像頭、input:record能夠調用錄音功能等。
所謂多媒體控件是指使用了多種元素進行展示的控件。比較經常使用的多媒體控件有:
控件標籤 |
說明 |
<baidumap> |
百度地圖 |
<browser> |
瀏覽器控件 |
<fileset> |
附件集,進行附件的預覽、下載、打開、籤批等 |
<gaodemap> |
高德地圖 |
<img> |
圖片控件 |
<photoupload> |
加強型拍照控件 |
中間件客戶端支持對文字多樣性的展示,好比:文字的大小、顏色、樣式等,也能給文字添加超連接,使文字可以與其餘頁面進行交互。以下面的頁面效果:
其代碼實現以下:
<html> <head> <meta charset="UTF-8"/> <title>基本文字展示</title> </head> <body> <br/> <font>通常用font修飾字體,這是沒有通過修飾的字體</font> <br/> <font style="font-size:large;">將文字變大</font> <br/> <font style="font-size:small;">將文字變小</font> <br/> <font style="color:#ff0000;">給文字添加顏色</font> <br/> <font style="font-weight:bold;">將文字加粗</font> <br/> <font style="font-style:italic;">將文字顯示爲斜體</font> <br/> <font style="text-decoration:underline;">給文字添加下劃線</font> <br/> <font style="font-size:small;color:#00ff00;font-weight:bold;font-style:italic;text-decoration:underline;">綜合使用樣式的文字,能夠任意組合</font> <br/> <div style="font-size:large;color:#0000ff;font-style:italic;">不少控件能夠修飾文字,這是在div中修飾文字</div> <br/> <br/> <a href="res:page/interactive.xhtml">給文字添加超連接,不加樣式</a> <a href="res:page/layout.xhtml" style="color:#ff0000;text-decoration:none;">這是一個超連接,去掉了下劃線並設置了顏色</a> </body> </html> |
頁面跳轉能夠經過超連接來實現,可以經過超連接打開的頁面類型有兩種:
頁面類型 |
地址特徵 |
示例 |
應用中的本地文件 |
res:開頭 |
<a href="res:page/index.xhtml">打開一個本地頁面</a> |
網絡地址 |
http://開頭 |
<a href="http://www.exmobi.cn">合做夥伴門戶</a> |
超連接一般搭配target屬性使用,指定爲新打開一個頁面(_blank)仍是在本頁面中打開(_self)。
任何頁面類型均可以經過如下方式觸發:
觸發方式 |
示例 |
href |
<div href="http://www.nj.fiberhome.com.cn">烽火星空門戶網站</div> |
onclick |
<input type="button" value="請點擊" οnclick="res:page/index.xhtml"/> |
JS |
window.open("res:page/index.xhtml"); |
任何具備href、onclick的控件都能觸發超連接,並且超連接除了能夠打開頁面,也能夠進行其餘的交互,以下表:
交互類型 |
特徵 |
示例 |
應用資源 |
res:開頭 |
<img src="res:image/logo.png"/> <script src="res:script/exmobijs/base.js" /> <a href="res:page/index.xhtml">打開一個本地頁面</a> |
客戶端資源 |
sys:開頭 |
<a href="sys:data/sys/help.xhtml">客戶端幫助頁面</a> |
終端資源 |
file:開頭 |
<a href="file:SD/text.txt ">安卓系統文件</a> |
網絡資源 |
http://開頭 |
<a href="http://www.exmobi.cn">合做夥伴門戶</a> |
FTP資源 |
ftp://開頭 |
<a href="ftp://admin:111@192.168.100.231/home/gaea/ ">能夠指定FTP帳號密碼ip目錄等信息</a> |
JS函數 |
JS語法 |
<a href="window.open('res:index.xhtml')">用JS打開一個頁面</a> |
撥打電話 |
tel:開頭 |
<a href="tel:17584068201">撥打電話</a> |
發送短信 |
sendsms:開頭 |
<a href="sendsms:17584068201,13813900000:true:節日快樂">短信羣發</a> |
調用手機程序打開文件 |
open:開頭 |
<a href="open:file:SD/ppt培訓.pdf ">打開安卓文件 </a> <a href="open:http://192.168.100.131:8001/Test/file/mp4/cat.mp4 " target="_blank " >cat.mp4播放</a> |
調用瀏覽器 |
browser:開頭 |
<a href="browser:http://wap.sohu.com">進入WAP主頁</a> |
下載文件 |
download@http://開頭 |
<a href="download@http://192.168.1.doc/test/tywt.doc">下載天下無天.doc</a> <a href="download@http://192.168.1.doc/test/tywt.doc">下載天下無天.doc</a> |
內置腳本 |
script:開頭 |
<a href="script:close">返回上一頁</a> <a href="script:exit">退出客戶端</a> |
ExMobi採用的是流式佈局的原則對界面控件進行佈局。所謂流式佈局是指在容器(區域控件和佈局控件)中,從左到右、至上而下對該容器裏面的控件進行佈局,當一行不能容納的時候自動換行。
橫向佈局能夠設置容器控件(如:div、font等)的text-align屬性來指定子控件的佈局,也能夠使用控件自己的align屬性指定本控件在父容器中的佈局,他們的屬性值是同樣。好比:center爲居中、left停靠左側、right停靠右側顯示。
縱向佈局只能經過div的text-valign屬性來指定子控件的佈局,好比:top爲停靠容器頂部、middle垂直居中、bottom停靠容器底部顯示。也能夠經過br、hr對內容進行換行和分隔。
內外邊距能夠使容器和控件間產生一些間距。其中外邊距margin能夠使容器或者控件與其餘容器控件產生間距;內邊距padding能夠使容器和它內部的容器或者控件以及文本產生間距。也能夠給控件添加border邊框,並設置邊框的弧度。
須要注意的是,控件的寬度width和高度height已經包含了margin、padding和border的大小。而通常控件的margin、padding和border都會有默認值。因此實際顯示的控件大小應該是寬度或者高度減去margin、padding和border後剩下的大小。
好比下面代碼:
<html> <head> <meta charset="UTF-8"/> <title>基本佈局</title> </head> <body> <font style="color:#ff0000">橫向佈局</font><font>:給div設置text-align(對子容器有效)和給button設置align(對自身有效)</font> <div style="border-size:1dp;padding:4 4 4 4;text-align:center"> <input type="button" value="給div設置text-align:center"/> </div> <div style="border-size:1dp;padding:4 4 4 4; "> <input type="button" value="給button設置align:center" style=" align:center;"/> </div> <font>下面的兩個div在一行顯示,各佔50%,各div內部左邊的內容佔40%,右邊的輸入框佔60%,而且div設置了外邊距(margin),讓div之間有間隔:</font> <div style="width:50%;border-size:1dp;margin:0 0 0 2%"> <font style="width:30%;text-align:right;">姓名</font> <input type="text" style="width:70%"/> <font style="width:30%;text-align:right;">性別</font> <input type="text" style="width:70%"/> </div> <div style="width:50%;border-size:1dp;margin:0 0 0 2%"> <font style="width:30%;text-align:right;">班級</font> <input type="text" name="class" style="width:70%"/> <font style="width:30%;text-align:right;">年級</font> <input type="text" name="grade" style="width:70%"/> </div> <font style="color:#ff0000">縱向佈局</font><font>:div設置text-valign(對子容器有效):</font> <div style="border-size:1dp;height:100;text-valign:middle;"> <font>文字</font><input type="text" style="width:40%" /> </div> <font>下面的的div設置了上下內邊距(padding)都爲30,也能夠達到垂直居中的效果:</font> <div style="border-size:1dp;padding:30 0 30 0;"> 這裏的文字跟div有上下間距 </div> </body> </html> |
實現效果以下:
除了使用div佈局,還有其餘框架佈局控件:page(頁容器)、scroll(滾動容器)、 slidingcontainer(左右滑動容器)、table(表格容器)、tree(樹容器)。
佈局的基本原則是:
1) 框架佈局控件多用於構建一個頁面的框架結構;
2) 其餘佈局控件(如:div、font等)則是在進行微調的時候使用。
3) 應儘可能避免使用div來實現大量佈局和嵌套使用以提高界面顯示的效率。
4) 表單控件具備佈局能力。表單控件自己能佈局的不要使用佈局控件代替。
常見的導航通常有位於底部的菜單導航(menubar)、九宮格導航(grid)、列表導航(listitem)、分頁、位於頂部的tab導航(tabbar)。
經過下面代碼認識它們:
<html> <head> <meta charset="UTF-8"/> <title>導航頁面</title> <style> .tabbar{ height:40; border-size:1; padding:8 4 0 4; } .tab{ height:40; width:33%; border-size:1; border-radius:8; padding:4 4 4 4; margin:0 4 0 4; text-align:center; text-valign:middle; background-click-color:#dddddd; } .current{ background-color:#cccccc; } </style> </head> <header>
<tabbar showtype="text"> <tab text="tab1頁" selected="true" bindpage="page1"/> <tab text="tab2頁" bindpage="page2"/> <tab text="tab3頁" bindpage="page3" />
</tabbar>
</header> <body> <page id="page1"> <grid style="color:black;click-color:#ff8800;color-current:#0000ff;cell-background-click-color:#cccccc;cell-background-radius:4;"> <cell text="列表控件" href="res:page/listitem.xhtml" icon="res:image/icon_man.png" /> <cell text="拖動列表" href="res:page/list.xhtml" icon="res:image/icon_man.png" /> <cell text="功能模塊" icon="res:image/icon_man.png" /> <cell text="功能模塊" icon="res:image/icon_man.png" /> <cell text="功能模塊" icon="res:image/icon_man.png" /> <cell text="功能模塊" icon="res:image/icon_man.png" /> <cell text="功能模塊" icon="res:image/icon_man.png" /> <cell text="功能模塊" icon="res:image/icon_man.png" /> <cell text="功能模塊" icon="res:image/icon_man.png" /> <cell text="功能模塊" icon="res:image/icon_man.png" /> <cell text="功能模塊" icon="res:image/icon_man.png" /> <cell text="功能模塊" icon="res:image/icon_man.png" /> </grid> </page> <page id="page2"> 這是第二頁的內容 </page> <page id="page3"> 這是第三頁的內容 </page> </body> <footer> <menubar showtype="mix" style="menu-background-current-color:#000000;"> <menu text="待辦" bindpage="page1" icon="res:image/menubar-icon-daiban.png" currenticon="res:image/menubar-icon-daiban.png"/> <menu text="新建" bindpage="page2" icon="res:image/menubar-icon-xinjian.png" currenticon="res:image/menubar-icon-xinjian.png"/> <menu text="通信錄" bindpage="page3" icon="res:image/menubar-icon-tongxunlu.png" currenticon="res:image/menubar-icon-tongxunlu.png"/> <menu text="公告" icon="res:image/menubar-icon-tongzhigonggao.png" currenticon="res:image/menubar-icon-tongzhigonggao.png"/> <menu text="更多" icon="res:image/menubar-icon-gengduo.png" currenticon="res:image/menubar-icon-gengduo.png"> <submenu text="加班" icon="res:image/icon_jiaban.png" clickicon="res:image/icon_jiaban.png"/> <submenu text="請假" icon="res:image/icon_qingjia.png" clickicon="res:image/icon_qingjia.png"/> <submenu text="調休" icon="res:image/icon_tiaoxiu.png" clickicon="res:image/icon_tiaoxiu.png"/> </menu> </menubar> </footer> </html> |
效果以下:
其中tabbar和menubar能夠與page控件進行綁定,達到能夠經過左右滑動局部卻換內容。
列表導航(listitem)通常分爲單行(oneline)和雙行(twoline),代碼以下:
<html> <head> <meta charset="UTF-8"/> <title>列表展現</title> </head> <body> <!-- 單行列表 --> <listitem type="oneline" href="res:page/index.xhtml" icon="res:image/icon_user.png" caption="列表caption" ricon="res:image/icon_arrow.png" /> <listitem type="oneline" href="res:page/index.xhtml" icon="res:image/icon_user.png" caption="列表caption" ricon="res:image/icon_arrow.png" /> <!-- 雙行列表 --> <listitem type="twoline" href="res:page/index.xhtml" icon="res:image/icon_user.png" caption="列表caption" sndcaption="列表sndcaption" ricon="res:image/icon_arrow.png"/> <listitem type="twoline" href="res:page/index.xhtml" icon="res:image/icon_user.png" caption="列表caption" sndcaption="列表sndcaption" ricon="res:image/icon_arrow.png"/> </body> <footer style="background-color:#cccccc;"> <input type="button" value="<<"/> <input type="button" value="<"/> <font style="align:center;">1/2</font> <input type="button" value=">" style="align:right;"/> <input type="button" value=">>" style="align:right;"/> </footer> </html> |
效果以下:
上面的是比較固定佈局的list,若是須要進行簡單的自定義佈局,能夠使用list控件,同時支持上下拖拽進行分頁。須要注意的是list控件只支持經過注入JS數據來生成列表數據。代碼以下:
<html> <head> <meta charset="UTF-8"/> <title>拖動列表</title> <script> <![CDATA[
function doLoad(){ showList(); }
function showList(){
var listview = document.getElementById('listview'); var dp = listview.getDataProvider(); var item = []; item.push('{"name":{"innerHTML":"黃楠"},"number":{"innerHTML":"18901596118"},"listitem":{"href":"alert(1)"}}'); item.push('{"name":{"innerHTML":"高明珠"},"number":{"innerHTML":"13655190214"},"listitem":{"href":"alert(2)"}}'); dp.appendItems('['+item.join(',')+']'); dp.refreshData(); } ]]> </script> </head> <body onload="doLoad()"> <list id="listview" onscrolltop="showList()" onscrollbottom="showList()"> <div data-role="listtopdrag" style="text-align:center;height:30dp"> <font style="font-size:20dp;color:#333333">向下拖動</font> </div> <div data-role=" listtoprelease" style="text-align:center"> <font style="font-size:20dp;color:#333333">釋放即將刷新</font> </div> <div data-role="listtoprefresh" style="text-align:center" > <font style="font-size:20dp;color:#333333">正在刷新.....</font> </div>
<div data-role="listitem" style="background-click-color:#cccccc;"> <div style="width:10%;"> <img src="res:image/icon_user.png"/> </div> <div style="width:80%;"> <font id="name" style="color:#5f5f5f;font-size:1.5em">姓名</font> <br /> <font id="number" style="color:#5f5f5f;font-size:1.5em">電話號碼</font> </div> <div style="width:10%;"> <img src="res:image/icon_arrow.png"/> </div> <hr/> </div>
<div data-role="listbottomdrag" style="text-align:center;height:30dp"> <font style="font-size:20dp;color:#333333">向上拖動</font> </div> <div data-role=" listbottomrelease" style="text-align:center"> <font style="font-size:20dp;color:#333333">釋放即將刷新</font> </div> <div data-role="listbottomrefresh" style="text-align:center"> <font style="font-size:20dp;color:#333333">正在刷新.....</font> </div> </list> </body> </html> |
其效果以下,拖拽後釋放能夠繼續追加數據:
其餘經常使用的導航控件還有:
1) animationmenu動態菜單(必須置於fixset中);
收起狀態:
展開狀態:
2) contextmenu彈出菜單,必須經過內置腳本script:popmenu(id)才能打開菜單;
3) hdiv臨界事件導航,即拖動到頁面頂部或者底部的時候能夠引導觸發事件,好比:上下拖動加載上下頁內容。
ExMobi客戶端的表單控件除了提供標準html的表單控件,也提供了好比:拍照、攝像、日期、時間、可編輯選擇框、開關等控件,更適合在移動終端上使用。
在移動終端中因爲屏幕大小有限,不少時候須要對錶單內容進行分組,這時候能夠使用div,爲div設置邊框大小(border-size)、邊框顏色(border-color)和邊框弧度(border-radius)等相關樣式便可達到分組的效果,結合使用tabbar能夠進行更復雜的分組。
好比下面的代碼:
<html> <head> <title show="false"/> <style> .container{ border-size:1; border-color:#ababab; border-radius:4; background-color:#ffffff; margin:4 4 4 4; } </style> <script> <![CDATA[ function doSubmit(){ document.getElementById('form').submit(); } ]]> </script> </head> <header> <titlebar caption="返回" title="表單界面" rcaption="操做" riconhref="script:popmenu(oper)" iconhref="script:close"/> </header> <body> <contextmenu id="oper"> <option caption="保存" onclick="doSubmit()"/> <option caption="提交" onclick="doSubmit()"/> </contextmenu>
<form id="form" action="http://local/submit.jsp" method="post"> <eselect> <option selected="true">可編輯的select</option> <option>eselect控件</option> </eselect> <select> <option selected="true">select控件</option> <option>select控件</option> </select> <input type="text" prompt="文本提示"/> <div class="container"> <font style="width:25%;margin:0 0 0 8;">文本固定</font> <input type="text" prompt="只能輸入數字" validate="num" style="width:75%;border-size:0;margin:1 0 1 0;"/> </div> <input type="file" prompt="file"/> <input type="camera" prompt="拍照"/> <div class="container"> 日期段 <br/> <object type="date" prompt="開始日期" style="width:50%;"></object><object type="time" prompt="開始時間" style="width:50%;"></object> <object type="date" prompt="結束日期" style="width:50%;"></object><object type="time" prompt="結束時間" style="width:50%;"></object> </div> <div class="container"> <font>內容:</font> <br/> <textarea rows="3" type="text"></textarea> </div> <div class="container"> 開關 <switch ontext="開" offtext="關" id="switch_autologin" checked="true" style="align:right;on-background-color:#ff8800;" /> </div> <div class="container"> 單選 <br/> <input type="radio" name="radio" caption="選項1"/> <input type="radio" name="radio" caption="選項2"/> <input type="radio" name="radio" caption="選項3"/> </div> <div class="container"> 多選 <br/> <input type="checkbox" name="checkbox" caption="選項1"/> <input type="checkbox" name="checkbox" caption="選項2"/> <input type="checkbox" name="checkbox" caption="選項3"/> </div> </form> </body> </html> |
其效果以下,點擊操做能夠調用JS打開contextmenu菜單,注意這裏隱藏了title標題區域,取而代之的是在header中使用titlebar控件顯示標題,做用是給titlebar添加操做按鈕:
通常經常使用的屬性和使用場景以下:
屬性名 |
說明 |
使用場景 |
id |
控件的惟一標識 |
一般用於對該控件進行JS操做的時候使用。 |
name |
表單控件的名稱 |
當表單控件須要提交到服務端的時候須要給控件指定name,不然控件不會被提交。 |
href、onclick |
進行超連接操做 |
一般用於頁面跳轉、執行JS邏輯或者交互操做的時候使用。 |
style、class |
給控件設置樣式 |
須要給控件設置樣式的時候使用,style爲直接寫樣式,class是使用外部的樣式。 |
每個控件自己都有一些默認的樣式,一般狀況下是不須要修改的,因此瞭解一些經常使用樣式能夠靈活的對控件進行修飾。
樣式名 |
說明 |
使用場景 |
設置文字 |
||
font-size |
文字尺寸 |
通常須要對文字進行強調或者弱化的時候使用。 |
font-weight |
文字加粗 |
通常須要對文字進行強調的時候使用。 |
font-style |
文字傾斜 |
通常須要對文字進行強調的時候使用。 |
text-decoration |
文字下劃線 |
在A控件中設置是否下劃線達到界面顯示效果一致。 |
color |
文字顏色 |
通常須要對文字進行強調或者弱化的時候使用。 |
控件顯示 |
||
width |
控件寬度 |
設置控件寬度 |
height |
控件高度 |
設置控件高度 |
display |
控件顯隱 |
通常在表單中一般用於內容分組或者邏輯處理 |
背景(點擊)顏色 |
||
background-color |
背景顏色 |
設置背景顏色 |
background-click-color |
點擊時的背景色 |
給控件添加點擊效果,一般用在div或者導航控件中 |
background-image |
背景圖片 |
設置背景圖片 |
background-click-image |
點擊時的背景色 |
給控件添加點擊效果,一般用在div或者導航控件中 |
內容佈局 |
||
text-align |
子容器橫向佈局 |
通常在頂級容器或者div、font中使用,對內部的容器設置橫向對齊方式 |
text-valign |
子容器縱向佈局 |
通常在頂級容器或者div、font中使用,對內部的容器設置縱向對齊方式 |
align |
控件自身佈局 |
通常用在表單控件、font、div中使用,用於指定控件自己在父容器中的橫向對齊方式 |
能夠經過樣式設置點擊效果的經常使用控件主要有:
控件名稱 |
樣式 |
div |
background-click-image、background-click-color |
menubar |
menu-background-click-image、menu-background-current-image、menu-background-click-color、menu-background-current-color、menu-click-color、menu-current-color |
tabbar |
tab-background-click-color、tab-background-current-color、click-color、current-color、border-current-color |
grid |
cell-background-click-image、cell-background-current-image、cell-background-click-color、cell-background-current-color |
listitem |
background-click-color、background-click-image |
list |
background-click-image、background-click-color |
input:button |
background-click-color |
其中click表明點擊的效果,current表明選中的效果。
能夠經過給html標籤設置樣式實現頁面切換動畫效果:
樣式名 |
含義 |
取值 |
openanimation |
頁面打開的動畫效果 |
none:無;slideright:從左到右;slideleft:從右到左(默認);slidedown:從上到下; slideup:從下到上;zoom:從內向外;fade:漸顯 |
closeanimation |
頁面關閉的動畫效果 |
none:無;slideright:從左到右(默認);slideleft:從右到左;slidedown:從上到下;slideup:從下到上;zoom:從外向內;fade:漸隱 |
有時候須要在頁面間傳值,來實現客戶端數據的複用。好比:在a.xhtml頁面中的input輸入框的值須要傳遞到b.xhtml中使用,能夠有兩種方法:
n 在url中添加參數
能夠在a頁面打開b頁面的超連接中添加參數,如:res:b.xhtm?param=111,而後在b中能夠使用window.getParameter("param")獲得傳過來的值111。
n 使用客戶端的session
能夠在a頁面中設置客戶端的session,這個session只能供客戶端使用與服務端和第三方系統均無關,只要應用沒有關閉該session均會存在,好比:window.setStringSession("session","222"),那麼在打開B頁面的時候或者任意頁面的時候均可以使用window.getStringSession("session")來獲取到保存在客戶端的session值222,屢次設置,獲取到的是最後一次設置的值。
jsA.xhtml代碼以下:
<html> <head> <meta charset="UTF-8"/> <title>A頁面</title> <script> <![CDATA[ function openB(){ var param = "param="+document.getElementById("param").value; window.setStringSession("session", document.getElementById("session").value); window.open("res:page/jsB.xhtml?"+param); } ]]> </script> </head> <body> 傳參的輸入框: <input type="text" id="param"/> 放到session的輸入框: <input type="text" id="session"/> <input type="button" value="打開B頁面" onclick="openB()"/> </body> </html> |
jsB.xhtml代碼以下:
<html> <head> <meta charset="UTF-8"/> <title>B頁面</title> <script> <![CDATA[ function getA(){ document.getElementById("param").value = window.getParameter("param"); document.getElementById("session").value = window.getStringSession("session"); } ]]> </script> </head> <body onload="getA()"> 獲取參數的輸入框: <input type="text" id="param"/> 獲取session的輸入框: <input type="text" id="session"/> <input type="button" value="關閉" onclick="script:close"/> </body> </html> |
效果以下:
每個XHTML頁面都是一個JS的Window對象。每一個頁面的控件和函數都存在於Window中。單個頁面內部的控件和函數調用不須要聲明Window對象,好比頁面中存在某個id爲divCtr的控件,在當前頁面中能夠使用下面方法獲取到控件對象:
var divCtrObject = document.getElementById(「divCtr」); |
可是若是是兩個頁面之間的控件和函數的調用就必須聲明Window對象。
每一個XHTML頁面的html控件都有一個屬性id,做爲當前頁面Window的惟一標識,好比:
本地數據的存儲只要有3種方式:緩存存儲、SQLite存儲、本地文件存儲。
本地數據存儲是不隨客戶端的關閉而消亡的,因此只要緩存沒有刪除,在任什麼時候候使用都是能夠的。
緩存存儲是最經常使用的一種本地數據存儲方式,這種方式經過鍵值對的方式把數據存儲到移動終端本地。
其具體實現是經過類document.cache來實現,其用法以下:
設置緩存:
document.cache.setCache(key, value);//以鍵值對方式設置緩存,便可把緩存存到本地 |
讀取緩存:
var str = document.cache.getCache(key);//經過設置的key來獲取已經設置過的值 |
清除緩存:
document.cache. remove(key);//清除某個key對應的值 document.cache.clear();//清除全部緩存 |
常見的登錄用戶名、密碼的保存、臨時數據的存儲等均可以採用這種方式。
SQLite是一種輕型的關係數據庫管理系統,通常用於嵌入式的系統,在移動設備中被普遍應用,因此也是比較經常使用的一種數據存儲的方式。
SQLite跟大多數數據庫同樣,都須要鏈接、建立(數據庫、表)、對錶的增刪改查、數據庫關閉等一系列的操做。其基本步驟以下:
注:每一個應用都會自動建立一個名稱爲應用id的數據庫,因此通常狀況下都使用var db = Util.getDB(appId);獲取一個DB數據庫對象,而不用再建立自定義的數據庫。
經常使用SQL語句範例:
建立表createSQL: create table Keys(id INTEGER,name TEXT) 查詢表querySQL: select * from Keys 插入表內容insertSQL: insert into Keys (id,name) values (21,'test1') 更新表內容updateSQL: update Keys set name=’aaa’ where id=21 刪除表內容deleteSQL: delete from Keys where id=21 |
本地文件存儲涉及本地文件的操做,經常使用於通常文本內容的存儲。
讀寫某個文本文件的內容方法以下:
var filePath = "res:page/home.uixml"; var content = Util.readFile(filePath);//獲取指定路徑文件的內容 Util.writeFile(filePath,content);//將文本內容寫到本地文件中 |
與文件相關的操做還包括,文件(夾)的實例化:
var file = new File(filePath);//參數爲文件夾路徑則實例化一個文件對象 var folder = new File(folderPath, true);//參數爲文件夾路徑則實例化一個文件夾對象 |
經常使用的文件(夾)操做方法:
方法 |
描述 |
bool isFolder() |
判斷file是不是一個文件夾。返回true,表示文件夾;false表示文件。 |
bool exists() |
判斷文件或文件夾是否存在。返回true,存在;false,不存在。 |
bool deleteFile() |
刪除文件/文件夾。返回true,刪除文件成功;false,刪除文件失敗或文件不存在。 |
bool mkdir() |
建立此抽象路徑名指定的目錄。返回true,建立文件成功;false,建立文件失敗。語法:file.mkdir() |
bool createFile() |
當且僅當不存在具備此抽象路徑名指定名稱的文件時,建立一個新的空文件。返回true(1):建立文件成功;false(0):建立文件失敗。 |
bool renameTo(destPath) |
從新命名此抽象路徑名錶示的文件或文件夾。返回true:重命名文件或文件夾成功;false:重命名文件或文件夾失敗 |
bool copyTo(destPath) |
複製文件或文件夾到指定路徑。返回true:複製文件或文件夾成功;false:複製文件或文件夾失敗 |
String getFilePath() |
獲取文件路徑,絕對路徑。 |
String getFileName() |
獲取文件名 |
移動應用開發跟設備信息息息相關,因此經過獲取客戶端的信息進行一些邏輯處理是頗有必要的。
客戶端設備也就是客戶端所在的移動設備的信息,一般包括:操做系統信息、設備型號、設備的屏幕尺寸、設備的IMSI號、設備的ESN號、客戶端clientId、網絡信息等。
信息 |
方法 |
說明 |
獲取ESN |
String Util.getEsn() |
返回手機ESN號(iphone沒法得到,返回的是內部自定義字符串) |
獲取IMSI |
String Util.getImsi() |
返回手機IMSI號(iphone沒法得到,返回的是內部自定義字符串) |
操做系統信息 |
String Util.getOs() |
獲取操做系統平臺。PC模擬器根據配置手機類型返回,如設置爲Android則返回android,設置Iphone則返回ios。各個系統返回值以下:android、ios |
獲取手機型號 |
String Util.getPhoneModel() |
得到手機型號名,如(htc6950)。PC模擬器返回模擬器配置文件中的phonemodel節點屬性name值 |
獲取屏幕高度 |
int Util.getScreenHeight() |
獲取當前手機屏幕高度。返回值爲屏幕絕對像素值;若重力感應切換爲橫屏模式,則該值會變爲豎屏模式下的屏幕寬度; |
獲取屏幕寬度 |
int Util.getScreenWidth() |
獲取當前手機屏幕寬度。返回值爲屏幕絕對像素值;若重力感應切換爲橫屏模式,則該值會變爲豎屏模式下的屏幕高度; |
獲取客戶端clientId |
String Util.getClientId() |
獲取客戶端標識。返回值:返回客戶端標識,字符串 |
獲取設備當前網絡鏈接類型 |
int getConnectionType() |
獲取設備當前網絡鏈接類型。pc模擬器實現空方法,固定返回1 返回值: 0:設備無網絡鏈接 1:設備鏈接方式爲WIFI無線網絡 2:設備鏈接方式爲移動網絡 |
獲取網絡分配的IP地址 |
String getNetIp() |
獲取當前網絡分配ip地址。PC獲取爲空串 |
打開系統的APN設置界面 |
void openApnSetting() |
打開系統的APN設置界面 |
調用系統的網絡設置界面 |
bool startConnectSetting() |
返回true,表示調用成功;false,表示調用失敗。【注】該函數僅Android支持,pc模擬器及Iphone實現空方法便可 |
客戶端應用指的是在MBuilder中建立的項目。應用最終會部署到ExMob的服務端中,一個服務端能夠部署無限個應用,前提是服務器性能能夠支撐。
因此若是須要對應用進行管理就要獲取應用的信息。
獲取當前XHTML頁面所在應用的應用id的方法爲:String Util.getAppId();該方法返回的是應用id的字符串
獲取全部應用的信息方法爲:Array Util.getApplicationInfos();該方法返回的是當前ExMobi服務器下的全部應用的信息數組,該數組的每個元素都是應用信息對象ApplicationInfo。
ApplicationInfo對象包含的內容有:
屬性 |
描述 |
objName |
返回該對象所屬類名, 字符串全小寫 只讀。則返回applicationinfo |
status |
當前應用狀態。getApplicationInfos方法只獲取本地應用,狀態固定爲local local:純本地已安裝應用 setup:服務器存在已安裝應用,無須更新 update:服務器存在已安裝應用,且可更新 unsetup:服務器存在但未安裝應用 |
type |
返回應用適用類型,字符串。只讀 client:客戶端應用(默認);wap:wap應用;all:客戶端及wap應用均支持 |
appid |
返回應用id節點,應用惟一標識。只讀。默認爲空。在腳本里使用appid時,對iphone客戶端,appid須要使用全小寫 |
appname |
返回定義應用名稱 字符串。只讀。默認爲空 |
description |
返回應用描述 字符串。只讀。默認爲空 |
localVersion |
返回本地應用版本號 字符串。只讀。默認爲空 |
serverVersion |
返回服務器應用版本號,字符串。只讀。默認爲空 若服務器不存在該應用,返回爲空 |
date |
返回應用發佈日期 字符串。只讀。默認爲空 |
homepageSrc |
返回主頁指向地址 字符串。只讀。默認爲空 |
vendorUrl |
返回應用開發者主頁 字符串。只讀。默認爲空 |
vendorEmail |
返回應用開發者郵件地址 字符串。默認爲空。只讀 |
iconMain |
返回應用主界面大圖 路徑。只讀 |
iconLogo |
返回應用logo小圖 路徑。只讀 |
iconSelectedLogo |
返回應用選中logo小圖。只讀。建議爲png格式 |
size |
客戶端下載時須要下載的應用包大小,服務端根據客戶端屏幕尺寸來計算應用包下載大小後再動態加上。計算時遵循大於1M 就用MB ,精確到小數點後1位數,如1.2MB;小於1M 就用KB ,精確到小數點後1位數,如800.2KB。對於服務器上不存在的本地應用,size爲空。 |
若是要獲取當前應用的信息,能夠先獲取全部應用信息的數組,而後再根據數組元素ApplicationInfo對象的appid值與Util.getAppId()值進行比較,id值同樣ApplicationInfo對象信息就是當前應用的信息。以下:
function getCurrentAppInfo(){ //先獲取全部應用的信息 var list = Util.getApplicationInfos(); var currentAppInfo;//定義當前應用信息變量 for(var i = 0;i < list.length;i=i+1){ if(list[i].appid==Util.getAppId()) return list[i]; } return null; } |
調用上面的getCurrentAppInfo()函數便可得到當前應用的信息。
本地能力是指移動設備特有能力,這裏主要介紹:攝像頭、GPS/Location定位、地圖、發短信/發郵件/打電話、條形碼/二維碼掃描。
這裏的攝像頭主要指拍照和攝像。在4.4.5一節咱們已經瞭解到ExMobi中已經提供現成的控件支持:
<html> <head> <title></title> <script type="text/javascript"> <![CDATA[ //回調函數有默認參數能夠獲取文件的完整路徑 function getFilePath(path){ document.getElementById("divid").innerHTML = "文件名稱:"+path; } ]]> </script> </head> <body> <!-- mode屬性能夠指明調用的是拍照(still)仍是攝像(videoaudio),默認爲拍照 --> <input type="camera" mode=" videoaudio" savedcallback="getFilePath"/> <div id="divid"/> </body> </html> |
除此以外,經過JS對象CameraWindow也能夠喚起攝像頭進行拍照或者攝像:
var camerawindow = new CameraWindow();//建立一個攝像頭對象,一般是全局變量 function show(){//能夠在某個函數中來喚起攝像頭 camerawindow.mode=」still」;//設置攝像頭的模式是拍照(still)仍是攝像(videoaudio) camerawindow.onCallback=callback;//設置拍照結束後的回調函數 camerawindow.pwidth = 800;//設置照片的尺寸 camerawindow.startCamera();//喚起攝像頭 } function callback(){//攝像頭使用完畢會調用回調函數 if(camerawindow.isSuccess()){//判斷是否拍照或者攝像成功 //能夠從camerawindow對象獲取攝像的結果 //好比camerawindow.path爲獲取照片或者視頻的路徑 } } ]]> |
定位功能在社交類、外勤、快消等行業中有普遍的應用。ExMobi中也提供了相應的API。
定位按照空間分類,可分爲室內定位和室外定位;按照定位方式分類,可分爲基站定位(LBS)和全球衛星定位(GPS)。
LBS定位是經過電信運營商的基站信號差別來計算出手機所在的位置,因此有必定的偏差,而且須要有移動信號;GPS定位經過接收GPS衛星提供的經緯度座標信號來進行定位,因此須要移動設備有GPS模塊才能定位,而且室內不必定能搜星。
因此在開發定位功能的時候一般要考慮室內和室外因素,須要同時採用LBS定位和GPS定位來最終肯定定位的經緯度。
ExMobi中實現GPS定位的JS對象是Gps;實現LBS定位的JS對象是BaiduLocation。
它們的基本用法和步驟都是類似的,以下:
步驟 |
GPS |
LBS |
實例化 |
var position = new Gps(); |
var baiduLocation = new BaiduLocation(); |
初始化屬性 |
position.setTimeout(2000); |
baiduLocation.setTimeout(5000); |
設置回調函數 |
position.onCallback = callback; |
baiduLocation.onCallback = callback; |
執行定位 |
position.startPosition(); |
baiduLocation.startPosition(); |
回調函數處理定位結果 |
function callback(){ if(position.isSuccess()){/*返回定位是否成功*/ var latitude = position.latitude;/*得到緯度*/ var longitude = position.longitude;/*得到經度*/ } |
function callback(){ if(baiduLocation.isSuccess()){ var latitude = baiduLocation.latitude; var longitude= baiduLocation.longitude; } |
因爲GPS定位比較準確,因此通常先進行GPS定位,而後在GPS定位的回調函數中判斷是否認位成功,若是定位未成功繼續進行LBS定位,這樣就雙重保證定位結果了。
讓然,若是使用GPS定位,還須要判斷GPS是否已經打開,判斷的JS函數爲:
var gpsState = Util.getGpsState();//true表示已打開,false表示未打開 |
地圖的使用能夠在界面展示上更具體化,給人真實的體驗。ExMobi中集成了百度和高德的地圖SDK,將地圖封裝成控件,簡單設置屬性便可在ExMobi中進行地圖的展示。
同時,地圖有時還依賴於定位功能,由於地圖展現須要的條件是經緯度信息,而經緯度一般須要經過定位功能獲取。
因爲百度和高德地圖的SDK和用法相似,下面以百度地圖爲例說明地圖的使用方法。
百度地圖控件通常用法以下:
<baidumap id="baidumapid" key="076E0EB3C38AA7CAC23E5CE9BE5EC43C827FF95E" zoom="10" maptype="satellite"> <mark id="markid1" name="明基醫院" description="江蘇省南京市建鄴區河西大街2號" longitude="118.725487589807" latitude="31.9906975918588" /> <mark id="markid2" name="元通" description="江蘇省南京市建鄴區江東中路341號" longitude="118.716228604352" latitude="31.99743100150875" /> </baidumap> |
效果圖以下:
其主要的屬性爲:
元素名稱 |
描述及取值說明 |
baidumap |
初始化地圖 |
id |
標準屬性 規定元素的惟一id |
key |
百度地圖使用key。百度地圖使用時須要key的,考慮到安全及合法性影響,ExMobi不預置key,若使用該控件需用應用預製key,key是一個字符串,如: 076E0EB3C38AA7CAC23E5CE9BE5EC43C827FF95E字符串,申請地址http://dev.baidu.com/wiki/static/imap/key/ |
maptype |
地圖顯示方式,normal:普通地圖(默認); satellite:衛星地圖 |
center |
設置初始預製顯示在地圖中心的起始座標點。若該屬性不存在或值設置失敗則採用當前定位點爲中心點。格式爲:longitude(經度),latitude(緯度) 如center="116.232323,39.021521" |
zoom |
設置地圖縮放比例。初始地圖zoom值控件自動完成計算,保證當前點與至少一個標註點在同一屏幕內zoom="15"。取值範圍爲:3~18。3:地圖縮放比例最小;18:地圖縮放比例最大。取值數字 |
onload |
百度地圖加載完畢後執行腳本。地圖標註類js操做須要在此屬性設置函數中執行 |
mark |
設定標註點 |
id |
標準屬性 規定元素的惟一 id |
longitude |
標註點經度。如longitude="116.232323" |
latitude |
標註點緯度。如latitude="39.021521" |
name |
標註點名稱。如name="南京夫子廟總店" 1:若單個標註點則地圖標註時即彈出; 2:若多個標註點,顯示最近的1個標註點提示信息,點擊其餘標註點時切換彈出提示; 3:若name屬性爲空或未設置,則標註tip提示不彈出。 4:name屬性和city屬性須要同時使用才能顯示標註點 |
description |
標註點描述。如description="南京市白下區中山南路22號" 1:若單個標註點則地圖標註時即彈出; 2:若多個標註點,顯示最近的1個標註點提示信息,點擊其餘標註點時切換彈出提示 |
city |
標註點所在城市。根據mark標籤標註時,若存在經緯度定義,則按照經緯度進行標註,若不存在經緯度信息則採用name+city方式進行標註(單個標註)。name屬性和city屬性須要同時使用才能顯示標註點 |
注意:百度地圖的使用須要到百度開發者中心得到受權key方能使用。
發短信、發郵件、打電話都是移動終端經常使用的功能,在移動應用中經過JS能夠對用戶體驗有很大提高。
1) 發短信有兩種方式,一種是打開系統發短信界面發送,一種是直接後臺無感知發送。
//打開系統發短信界面,phone爲接收人手機號碼,content爲短信內容 Util.openSystemSms(phone,content); //後臺無感知發短信,phone爲接收人手機號碼,content爲短信內容 Util.sendSms(phone,content) |
2) 發郵件是經過MailObject對象實現調取系統的發郵件頁面,因此須要系統自己已經內置了郵件系統,其用法以下:
var mail=new MailObject(); //建立一個MAILOBJECT對象 mail.to="12@sina.com;34@123.com;4@ss.com";/*收件人地址列表,若是有多個,用";"分隔*/ mail.cc="chen@nj.fiberhome.com.cn;qiu@njavascript.com";/*抄送地址列表,若是有多個,用";"分隔*/ mail.bcc="66@43.com"; /*密送地址列表,若是有多個,用";"分隔*/ mail.subject="hello987"; /*郵件的標題會變成主題設置的文字,默認會提示"新郵件"*/ mail.body="hello,world,678.yuie623485892347"; /*郵件正文內容*/ mail.show(); /*調出系統郵件界面*/} |
3) 撥打電話能夠經過Util.tel(phone)函數來實現,其中phone爲手機號碼。
條形和二維碼掃描是當前比較流行的一種社交形式,由於條形碼和二維碼自己能夠存儲必定的信息,經過移動終端掃描便可輕鬆獲取。ExMobi中在控件和JS對象中都對其進行了封裝統一稱爲「動態解碼」。
動態解碼控件的通常寫法爲:
<input type="decode" id="decodeid" name="decodeName"/> |
Input控件設置type爲decode即爲動態解碼控件。以下圖所示:
點擊右側圖標能夠啓動攝像頭掃描解碼,解碼後的內容會顯示在輸入框中。
經過JS也能夠調用Decode對象使用動態解碼功能:
var decode = new Decode();//實例化一個Decode對象 function startdecode(){ decode.onCallback = callback;/*設置解碼結束回調函數*/ decode.startDecode();/*開始解碼*/ } function callback(){ var text='objName:'+decode.objName;/*objName*/ if(!decode.isSuccess()){/*返回解碼是否成功*/ vr.innerHTML = text+'解碼失敗'; return; } text = text+"解碼結果:"+ decode.result+"解碼時間:"+decode.time; } |
基座提供了獲取應用列表、應用下載、IP和端口設置、註冊信息提交、文件下載管理等功能,以下圖所示:
而通常應用開發完畢,須要把開發好的應用打包成一個客戶端(IPA、APK等格式),這時候打開客戶端是看不到基座的,若是須要使用基座的能力,就須要使用一些基座對象來進行操做。
ExMobi提供5個基本的基座對象來調用基座能力:
1) ApplicationInfo對象:ExMobi中的應用信息即爲一個ApplicationInfo對象,好比應用的版本號,appid等,ApplicationInfo對象經過Util工具類函數獲取Util.getApplicationInfos()。
2) AppManager對象:用於實現ExMobi應用相關信息獲取和操做,好比:應用下載、應用卸載等。
3) DownloadInfo對象:經過ExMobi客戶端下載的文件就是一個DownloadInfo對象,能夠經過Util. getDownloadInfos(tag)獲取已下載、下載中和所有文件的數組,每一個數組對象就是一個Down。
客戶端中能夠使用JS進行簡單邏輯處理,如:表單校驗、動態頁面、應用信息調用、終端能力調用、本地數據庫操做等場景。
JS都是經過一些事件來觸發才能開始執行,好比單擊、長按、加載完成等事件,目前客戶端支持的事件主要有:
屬性名 |
事件含義 |
支持的控件 |
onload |
頁面加載完畢後執行的事件,一個頁面只執行一次 |
body |
onstart |
當頁面處於激活狀態時執行的事件,頁面屢次被激活則會執行相應次數 |
body |
onstop |
當頁面轉變爲不可見的時候執行的時間 |
body |
ondestroy |
當頁面被銷燬的時候執行的時間,一個頁面只會執行一次 |
body |
onl2rscroll
|
手勢從左滑到右的時候觸發的事件 |
body/footer/header |
onr2lscroll |
手勢從右滑到左的時候觸發的事件 |
body/footer/header |
onresize |
屏幕橫豎屏切換的時候觸發的事件 |
body |
menubind |
點擊終端的菜單按鍵的時候觸發的事件 |
body |
backbind |
點擊終端的返回按鍵的時候觸發的事件 |
body |
href |
點擊控件的時候觸發的事件 |
a/img/menubar(menu)/menubar(submenu)/div/grid(cell)/h/item/jiugong(cell)/listitem-oneline/listitem-twoline/tree(item)/v/fileset(item)/base/marquee/ |
onclick |
點擊控件的時候觸發的事件 |
eselect(option)/checkbox/radio/select(option)/switch/contextmenu(option)/button/menu/menu(option)/ |
onlongclick |
長按控件的時候觸發的事件 |
img/button/div/item/listitem-oneline/listitem-twoline/ |
onscroll |
當頁面進行上下滑動的時候觸發的事件 |
hdiv |
onscrollbottom |
當容器滑動到底部並向下拖拽的時候觸發的事件 |
list |
onscrolltop |
當容器滑動到頂部並向上拖拽的時候觸發的事件 |
list |
onchange |
當控件輸入值改變時調用的觸發事件 |
autocompletetext/password/radio/text/object:date/object:time/select/textarea/ |
ontextchanged |
自動完成編輯文本框激活狀態下,輸入值改變時觸發腳本事件 |
autocompletetext/text |
liconhref |
點擊左側圖片連接的觸發事件 |
password/text |
riconhref |
點擊右側圖片的連接的觸發事件 |
password/text/item/listitem-oneline/listitem-twoline/titlebar |
iconhref |
點擊左側圖片連接地址觸發事件 |
item/listitem-oneline/listitem-twoline/titlebar/ |
collapsehref |
點擊+或者-時觸發的連接事件 |
tree(item)/ |
checkboxhref |
點擊checkbox圖標觸發的連接事件 |
tree(item) |
在表單校驗中,一般使用JS的DOM模型獲取到控件的值後對其進行準確性校驗。
示例:要驗證一個登錄信息是否填寫完整,咱們能夠在點擊登錄按鈕的時候觸發點擊事件,經過document.getElementById(objId).value來獲取控件的值,並判斷是否爲空。
代碼示例以下:
<html> <head> <title>表單驗證</title> <script> function doSubmit(){ if(document.getElementById("username").value==""||document.getElementById("password").value==""){ alert("用戶名或者密碼不能爲空!"); return; } document.getElementById("form").submit(); } </script> </head> <body> <form id="form" action="http://local/login.jsp" method="post"> <font style="width:30%">用戶名</font> <input type="text" style="width:70%" id="username" name="username"/> <font style="width:30%">密碼</font> <input type="password" style="width:70%" id="password" name="password"/> <br size="10"/> <input type="button" style="width:70%;align:center" value="登入" onclick="doSubmit()"/> </form> </body> </html> |
效果以下:
動態頁面中最多見到的是某個控件的顯隱(如:document.getElementById(objId).style.display = "none")、某一個容器裏的內容改變(document.getElementById(objId).innerHTML="<a href='http://www.baidu.com'>百度</a>")或者某一個控件值改變(document.getElementById(objId).value="取消")。
示例:切換兩個tab的顯隱展現,並動態修改tab的內容。[使用批處理]
代碼示例以下:
<html> <head> <meta charset="UTF-8"/> <title>導航頁面</title> <style> .tabbar{ height:40; border-size:1; padding:8 4 0 4; } .tab{ height:40; width:50%; border-size:1; border-radius:8; padding:4 4 4 4; margin:0 4 0 4; text-align:center; text-valign:middle; background-click-color:#dddddd; } .current{ background-color:#cccccc; } </style> <script> <![CDATA[ function showHide(s,h){ //顯隱批處理能夠提升界面顯示效率 beignPreferenceChange(); //修改tab的選中狀態 document.getElementById(s+"_tab").className = "tab current"; document.getElementById(h+"_tab").className = "tab"; //顯隱div document.getElementById(s).style.display = "block"; document.getElementById(h).style.display = "none"; //修改底部的顯示信息 document.getElementById("msg").innerHTML = "如今顯示的是"+document.getElementById(s+"_tab").innerHTML; //批處理最後結束,不然全部顯隱都將不生效 endPreferenceChange(); } ]]>
</script> </head> <header> <div class="tabbar"> <div href="showHide('left','right')" id="left_tab" class="tab current" target="_self">交通違法信息</div> <div href="showHide('right','left')" id="right_tab" class="tab">電子監控記錄</div> </div> </header> <body> <!-- 交通違法信息查詢 表單 --> <div id="left" style="display:block;"> <div> <form name="form1" method="post" id="form1" action=""> <!-- 參數jszh --> <div> <font>駕駛證號:</font> <input name="jszh" id="jszh" type="text" prompt="請輸入駕駛證號" promptcolor="#c1c2c2" value="421239206413" /> </div>
<!-- 參數dabh --> <div> <font>檔案編號:</font> <input name="dabh" id="dabh" type="text" prompt="請輸入檔案編號" promptcolor="#c1c2c2" value="123123123" /> </div>
</form> </div> </div>
<!-- 電子監控記錄查詢 表單 --> <div id="right" style="display:none"> <div> <form accept-charset="UTF-8" name="form2" method="post" id="form2" action=""> <div> <font>號牌種類:</font> <!-- select列表 --> <select name="driverType" > <option value="1">小客車</option> </select> </div>
<div> <font>號牌號碼:</font> <input name="cphm" id="cphm" type="text" prompt="請輸入號牌號碼" promptcolor="#c1c2c2" value="蘇A123456" /> </div> </form> </div> </div> </body> <footer style="background-color:#cccccc;"> <div id="msg" style="text-align:center;">如今顯示的是交通違法信息</div> </footer> </html> |
界面效果和JS的對應關係以下:
移動應用開發常常會涉及重力感應(橫豎屏切換)、響應碼攔截、應用主題使用等問題。
客戶端的通用配置均在應用根目錄下的config.xml文件中。
重力感應有兩方面的配置,一方面是配置應用是否支持重力感應;另外一方面是配置非重力感應時候的顯示方向。
默認應用是不支持重力感應的,默認顯示方向是豎屏(正方向)。
重力感應的配置在access節點,其中land屬性設置是否支持重力感應,orientation屬性設置非重力感應時候的顯示方向。
land屬性有2個值:true和false。值爲true則指明應用支持重力感應,能夠橫豎屏切換;值爲false則指明應用不支持重力感應,沒法進行橫豎屏切換。
orientation屬性有4個值:port、land、padland_phoneport和padport_phoneland。若是phone和pad的顯示方向同樣,能夠使用port(均爲豎屏)和land(均爲橫屏);若是phone和pad的顯示方式不一樣,能夠使用padland_phoneport(pad橫屏、phone豎屏)和padport_phoneland(pad豎屏、phone橫屏)。
假設應用須要支持重力感應,就只須要設置land屬性爲true,而orientation屬性則不須要設置(設置爲默認值port也能夠)。好比:
<access orientation="port" land="true"/>
|
假設應用不須要支持重力感應,而且pad要橫屏顯示,phone須要豎屏顯示,就須要設置land屬性爲false,orientation屬性設置爲padland_phoneport。好比:
<access orientation="padland_phoneport" land="false"/>
|
ExMobi在交互過程當中會對一些異常進行分類並響應給客戶端,若是不進行相應的處理,客戶端就是把響應碼信息提示到頁面中。有時候這種提示不是很友好,而且對終端用戶有指導性的意義,用戶即便看到響應碼也不知道如何操做。因爲響應碼在大多數狀況下是作異常提示的,因此響應碼又叫作響應碼。
這時候就須要用到響應碼攔截配置,該配置能夠爲不一樣的響應碼配置一個本地XHTML頁面進行展現,以代替客戶端的生澀提示。
響應碼攔截配置須要三步:
第一步:在confix.xml的根節點中增長一個響應碼的配置faultconfig:
<faultconfig src="res:page/faultconfig.xml" /> |
其中src屬性指向一個本地的響應碼配置文件。
第二步:在相應位置建立faultconfig節點指向的配置文件「faultconfig.xml」。
第三步:編輯「faultconfig.xml」文件,增長對應響應碼的攔截:
<?xml version="1.0" encoding="UTF-8"?> <faultconfig> <!-- ExMobi服務器返回響應碼 --> <fault> <code>響應碼</code><!—要攔截的響應碼--> <description>描述信息</description> <!—指定要跳轉的本地頁面,如res:page/fault2008.xhtml --> <nextaction>本地頁面</nextaction> </fault> </faultconfig> |
每個響應碼配置須要放在fault節點中,fault節點下包含3個子節點描述一個響應碼的信息:code、description和nextaction。其中,code節點內容爲具體的響應碼(一般爲數字);description是對響應碼的描述,以方便開發人員可以快速分辨響應碼的做用;nextaction是當服務端響應該響應碼給客戶端的時候客戶端的處理頁面,也就是說當對應的響應碼出現的時候,就會去執行nextaction指定的地址打開頁面進行相應的處理,而不是彈出默認的響應碼信息。
好比,咱們常見的一個錯誤:某個url沒有配置mapp路由,就會報出響應碼。以下所示:
這個響應碼的含義咱們能夠在MBuilder的Error Code工具中查看響應碼出現的緣由:
這裏提示的是缺乏相應的應用處理頁面。若是頁面是須要適配的,可是沒有適配,能夠爲請求的url配置mapp解決。可是有的時候咱們不可能考慮到全部頁面的適配,爲了可以有更好的用戶體驗,咱們能夠經過攔截該響應碼,當某些頁面沒配置mapp就跳轉到另外一個頁面進行友好提示。
應用主題是對應用總體風格的一個定義,使用了應用主題的應用,在默認的顯示狀態都採用主題的風格進行展現,能夠大大的減輕開發者的工做量。
應用的主題是設置在config.xml的根節點config上的,其屬性爲theme,其值爲主題文件的id。
主題文件應該置於client目錄下的theme子目錄,以下圖所示:
其中cdf.xml文件即爲主題文件,該主題文件中有一個id節點:
Config.xml配置的theme屬性即爲該id值,以下所示:
如何進行主題的開發咱們會在獨立章節進行講解。
ExMobi服務端做爲客戶端與數據源(WEB頁面、數據庫、Web Service、標準接口等第三方系統)鏈接的橋樑,它主要實現與第三方系統的模擬,而且將第三方系統的響應數據進行揀選後重組爲客戶端識別的內容(XHMTL、XML、JSON、標準文檔格式等)下行給客戶端進行展現。
MAPP規則是服務端進行數據處理的基本規則。在第2.6.6章節中已經介紹過MAPP的經常使用功能。
其中最經常使用也是最重要的一個規則就是route路由規則,因此這裏再贅述一下。
客戶端與服務端的交互都是經過URL找到對應關係的,這個URL能夠是一個真實的URL也能夠是一個虛擬的URL。因此這個URL實際起到橋樑做用,並不會在客戶端觸發的時候當即請求該URL。而是,服務端根據MAPP的route路由配置去server目錄找該URL對應的處理JSP文件,具體要不要往這個URL實際去發起請求由JSP決定。
因此,對於數據庫這種沒有URL的數據源,一般都是使用虛擬的URL,在對應處理的JSP中進行SQL執行和結果返回;而對於頁面抓取或者Web Service等有URL的數據源,一般能夠直接使用該URL來做爲路由配置指定相應的處理JSP。
route路由規則的forward節點爲每個符合要求的URL指定處理的JSP。其中,pattern屬性指明URL的匹配的規則,該規則是一個正則表達式;path是處理的JSP文件名。也就是說,匹配pattern屬性指定正則規則的URL會進入path屬性指定的JSP文件進行處理。
服務端的處理邏輯爲:服務端接收到客戶端上行過來的請求中包含了第三方系統的URL、頭信息、請求體等內容,服務端經過URL信息在服務端的MAPP路由配置中找到該URL對應的處理JSP,而後在JSP中向第三方系統發起請求並獲取到響應,而後繼續在JSP中對該響應經過揀選和重組後下行給客戶端進行展現。
客戶端和服務端間的關係以下圖:
ExMobi處理步驟爲:
第一步:客戶端上行請求到服務端,其中攜帶要請求的第三方系統的URL、頭信息、請求體等數據。
第二步:服務端獲取到客戶端的數據後,提取出其中的URL信息,根據MAPP路由智能匹配該URL對應的處理JSP。
第三步:在JSP中模擬第三方系統的URL並提交頭信息和請求體等數據。
第四步:第三方系統接受到URL等信息後給JSP一個響應數據。
第五步:JSP將接收到的第三方系統的響應後進行揀選,並處理爲客戶端能夠識別的格式而後下行給客戶端進行展現。
注:MAPP路由爲第三方系統URL與服務端對應處理的JSP的映射關係。經過MAPP路由能夠爲每個第三方系統URL配置一個處理的JSP文件。
因此JSP在ExMobi中起到的是一個承上啓下的做用。即:處理客戶端上行過來的請求信息(經過抽取標籤實現)和將請求回來的響應下行給客戶端(經過揀選模式實現)。
JSP揀選模式的特色是能夠經過多種抽取標籤(<aa:http>、<aa:sql-excute>、<aa:datasource>)獲取第三方系統的數據做爲數據源,而後經過取值標籤、推送標籤、邏輯標籤及一些擴展標籤從該數據源中揀選想要的數據進行展現。因此,一個數據源必然對應一個抽取標籤,數據源做爲抽取標籤的元數據,以供其餘標籤和函數從中揀選須要的數據。
全部抽取標籤的根標籤都有一個屬性就是id,全部取值標籤和邏輯標籤都有一個屬性dsId,其值就是要獲取數據的抽取標籤的id。非抽取標籤向抽取標籤取值就須要指定dsId,以造成一個對應關係。
好比:抽取標籤<aa:http id="login"/>表明的是一個網絡請求,請求結果就是一個數據源,經過該抽取標籤獲取到數據後,能夠在取值標籤<aa:value-of dsId="login" xpath="..."/>中經過設置dsId屬性的值爲login來指定xpath獲取的值來自於id爲login的抽取標籤(數據源)。
敏捷適配JSP標籤庫是一套封裝了大量數據集成業務邏輯的標籤庫,經過該標籤庫能夠方便對第三方數據進行集成,主要包括:數據的抽取、數據的獲取、數據的格式化以及數據的輸出。它主要包括:抽取標籤、取值標籤、推送標籤、邏輯標籤和擴展標籤。
本節主要介紹抽取標籤、取值標籤、邏輯標籤和擴展標籤。推送標籤將有獨立模塊進行講解。
抽取標籤用於獲取第三方系統的數據,包括http請求的響應、數據庫請求、自定義數據源等。經常使用的抽取標籤有:
標籤名 |
用途 |
<aa:http> |
經過http請求抽取第三方系統的數據,能夠設置url、method、enctype、reqcharset、rspcharset、id等屬性。 |
<aa:header> |
爲<aa:http>的子標籤,能夠設置請求頭信息。 |
爲<aa:http>的子標籤,能夠設置請求體爲鍵值對的參數。 |
|
爲<aa:http>的子標籤,在http協議請求時設置請求cookie。 |
|
爲<aa:http>的子標籤,能夠設置請求體爲非鍵值對的參數。 |
|
<aa:datasource> |
將某一個String類型的JAVA對象轉換爲數據源,具備id屬性能夠供其餘標籤從中獲取數據。其餘標籤經過設置dsId爲該id值與該數據源關聯。 |
<aa:sql-excute> |
執行一個SQL語句並獲取響應,須要與路由配置中的數據源綁定。 |
須要注意的是,具備id屬性的抽取標籤,在實際請求的時候能夠在臨時文件目錄裏生成一個文件名爲id的文件,因此對於不一樣請求,設置的id儘可能不一樣,以方便分析臨時文件的時候能迅速定位到想要的文件。
取值標籤主要用於從抽取抽取標籤請求到的數據源揀選想要的數據。經常使用的取值標籤有:
標籤名 |
用途 |
<aa:copy-of> |
支持經過xpath或者regex從dsId(數據源)裏將符合要求的數據原樣輸出。 |
<aa:value-of> |
支持經過xpath或者regex從dsId(數據源)裏將符合要求的數據的文本值取到。 |
注意:經過<aa:value-of>取到的內容若是包含XML中的特殊字符如<、>、"、'、&等會被轉義成<、>、"、'、&的等。
邏輯標籤用於進行一些邏輯判斷,從而判斷後面的處理邏輯。經常使用的邏輯標籤有:
標籤名 |
用途 |
<aa:if> |
支持經過xpath、regex或者JAVA表達式結果判斷一個邏輯是否符合要求,並進行相應操做。 |
<aa:choose> <aa:when> <aa:otherwise> |
<aa:choose>下的<aa:when>和<aa:otherwise>經過xpath、regex或者JAVA表達式結果進行邏輯處理。 |
<aa:for-each> |
經過xpath或者regex從dsId(數據源)中獲取符合要求的數據,並進行循環處理。 |
擴展標籤訂義了一些特殊數據的處理的標籤。好比:文檔下載標籤、文檔預覽把標籤、xsl轉換標籤等。
標籤 |
描述 |
<aa:file-download> |
文件下載標籤,該標籤實現將響應的內容以附件下載方式響應給客戶端 |
<aa:file-preview> |
文件預覽標籤。該標籤實現將響應數據進行預覽處理並將預覽後的結果響應給終端 |
<aa:file-signature> |
文檔籤批標籤。該標籤用於處理在籤批模式預覽文檔後,用戶對展示在客戶端上的圖片籤批並點擊提交所觸發的請求,該標籤對用戶繪製的籤批圖片作籤批處理 |
<aa:addwatermark> |
增長一個在指定圖片上添加水印效果 |
<aa:xsl> |
數據源標籤,支持xsl渲染數據並將渲染結果做爲數據源或直接輸出到終端設備 |
須要特別說明的是aa:xsl標籤,它既是數據源標籤也是取值標籤,也就是說它自己的數據能夠被其餘標籤獲取,也能夠直接做爲數據輸出,因此aa:xsl既有id也有dsId屬性。
1) 全部取值標籤和邏輯標籤均可以經過XPATH或者正則進行操做。
2) 全部aa標籤本質都是JSP標籤,應該遵循JSP標籤的規範。其屬性值能夠使用固定的文本或者經過JAVA表達式賦值,如:
<aa:http url="http://www.exmobi.cn"/> 或者 <aa:http url="<%=aa.getReqHeaderValue("url")%>"/> |
可是混搭是不容許的,好比:
<%-- 下面語句會報錯,由於不容許固定文本和JAVA變量混搭實用 --%> <aa:http url="http://www.exmobi.cn/<%=aa.getReqParameterValue("myurl")%>"/> |
若是想要組合字符串只能把固定文本部分也放到JAVA表達式中,如:
<aa:http url='<%="http://www.exmobi.cn/"+aa.getReqParameterValue("myurl")%>'/> |
可是要注意因爲JAVA表達式中只能用雙引號,因此url的值外層只能使用單引號。若是想要url的值用雙引號,只能把JAVA表達式定義到外面的一個變量中,再把該變量的值賦值給url,以下:
<% String myurl = "http://www.exmobi.cn/"+aa.getReqParameterValue("myurl"); %> <aa:http url="<%=myurl %>"/> |
通常這種寫法一般還能解決JAVA表達式中必須有單引號的狀況,不然JAVA表達示中的單引號都要轉義。
敏捷適配JAVA工具集函數是做爲JSP標籤庫的一個補充,讓開發者可以經過JAVA函數的方式對數據源進行靈活的處理。
本節主要介紹請求取值函數、響應取值函數和通用功能函數。
全部工具集函數都封裝在aa類中,故調用方法都是aa.方法名(參數),如:
String usr = aa. getReqParameterValue (「username」);//獲取參數名爲username的值
請求取值函數是主要功能是獲取請求頭信息或者請求體相關信息的函數,一般用在<aa:http>請求以前進行預處理,而後重置<aa:http>的請求信息。經常使用的請求取值函數有:
函數名 |
用途 |
getReqHeaderValue |
根據請求頭的name獲取上下文(pageContext)中的請求頭信息。 |
getReqParameterValue |
經過鍵值對請求體的name獲取對應的value值。 |
getReqParameterValueFromUrl |
獲取url中參數名爲name 的參數值。 |
響應取值函數主要功能是對抽取標籤請求到的數據源進行揀選。經常使用的響應取值函數有:
標籤名 |
用途 |
xpath |
經過xpath獲取指定dsId對應的數據源(抽取標籤)的數據。 |
regex |
經過正則獲取指定dsId對應的數據源(抽取標籤)的數據。 |
copyOfNodeAsStr |
將整個節點(含全部子節點)的內容轉換爲字符串 |
通用功能函數提供了一些與請求無關的函數,主要是對數據的二次轉換和處理等。經常使用的通用功能函數有:
函數名 |
用途 |
escapeXML |
用於對xml的特殊字符進行轉義。 |
jsonToXmlString |
將json格式數據轉換爲xml格式。 |
xpathNode |
用於獲取node節點下指定xpath節點。可用於判斷節點是否存在。 |
regexFilter |
獲取指定字符串中符合正則的部分 |
JSP的工具集函數中跟正則相關的函數,必須至少包含一個組即()包起來的部分,返回的內容就是組內的數據,好比:要取到<span οnclick="location.href='/getDetail.do?id=2423'">下載</span>中onclick裏面的url,一般能夠這麼寫:
aa.regexFilter("[^/]*([^']*)'", aa.xpath(./span/@onclick))
這樣匹配到的結果即爲/getDetail.do?id=2423。
XPATH擴展函數能夠爲數據處理提供便捷的操做。
函數名 |
用途 |
escapexml |
用於對xml的特殊字符進行轉義。 |
escapejson |
用於對json數據敏感的特殊字符進行轉義。 |
htmltoxml |
用於將html節點格式化爲標準的xml。 |
好比:<aa:copy-of xpath="htmltoxml(//table)" dsId="content"/>,能夠避免//table返回的不是標準xml格式致使客戶端沒法解析的錯誤。
JSP主要就是將服務端獲取到的客戶端上行數據真實往第三方系統發起請求,並將第三方系統的響應進行揀選後下行給客戶端。
下面以http://miap.cc:1001/app/template/login.jsp這個系統的登錄頁面爲例,進行說明。(用戶名/密碼:admin/111)
建立一個名爲hellojsp的應用,其首頁地址爲系統的登錄頁面。以下圖所示:
點擊「Next」後,繼續點擊「Finish」後會建立應用的基本目錄和文件,其中客戶端配置文件config.xml的內容以下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <config clientversion="4" scope="client"> <appid>hellojsp</appid> <appname>第一個jsp頁面</appname> <description></description> <version>1.0.0</version> <date>2013-12-05</date> <homepage src="http://miap.cc:1001/app/template/login.jsp"/> <faultconfig src=""/> <vendor url="" email=""></vendor> <access orientation="port" land="false" network="true" gps="true" camera="true" certificate="true"/> <icon selectedlogo="res:image/selectedlogo.png" main="res:image/main.png" logo="res:image/logo.png"/> </config> |
當應用建立好後,同步應用,而且打開客戶端PC模擬器,就能夠看到應用已經在模擬器中,點擊「進入」按鈕,就會報響應碼爲「5019」的錯誤。這個錯誤前面瞭解到是由於沒有配置MAPP路由route的緣由。
打開server目錄下的mapp.xml,爲該首頁地址配置路由指向login.jsp文件進行處理,以下:
<?xml version="1.0" encoding="UTF-8" ?> <maxml version="2.0" xmlns="http://www.nj.fiberhome.com.cn/map" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.nj.fiberhome.com.cn/map maxml-2.0.xsd"> <config> <htmlformat wellformat="true" /> <domain address="miap.cc:1001" name="domain"/> </config>
<route baseaddr="http://domain"> <forward pattern="/app/template/login.jsp" path="mLogin.jsp"/>
</route> </maxml> |
注意:
1) 在這裏使用了僞域名,即將第三方系統的ip:port(miap.cc:1001)部分用一個字串(domain)代替。一旦使用了僞域名,後面應用中請求第三方系統的地方都必須使用僞域名來代替。使用僞域名的好處是當第三方系統的ip或者域名變了,只須要修改僞域名的配置便可,不須要每一個頁面、每一個請求都修改。
下面confi.xml中的首頁地址也須要作相應的調整,以下:
<homepage src="http://domain/app/template/login.jsp"/> |
2) route節點的baseaddr屬性和forward節點的pattern屬性都是正則格式,因此其值若是包含正則的關鍵字是須要轉義的,好比:問號?。
因此若是一個地址爲http://miap.cc:1001/app/getdoc.do?id=4235,那forward的pattern能夠寫爲:
<forward pattern="/app/getdoc.do\?id=4235" path="getdoc.jsp"/> |
可是通常更通用的寫法爲:
<forward pattern="/app/getdoc.do.*" path="getdoc.jsp"/> |
表明一系列的類似請求經過同一個JSP文件進行處理。
3) path指向的JSP文件位於server目錄下的jsp子目錄
從hellojsp應用的server/jsp目錄郵件點擊彈出的「菜單欄」開始選擇「New」-》「JSP File」,以下圖所示:
在彈出的新建JSP的對話框中把文件名填上「mLogin.jsp」:
點擊「Finish」按鈕則文件建立完畢。
JSP的默認內容以下:
<%@ page language="java" import="java.util.*,com.fiberhome.bcs.appprocess.common.util.*" contentType="application/uixml+xml; charset=UTF-8" pageEncoding="UTF-8"%> <%@ include file="/client/adapt.jsp"%> <!DOCTYPE html SYSTEM "http://www.nj.fiberhome.com.cn/exmobi.dtd"> <html> <head> <meta charset="UTF-8"/> <title>Hello World</title> <script> <![CDATA[
]]> </script> </head> <body> This is the page content. </body> </html> |
能夠先嚐試啓動Tomcat,而後點擊客戶端的應用圖標,能夠看到默認的展現效果,以下圖:
判斷一個路由是否配置正確,能夠經過MBuilder的控制檯日誌信息來進行驗證,若是路由正確,會看到有這樣的日誌信息:
能夠清晰看到請求的地址(login.jsp)和轉向的地址(mLogin.jsp),若是跟路由配置的一致就是正確的,若是不一致就須要去看路由配置是否正確或者JSP文件是否正確。
下面開始真正進入JSP的使用。
JSP中的抽取標籤<aa:http>提供了模擬第三方系統請求的方法。客戶端向服務端上行請求的時候帶有第三方系統的URL、請求頭、請求體等信息,這些信息會內置到<aa:http>標籤中做爲默認值,通常不用設置。因此除非須要修改這些信息,不然通常狀況下是不須要進行設置的。
因此首先須要在JSP中拼接html前添加抽取標籤<aa:http>向第三方系統發送請求:
<%@ page language="java" import="java.util.*,com.fiberhome.bcs.appprocess.common.util.*" contentType="application/uixml+xml; charset=UTF-8" pageEncoding="UTF-8"%> <%@ include file="/client/adapt.jsp"%>
<aa:http id="login"></aa:http>
<!DOCTYPE html SYSTEM "http://www.nj.fiberhome.com.cn/exmobi.dtd"> <html> <head> <meta charset="UTF-8"/> <title>Hello World</title> <script> <![CDATA[
]]> </script> </head> <body> This is the page content. </body> </html> |
這表明的是發送一個默認請求,即客戶端要求的是什麼請求則執行什麼請求。可是<aa:http>支持對請求進行重置,包括請求地址、請求方法、請求頭、請求體以及請求/響應的編碼等。
若是請求地址想改成http://www.exmobi.cn,則能夠修改代碼爲:
<aa:http id="login" url="http://www.exmobi.cn"/> |
更多的http模擬請求將在第6章進行講解。
JSP中也能夠經過<aa:sql-excute>執行一個SQL語句並獲取響應。一樣的也會生成以其id爲名稱的臨時文件。如:
<% String title = aa.getReqParameterValue("title"); String sql = "select * from tbl_task where title like '%"+title+"%'"; %> <aa:sql-excute id="selectAll" dbId="postgresql" sql="<%=sql %>"/> |
與<aa:http>不一樣的是,<aa:sql-excute>的響應服務端封裝成了一個XML字符串。
更多數據庫的集成請看第10章。
任何一個JSP能夠跟第三方系統進行屢次請求交互。
臨時文件是服務端向第三方系統請求後生成的響應文件,或者是開發者在開發過程處理某些數據生成的臨時文件。它將請求和響應的數據存儲爲本地的固定格式文件,方便取值的時候有個參照物,也能夠對生成的數據進行檢驗。
通過抽取標籤(如:<aa:http>、<aa:sql-excute>)請求的數據都會在服務端生成臨時文件,臨時文件的地址通常爲:ExMobi-Server根目錄下的\data\bcs\tempfile\users目錄,好比:D:\MBuilder\ExMobi-server\data\bcs\tempfile\users。通過模擬請求後能夠看到請求到的臨時文件爲:
上圖的幾個文件順序恰好反映了「客戶端-服務端-第三方-服務端-客戶端」的一個處理過程其中包含「reqdata」的文件是客戶端請求服務端的時候生成的臨時文件;「*.html」文件是服務端請求第三方後第三方響應給服務端的原始取到的頁面文件,在JSP中寫正則的時候須要以該文件爲參照;「*-dom.xml」文件是服務端對原始文件進行默認轉換後的文件,在寫xpath的時候須要以該文件爲參照。包含「rspdata」開頭的文件是服務端最終響應給客戶端的文件,客戶端最終根據這個文件的內容進行界面展現,因此若是客戶端界面顯示錯誤或者報錯,須要經過查看該文件來排查問題。
打開「http-login-dom.xml」文件,能夠看到咱們須要的登錄頁面的信息:
<form id="frm" method="post"> <table align="center" border="0" cellpadding="0" cellspacing="0" class="right-table03" width="562"> <tr> <td width="221"><table border="0" cellpadding="0" cellspacing="0" class="login-text01" width="95%"> <tr> <td><table border="0" cellpadding="0" cellspacing="0" class="login-text01" width="100%"> <tr> <td align="center"><img height="97" src="images/ico13.gif" width="107"/></td> </tr> <tr> <td align="center" height="40">燶highlight40</td> </tr> </table></td> <td><img height="292" src="images/line01.gif" width="5"/></td> </tr> </table></td> <td><table border="0" cellpadding="0" cellspacing="0" width="100%"> <tr> <td class="login-text02" height="35" width="31%">用戶名<br/></td> <td width="69%"><input class="easyui-validatebox" id="username" name="username" required="true" size="30" type="text"/></td> </tr> <tr> <td class="login-text02" height="35">密碼<br/></td> <td><input class="easyui-validatebox" id="password" name="password" required="true" size="31" type="password"/></td> </tr>
<tr> <td height="35">燶highlight40</td> <td><input class="right-button01" id="submitBtn" name="submitBtn" type="button"value="確認登錄"/> <input class="right-button02" name="Submit232" onclick="doReset()" type="button" value="重 置"/></td> </tr> </table></td> </tr> </table> </form> |
其特色是咱們想要獲取的值(登錄信息)都在table下的td中。
可是form的提交地址是在JS中:
$(document).ready( function() { $('#submitBtn').click( function() { $('#frm').form('submit',{ url:'checkLogin.jsp', onSubmit:function(){ return $('#frm').form('validate'); }, success:function(data){ var msg = eval('(' + data + ')'); if(msg.status!='success'){ $.messager.alert('提示', msg.status, 'info'); changeRnd(); return; } window.location.href = 'index.html'; } }); }); |
因此能夠肯定揀選的方案是表單裏的內容經過xpath取,而提交的地址經過正則取,同時根據須要進行一些過濾處理。
在作移動應用開發的過程當中,須要注意的一個問題就是佈局上跟WEB有很大的不一樣,不能直接照搬WEB上的佈局來進行展現,並且ExMobi也是不容許的,由於並非全部的HTML標籤ExMobi都支持。因此,在進行取值的時候須要作一些過濾。
在XML工具中打開「http-login-dom.xml」文件,點擊xpath工具以下圖,在xpath輸入欄中輸入「//table[./tr/td/input]//td/node()[not(name()='br' or name()='a' or @type='button')]」,能夠看到輸出效果:
上面的xpath指明要取含有輸入框的table下的全部節點元素,可是不包括標籤名爲br、a和屬性的type爲button的節點,由於這些節點暫時不須要。過濾後的結果就只剩下咱們須要的登錄信息。
登錄提交的地址是在script標籤中,因此首先要經過xpath獲取到script標籤下的全部內容,而後再經過正則獲取url部分的內容:
<%=aa.regexFilter("url:'([^']*)',", aa.xpath("//script[not(@src)]", "login"))%> |
mLogin.jsp代碼編寫完成,其所有代碼以下:
<%@ page language="java" import="java.util.*,com.fiberhome.bcs.appprocess.common.util.*" contentType="application/uixml+xml; charset=UTF-8" pageEncoding="UTF-8"%> <%@ include file="/client/adapt.jsp"%>
<aa:http id="login"></aa:http>
<!DOCTYPE html SYSTEM "http://www.nj.fiberhome.com.cn/exmobi.dtd"> <html> <head> <meta charset="UTF-8"/> <title>登錄——get請求</title> </head> <body> <%-- action爲http://domain/app/template/checkLogin.jsp --%> <form action="http://domain/app/template/<%=aa.regexFilter("url:'([^']*)',", aa.xpath("//script[not(@src)]", "login"))%>" method="post" target="_self"> <%-- 包含有input的table就是登錄須要信息,對該表格td下的全部節點作循環讀取 --%> <aa:for-each dsId="login" var="node" xpath="//table[./tr/td/input]//td/node()[not(name()='br' or name()='a' or @type='button')]"> <aa:if testxpath="name()=''" dsId="node"> <font color="#ff0000" style="width:30%;"><aa:value-of xpath="." dsId="node"/></font> </aa:if> <aa:if testxpath="name()='input'" dsId="node"> <input type="<%=aa.xpath("@type", "node")%>" id="<%=aa.xpath("@id", "node")%>" name="<%=aa.xpath("@name", "node")%>" style="width:70%;"></input> </aa:if> </aa:for-each> <input type="submit" value="登錄" style="width:100%"/> </form> </body> </html> |
須要注意的是,每一個數據的處理都離不開數據源,好比<aa:for-each>的處理,是以<aa:http>爲數據源的,處理的是這裏面的數據,因此必須指定<aa:for-each>的dsId爲<aa:http>的id。而<aa:for-each>內部數據的取值都是每次循環的結果,因此也必需要指定dsId爲<aa:for-each>的var值(這裏比較特殊,通常數據源的標識都是id)。
進入客戶端,效果以下:
通過上一節的瞭解,咱們知道了一個JSP的基本使用,可是JSP具備強大的集成能力,因此瞭解JSP的處理流程會更有利於數據的集成開發。
JSP的處理流程有四步:請求信息預處理、請求第三方系統、第三方響應預處理和獲取響應信息。
JSP中是經過抽取標籤與第三方系統進行交互的,在交互以前的處理就稱爲請求信息預處理。也就是說,在與第三方交互前,把交互須要的準備的一些條件準備好。
能夠預處理的數據主要來源於客戶端的請求,包含客戶端請求的頭信息、請求參數、附件信息等。
這些信息一般能夠在臨時文件中帶有「reqdata」的臨時文件中,好比咱們打開請求login的臨時文件「reqdata.xml」,以下:
<clientReqMsg> <headers> <header name="host" value="127.0.0.1:8001"/> <header name="accept-language" value="zh-cn"/> <header name="user-agent" value="GAEA-Client"/> <header name="connection" value="close"/> <header name="dpi" value="hdpi"/> <header name="devicetype" value="phone"/> <header name="name" value=""/> <header name="ec" value=""/> <header name="imsi" value="100000000000001"/> <header name="esn" value="s00000000000001"/> <header name="os" value="pc"/> <header name="platformid" value="i9000"/> <header name="clientversion" value="4.8.0.0"/> <header name="osversion" value="1.0"/> <header name="clientid" value="gaeaclient-pc-000001"/> <header name="screenwidth" value="480"/> <header name="screenheight" value="800"/> <header name="msisdn" value=""/> <header name="cmd" value="URLREQ"/> <header name="appid" value="hellojsp"/> <header name="cookie" value="JSESSIONID=DABD95464BA87F6F9684C60F4F06744D.jvm1"/> <header name="content-length" value="65"/> <header name="url" value="http://domain/app/template/login.jsp"/> <header name="charset" value="GB2312"/> <header name="method" value="1"/> </headers> <parmters> <!-- <parmter name="username" value=""/> <parmter name="password" value=""/> --> </parmters> </clientReqMsg> |
其中,headers標籤中存儲的是客戶端請求的一些頭信息(客戶端頭信息說明請看14.2客戶端頭信息說明),parmters信息中存儲了客戶端請求的一些參數和附件信息。這些信息在JSP中都是能夠獲取的。
須要注意的是頭信息的名稱都是英文字母小寫,而參數的名稱是根據客戶端設定的控件的name的具體值而定。
1) 獲取頭信息
獲取某個header的頭信息方法以下:
String header = aa.getReqHeaderValue(「headerName」); |
獲取headers下的全部header的頭信息方法以下:
HashMap<String, String> headers = aa.getReqAllHeader(); |
好比,要獲取客戶端請求的url是什麼,能夠經過以下方法:
String url = aa.getReqHeaderValue(「url」);// 獲得http://domain/app/template/login.jsp |
2) 獲取參數信息
參數分爲鍵值對(形如:a=1&b=2)參數和非鍵值對(通常爲XML、JSON等文本)參數,而鍵值對參數有多是在URL中,也有多是在請求體中。
URL中的鍵值對參數值獲取方法以下:
String para = aa.getReqParameterValueFromUrl(「paraName」);//獲取某個url參數值 Map<String, Object> pmap = aa.getReqParametersFromUrl();//獲取所有url中的參數 |
請求體中的鍵值對參數值獲取方法以下:
String para = String para = aa.getReqParameterValue(「paraName」);//獲取某個請求體參數值 Map<String, Object> pmap = aa.getReqParameters();//獲取所有請求體中的參數 |
獲取非鍵值對參數值的方法以下:
String reqContent = aa.getReqContent();//獲取請求體中的非鍵值對參數 |
好比:客戶端請求一個地址爲http://domain/index.jsp?type=mobile那麼要獲取type參數的值mobile的方法爲:
String type = aa. getReqParameterValueFromUrl (「type」);//獲得的值爲mobile |
3) 獲取附件信息
附件信息通常包含文件名、文件的字節流、文件的物理地址等信息。經常使用的方法有:
byte[] file = aa.getReqAttachBody("file");//獲取某個文件控件參數下文件的字節流 String fileName = aa.getReqAttachName(「file」);//得到文件控件參數下文件的名稱 boolean isDownload = aa.isDownload();//得到客戶端請求的附件是不是作下載操做 |
只要具有了請求第三方系統的條件,就能夠請求第三方系統,目前ExMobi的JSP中已經封裝好的請求第三方系統的方法主要有兩種:一種是http請求<aa:http>標籤;一種是SQL腳本執行<aa:sql-excute>標籤。
請求第三方系統經常使用的處理:
1) 設置基本信息
基本信息能夠直接在相應的抽取標籤中進行設置便可:
<aa:http id=「login」 [ url, method, enctype, reqcharset, rspcharset, mimetype, unprocessurl, keepreqdata]/> <aa:sql-excute id="selectAll" dbId="postgresql" sql=""/> |
有些基本信息是在「請求信息預處理」中進行重置後從新賦值到請求中。
對於<aa:http>請求,若是不須要對基本信息進行設置,也就是說要原樣請求客戶端發送的信息,寫個空標籤就能夠了,即:
<aa:http id=「login」 /> |
2) 設置參數信息
參數信息一般都是在抽取標籤下的子標籤中進行,這些標籤通常包括:
<aa:param [name,value,type, filepath, filename]/> <aa:content [bytes]>[content]</aa:content> <aa:weboffice /> <aa:sql-param [type,value]/> |
3) 設置頭信息
設置頭信息只有在<aa:http>請求的時候纔會須要,對於一些特殊的請求須要特別的去設置頭信息,如cookie信息、content type等。
<aa:header [name,value,type]/> <aa:set-cookie [value,scope] /> |
通過這一步驟的處理,會產生臨時文件,以供開發者做爲參照物從中取值。
臨時文件的查看在5.7.5已經有介紹。
請求第三方系統結束後,能夠經過臨時文件直觀的瞭解請求的結果。可是有時候響應數據內容不規範,不方便獲取等。因此若是要獲取響應信息,有可能須要現對請求進行一個預處理。
請求響應預處理一般使用的方法爲:
1) 基本思路是先把響應當成普通文本(String)進行處理
2) 經過aa.regex或者aa.xpath獲取到數據後採用aa.regexReplace、aa.regexFilter等方法或者java的replace、replaceAll等方法進行替換部份內容
3) 也能夠將獲取到的json轉換爲xml,如:aa.jsonToXmlString(string),或者直接使用java的JSONObject類來處理的JSON數據。
4) 基本上JAVA對字符串的處理技巧或者特殊格式數據的處理技巧均可以靈活運用。
數據預處理後若是還須要把數據做爲XML格式數據源,能夠使用<aa:datasource>標籤進行格式化:
<aa:datasource id=「newLogin」 value=「前面預處理的變量」/> |
格式化後,便可經過<aa:datasource>的id指定獲取該數據源的數據。
特別的,<aa:datasource>內部也提供對數據的預處理,<aa: replace/>和<aa: filter/>是<aa:datasource/>標籤的子標籤,用於對<aa:datasource/>標籤的輸入內容作預替換處理,在<aa:datasource/>標籤構造數據源以前,先作正則替換處理,替換完成後,<aa:datasource/>再構造數據源,若是有多個<aa:replace/>或<aa: filter/>標籤,則依次順序替換。好比:
<aa:datasource value='<%=str1%>' id="login" > <aa:replace replacement="div" pattern="scritpt " /><!—把script替換爲div --> </aa:datasource> |
獲取響應信息的方法有不少種,這裏主要是指對XML數據的獲取除了XML之外的數據能夠經過JAVA的相關方法獲取,好比JSON的操做類JSONObject等。
XML數據的獲取方法:
1) 循環獲取
<aa:for-each var=「list」 dsId=「login」 [ xpath, regex]/> |
2) 取值和處理
<aa:copy-of [dsId,xpath,regex]/> <aa:value-of [dsId,xpath,regex]/> aa.xpath(string,dsId); aa.regex(string,dsId); aa.copyOfNodeAsStr(string, dsId); aa.regexFilter(string); |
除此以外,還有可能須要對附件進行處理:
<!—文件下載 --> <aa:file-download dsId=「file」 [filename, iscache]/> <!—文件預覽 --> <aa:file-preview dsId=「file」 [filename, iscache, previewtype,zoom]/> <!—獲取響應文件的字節流 --> byte[] file = aa.getRspAttachBody(「file"); byte[] file = aa.getWebOfficeRspOtherBytes(「file"); <!—將字節流專爲輸入流作進一步操做 --> InputStream is = new ByteArrayInputStream(file); |
第3篇ExMobi開發進階
頁面適配集成常說爲頁面抓取,就是經過模擬第三方系統的http請求抓取其web頁面的內容有選擇的進行展現。因此須要對HTTP協議有必要的瞭解。
中間件經過JSP中的<aa:http>標籤實現了徹底模擬http請求的功能,本節主要經過常見抓包分析不一樣抓包的特徵及實現方式。
HTTP是一個屬於應用層的面向對象的協議,因爲其簡捷、快速的方式,適用於分佈式超媒體信息系統。
HTTP協議的主要特色可歸納以下:
1) 支持客戶/服務器模式。
2) 簡單快速:客戶向服務器請求服務時,只需傳送請求方法和路徑。請求方法經常使用的有GET、HEAD、POST。每種方法規定了客戶與服務器聯繫的類型不一樣。因爲HTTP協議簡單,使得HTTP服務器的程序規模小,於是通訊速度很快。
3) 靈活:HTTP容許傳輸任意類型的數據對象。正在傳輸的類型由Content-Type加以標記。
4) 無鏈接:無鏈接的含義是限制每次鏈接只處理一個請求。服務器處理完客戶的請求,並收到客戶的應答後,即斷開鏈接。採用這種方式能夠節省傳輸時間。
5) 無狀態:HTTP協議是無狀態協議。無狀態是指協議對於事務處理沒有記憶能力。缺乏狀態意味着若是後續處理須要前面的信息,則它必須重傳,這樣可能致使每次鏈接傳送的數據量增大。另外一方面,在服務器不須要先前信息時它的應答就較快。
http請求由三部分組成,分別是:請求行、消息報頭、請求正文。
好比:
POST /app/template/checkLogin.jsp HTTP/1.1 Host: miap.cc:1001 User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Referer: http://miap.cc:1001/app/template/login.jsp Cookie: JSESSIONID=A22FA8A31DF0766B066635D0CD137ED2; JSESSIONID=8E5534524D9F783B8A6C2487664651EB Connection: keep-alive Content-Type: application/x-www-form-urlencoded Content-Length: 27
username=admin&password=111 |
請求行以一個方法符號開頭,以空格分開,後面跟着請求的URI和協議的版本,格式以下:Method Request-URI HTTP-Version CRLF
其中 Method表示請求方法;Request-URI是一個統一資源標識符;HTTP-Version表示請求的HTTP協議版本;CRLF表示回車和換行(除了做爲結尾的CRLF外,不容許出現單獨的CR或LF字符)。
請求方法(全部方法全爲大寫)有多種,各個方法的解釋以下:
GET 請求獲取Request-URI所標識的資源
POST 在Request-URI所標識的資源後附加新的數據
HEAD 請求獲取由Request-URI所標識的資源的響應消息報頭
PUT 請求服務器存儲一個資源,並用Request-URI做爲其標識
DELETE 請求服務器刪除Request-URI所標識的資源
TRACE 請求服務器回送收到的請求信息,主要用於測試或診斷
CONNECT 保留未來使用
OPTIONS 請求查詢服務器的性能,或者查詢與資源相關的選項和需求
目前ExMobi中支持的是GET和POST方法。
請求報頭容許客戶端向服務器端傳遞請求的附加信息以及客戶端自身的信息。
經常使用的請求報頭:
Accept
Accept請求報頭域用於指定客戶端接受哪些類型的信息。eg:Accept:image/gif,代表客戶端但願接受GIF圖象格式的資源;Accept:text/html,代表客戶端但願接受html文本。
Authorization
Authorization請求報頭域主要用於證實客戶端有權查看某個資源。當瀏覽器訪問一個頁面時,若是收到服務器的響應代碼爲401(未受權),能夠發送一個包含Authorization請求報頭域的請求,要求服務器對其進行驗證。
Host(發送請求時,該報頭域是必需的)
Host請求報頭域主要用於指定被請求資源的Internet主機和端口號,它一般從HTTP URL中提取出來的,eg:
咱們在瀏覽器中輸入:http://www.exmobi.cn/index.jsp
瀏覽器發送的請求消息中,就會包含Host請求報頭域,以下:
Host:www.exmobi.cn
此處使用缺省端口號80,若指定了端口號,則變成:Host:www.exmobi.cn:指定端口號
User-Agent
咱們上網登錄論壇的時候,每每會看到一些歡迎信息,其中列出了你的操做系統的名稱和版本,你所使用的瀏覽器的名稱和版本,這每每讓不少人感到很神奇,實際上,服務器應用程序就是從User-Agent這個請求報頭域中獲取到這些信息。User-Agent請求報頭域容許客戶端將它的操做系統、瀏覽器和其它屬性告訴服務器。不過,這個報頭域不是必需的,若是咱們本身編寫一個瀏覽器,不使用User-Agent請求報頭域,那麼服務器端就沒法得知咱們的信息了。
HTTP請求正文的做用是把一些自定義信息告訴服務器端,它能夠包含各類格式的內容。常見的有鍵值對格式(形如a=1&b=2)、非鍵值對格式(XML、JSON等)或者字節流等等。
在接收和解釋請求消息後,服務器返回一個HTTP響應消息。
HTTP響應也是由三個部分組成,分別是:狀態行、消息報頭、響應正文。
好比:
HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Content-Type: text/html;charset=utf-8 Transfer-Encoding: chunked Date: Sat, 07 Dec 2013 14:25:08 GMT
{"status":"success"} |
其格式以下:
HTTP-Version Status-Code Reason-Phrase CRLF
其中,HTTP-Version表示服務器HTTP協議的版本;Status-Code表示服務器發回的響應狀態代碼;Reason-Phrase表示狀態代碼的文本描述。
狀態代碼有三位數字組成,第一個數字定義了響應的類別,且有五種可能取值:
1xx:指示信息--表示請求已接收,繼續處理
2xx:成功--表示請求已被成功接收、理解、接受
3xx:重定向--要完成請求必須進行更進一步的操做
4xx:客戶端錯誤--請求有語法錯誤或請求沒法實現
5xx:服務器端錯誤--服務器未能實現合法的請求
常見狀態代碼、狀態描述、說明:
200 OK //客戶端請求成功
400 Bad Request //客戶端請求有語法錯誤,不能被服務器所理解
401 Unauthorized //請求未經受權,這個狀態代碼必須和WWW-Authenticate報頭域一塊兒使用
403 Forbidden //服務器收到請求,可是拒絕提供服務
404 Not Found //請求資源不存在,eg:輸入了錯誤的URL
500 Internal Server Error //服務器發生不可預期的錯誤
503 Server Unavailable //服務器當前不能處理客戶端的請求,一段時間後可能恢復正常
eg:HTTP/1.1 200 OK (CRLF)
響應報頭容許服務器傳遞不能放在狀態行中的附加響應信息,以及關於服務器的信息和對Request-URI所標識的資源進行下一步訪問的信息。
經常使用的響應報頭
Location
Location響應報頭域用於重定向接受者到一個新的位置。Location響應報頭域經常使用在 更換域名的時候。
Server
Server響應報頭域包含了服務器用來處理請求的軟件信息。與User-Agent請求報頭域是相對應的。下面是Server響應報頭域的一個例子:
Server:Apache-Coyote/1.1
WWW-Authenticate
WWW-Authenticate響應報頭域必須被包含在401(未受權的)響應消息中,客戶端收到401響應消息時候,併發送Authorization報頭域請求服務器對其進行驗證時,服務端響應報頭就包含該報頭域。
eg:WWW-Authenticate:Basic realm="Basic Auth Test!" //能夠看出服務器對請求資源採用的是基本驗證機制。
響應正文跟請求正文相似,是服務端響應給客戶端處理的一些數據。一般通常是HTML、XML、JSON或者文件格式等。
5.7.1一節中主要模擬了GET請求,本節繼續對其餘常見的請求進行抓包分析並進行處理。
對於不一樣的請求方式,基本處理技巧是在JSP將須要修改的抓包部分進行重置。
下面以oademo應用爲例說明不一樣請求方式的抓包特徵以及如何在JSP中進行模擬請求。
注:本章節內容均使用到僞域名,僞域名的使用請參考第5.7.2章節。
首先須要建立一個oademo的應用,其基本信息以下:
在建立好的應用中,配置僞域名和route路由以下:
<?xml version="1.0" encoding="UTF-8" ?> <maxml version="2.0" xmlns="http://www.nj.fiberhome.com.cn/map" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.nj.fiberhome.com.cn/map maxml-2.0.xsd"> <config> <htmlformat wellformat="true" /> <!-- 配置僞域名 --> <domain address="miap.cc:1001" name="oa"/> </config> <!-- 配置route路由,若是有多個域名能夠配置多個route --> <route baseaddr="http://oa"> <!-- 此處配置客戶端URL請求的處理JSP文件,pattern是匹配URL的正則表達式,path是對用的服務端的JSP文件名 -->
</route> </maxml> |
上一章的例子咱們實際上是作了一個get請求的例子;而登錄頁面的登錄提交操做其實是一個post帶鍵值對的請求;登錄測試OA系統後,能夠看到左側的菜單以下:
其中「建立任務」菜單點擊進去的頁面有個「保存」按鈕,點擊該按鈕實際是進行了post帶附件的請求;「任務信息查看[json格式]」菜單的請求實際是一個post帶json非鍵值對參數的請求;「任務信息查看[xml格式]」菜單的請求實際是一個post帶xml非鍵值對參數的請求。
下面咱們就來一一瞭解這些請求的抓包特徵以及再ExMobi中如何來模擬這些請求。
GET請求就是最簡單的請求一個URL地址。這種請求的特徵是不帶請求正文,若是有參數的話是拼接在URL地址中的,如:
http://www.nj.fiberhome.com.cn或者http://www.nj.fiberhome.com.cn/reg.jsp?type=login
在客戶端中,通常經過超連接發起一個GET請求。好比oademo應用的首頁地址:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <config clientversion="4" scope="client" devicetype="all"> <appid>oademo</appid> <appname>頁面抓取集成</appname> <description></description> <version>1.0</version> <date>2013-12-11</date> <homepage src="http://oa/app/template/login.jsp"/> <faultconfig src=""/> <vendor url="" email=""></vendor> <access orientation="port" land="false" network="true" gps="true" camera="true" certificate="true"/> <icon selectedlogo="res:image/selectedlogo.png" main="res:image/main.png" logo="res:image/logo.png"/> </config> |
能夠看到homepage的src請求的地址是http://oa/app/template/login.jsp,這就是一個GET請求的地址,oa是個僞域名,實際地址爲:http://miap.cc:1001/app/template/login.jsp
在頁面中也能夠經過href或者onclick等能夠執行超連接的屬性中設置一個地址發起GET請求。
首先打開抓包工具,在PC瀏覽器上訪http://miap.cc:1001/app/template/login.jsp
請求結果,這是一個登錄的頁面,以下圖:
可看到其抓包以下所示:
GET /app/template/login.jsp HTTP/1.1 Accept: text/html, application/xhtml+xml, */* Accept-Language: zh-CN User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0) Accept-Encoding: gzip, deflate Host: miap.cc:1001 Connection: Keep-Alive Cookie: JSESSIONID=5BE7331CD67DAC85C140ED09D7B1BCA1.worker1 |
GET請求的抓包特徵爲:沒有請求正文。若是有參數通常在URL中。
通常GET請求都不須要對抓包進行重組,直接在JSP中使用<aa:http>發起默認請求便可。執行結果會在服務端生成臨時文件,能夠從臨時文件中揀選數據。
在應用中爲該地址配置好處理的JSP:
<!-- 登錄頁面get --> <forward pattern="/app/template/login.jsp" path="mLogin.jsp"/> |
在mLogin.jsp代碼中直接使用<aa:http>,不作任何重置便可正確模擬GET請求。
<%@ page language="java" import="java.util.*,com.fiberhome.bcs.appprocess.common.util.*" contentType="application/uixml+xml; charset=UTF-8" pageEncoding="UTF-8"%> <%@ include file="/client/adapt.jsp"%>
<aa:http id="login"></aa:http>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.nj.fiberhome.com.cn/exmobi.dtd"> <html> <head> <meta charset="UTF-8"/> <title>登錄——get請求</title> </head> <body> <%-- action爲http://oa/app/template/checkLogin.jsp --%> <form action="http://oa/app/template/<%=aa.regexFilter("url:'([^']*)',", aa.xpath("//script[not(@src)]", "login"))%>" method="post"> <%-- 包含有input的table就是登錄須要信息,對該表格td下的全部節點作循環讀取 --%> <aa:for-each dsId="login" var="node" xpath="//table[./tr/td/input]//td/node()[not(name()='br' or name()='a' or @type='button')]"> <aa:choose> <aa:when testxpath="name()=''" dsId="node"> <font color="#ff0000" style="width:30%;"><aa:value-of xpath="." dsId="node"/></font> </aa:when> <aa:when testxpath="name()='input'" dsId="node"> <input type="<%=aa.xpath("@type", "node")%>" id="<%=aa.xpath("@id", "node")%>" name="<%=aa.xpath("@name", "node")%>" style="width:70%;"></input> </aa:when> </aa:choose>
</aa:for-each> <input type="submit" value="登錄" style="width:100%"/> </form> </body> </html> |
最終客戶端展現效果爲:
咱們常見的POST通常都是帶鍵值對請求體。好比登錄系統、查詢列表等都能看到這類請求的身影。
這裏要實現的點擊登錄頁面的「確認登錄」按鈕,就是一個post帶鍵值對的請求。
客戶端代碼見5.1.2.3一節,GET請求的數據通過揀選已經構成一個登錄的表單,該表單請求的地址爲http://oa/app/template/checkLogin.jsp,實際地址爲http://miap.cc:1001/app/template/checkLogin.jsp,請求的方法是POST。
在PC瀏覽器訪問到的登錄界面中,輸入用戶名(admin)、密碼(111),點擊「確認登錄」,在抓包工具中能夠看到抓包信息以下:
POST /app/template/checkLogin.jsp HTTP/1.1 Accept: text/html, application/xhtml+xml, */* Referer: http://miap.cc:1001/app/template/login.jsp Accept-Language: zh-CN User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0) Content-Type: application/x-www-form-urlencoded Accept-Encoding: gzip, deflate Host: miap.cc:1001 Content-Length: 45 Connection: Keep-Alive Cache-Control: no-cache Cookie: JSESSIONID=4EBCD5E41D5CA6A1AA0B6ECE05516C81.worker1
username=admin&password=111 |
POST請求帶鍵值對請求體特徵爲:請求體格式爲parameterName1=parameterValue1& parameterName2=parameterValue2……。
通常POST帶鍵值對請求若是參數不復雜的狀況不須要對請求進行重組,有的參數須要進行一些修改的能夠在客戶端經過JS進行修改後提交,或者先上行到JSP中,在JSP進行修改。
該請求屬於簡單請求,首先配置MAPP路由:
<!-- 登錄校驗post帶鍵值對 --> <forward pattern="/app/template/checkLogin.jsp" path="mCheckLogin.jsp"/> |
而後在mCheckLogin.jsp中模擬請求,執行結果會在服務端生成臨時文件,能夠從臨時文件中揀選數據。代碼以下:
<%@ page language="java" import="java.util.*,com.fiberhome.bcs.appprocess.common.util.*" contentType="application/uixml+xml; charset=UTF-8" pageEncoding="UTF-8"%> <%@ include file="/client/adapt.jsp"%>
<aa:http id="checkLogin"/>
<%/* 登錄成功的響應結果爲: {"status":"success"} 登錄失敗的響應結果不含success 因此能夠根據響應結果是否包含succes來判斷是否登錄成功 */%>
<% //登錄成功分支——登錄成功則顯示菜單頁面 if(aa.regex(".*", "checkLogin").indexOf("success")>-1){ %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.nj.fiberhome.com.cn/exmobi.dtd"> <html> <head> <meta charset="UTF-8"/> <title>校驗:post帶鍵值對</title> </head> <body> <listitem type="twoline" href="http://oa/app/template/jsp/addTask.jsp" caption="建立任務 " sndcaption="post帶附件的請求實例"/> <listitem type="twoline" href="document.getElementById('form').submit()" caption="任務信息查看[xml格式]" sndcaption="post請求體爲XML實例,經過form提交"/> <listitem type="twoline" href="http://oa/app/template/jsp/newInfo.jsp" caption="發送信息" sndcaption="合併請求實例,一個JSP發起兩次aa:http請求"/> <form id="form" action="http://oa/app/template/action/taskManagerAction.jsp?handler=list&dataType=xml&timeStamp=1340797760522" method="post"> <input type="hidden" name="page" value="1"/> <input type="hidden" name="rows" value="5"/> </form> </body> </html> <% }else{//登錄失敗分支,登錄失敗則進行提示 %> <html type="alert"> <body> <alert title="提示" icontype="alarm"> <msg>登錄信息錯誤,請從新輸入!</msg> </alert> </body> </html> <% } %> |
該請求中根據響應結果作了判斷,若是響應內容包含成功信息則把菜單列表顯示出來,不然若是內容不包含成功信息則認爲是錯誤的,會提示從新輸入帳號密碼,關閉提示框會跳轉到登錄頁面。
成功登錄效果:
POST請求帶附件,通常都是經過form方式提交的,而且form必需要設置屬性enctype的值爲multipart/form-data。
在5.1.3.3的結果中,第一個listitem超連接的地址爲http://oa/app/template/jsp/addTask.jsp,對應的實際地址爲http://miap.cc:1001/app/template/jsp/addTask.jsp,因爲這個也是一個GET請求前面已經分析過,這裏掠過。
其在PC瀏覽器的效果以下圖:
配置mapp路由
<!-- 任務建立get --> <forward pattern="/app/template/jsp/addTask.jsp" path="mAddTask.jsp"/> |
通過mAddTask.jsp處理後的代碼以下:
<%@ page language="java" import="java.util.*,com.fiberhome.bcs.appprocess.common.util.*" contentType="application/uixml+xml; charset=UTF-8" pageEncoding="UTF-8"%> <%@ include file="/client/adapt.jsp"%>
<%-- 請求新建任務頁面 --%> <aa:http id="addTask"/> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.nj.fiberhome.com.cn/exmobi.dtd"> <html> <head> <meta charset="UTF-8"/> <title>新建——get請求</title> <script> <![CDATA[ function doSubmit(){ if(document.getElementById("title").value==""){ alert("標題不能爲空!"); return; }else if(document.getElementById("days").value==""){ alert("工期不能爲空!"); return; }else if(document.getElementById("begin_time").value==""){ alert("開始時間不能爲空!"); return; }else if(document.getElementById("end_time").value==""){ alert("結束時間不能爲空"); return; }else if(document.getElementById("uploadImg").value==""){ alert("附件不能爲空!"); return; } document.getElementById("form").submit(); } ]]> </script> </head> <body> <%-- action爲http://oa/app/template/action/taskManagerAction.jsp?handler=save --%> <form id="form" action="http://oa/app/template<%=aa.regexFilter("url:'..([^']+)'", aa.xpath("//script[not(@src)]", "addTask"))%>" method="post" target="_self" enctype="multipart/form-data"> <%-- 包含有input的table就是登錄須要信息,對該表格tr作循環讀取 --%> <aa:for-each dsId="addTask" var="node" xpath="//fieldset/table/tr//td/node()"> <aa:choose> <%-- 獲取文本標題 --%> <aa:when testxpath="name()=''" dsId="node"> <font color="#ff0000" style="width:30%;"><aa:value-of xpath="." dsId="node"/></font> </aa:when> <%-- 將有選擇時間的input轉成ExMobi的object控件 --%> <aa:when test='<%=aa.xpath("contains(./@name, \'_time\')", "node") %>'> <object type="date" name="<%=aa.xpath("./@name","node")%>" id="<%=aa.xpath("./@id", "node")%>" style="width:70%;"></object> </aa:when> <%-- 普通input照常輸出 --%> <aa:when testxpath="name()='input'" dsId="node"> <input type="<%=aa.xpath("./@type", "node")%>" name="<%=aa.xpath("./@name", "node")%>" id="<%=aa.xpath("./@id", "node")%>" style="width:70%;"/> </aa:when> <%-- 普通textarea照常輸出 --%> <aa:when testxpath="name()='textarea'" dsId="node"> <textarea name="<%=aa.xpath("./@name", "node")%>" id="<%=aa.xpath("./@id", "node")%>" style="width:70%;height:60;"></textarea> </aa:when> <%-- 普通select照常輸出 --%> <aa:when testxpath="name()='select'" dsId="node"> <select name="<%=aa.xpath("./@name", "node")%>" id="<%=aa.xpath("./@id", "node")%>" style="width:70%"> <%-- 取select下的option --%> <aa:for-each var="option" xpath="option" dsId="node"> <option value="<%=aa.xpath("./@value", "option")%>"><%=aa.xpath(".", "option")%></option> </aa:for-each> </select> </aa:when> <aa:otherwise> <%-- 特殊的控件走此分支 --%>
</aa:otherwise> </aa:choose>
</aa:for-each> </form> </body> <footer> <div href="doSubmit()" style="padding:10 0 10 0;text-align:center;background-color:#cccccc;"> 保存 </div> </footer> </html> |
客戶端模擬器效果以下圖:
能夠看到,這是一個帶附件上傳的表單,點擊「保存」的時候,會把附件做爲表單內容進行提交。
這裏面作了一些表單校驗的處理,對一些必填項作校驗。
在PC瀏覽器上填寫好必填信息後,點擊「保存」按鈕,能夠獲得抓包信息以下:
POST /app/template/action/taskManagerAction.jsp?handler=save HTTP/1.1 Accept: text/html, application/xhtml+xml, */* Referer: http://miap.cc:1001/app/template/jsp/addTask.jsp Accept-Language: zh-CN User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0) Content-Type: multipart/form-data; boundary=---------------------------7dc16ab30822 Accept-Encoding: gzip, deflate Host: miap.cc:1001 Content-Length: 6557 Connection: Keep-Alive Cache-Control: no-cache Cookie: JSESSIONID=7E8076B7D00AC132AEB9EDEBE2226136.worker1
-----------------------------7dc16ab30822 Content-Disposition: form-data; name="title"
任務7 -----------------------------7dc16ab30822 Content-Disposition: form-data; name="days"
7 -----------------------------7dc16ab30822 Content-Disposition: form-data; name="begin_time"
2012-05-29 -----------------------------7dc16ab30822 Content-Disposition: form-data; name="end_time"
2012-07-04 -----------------------------7dc16ab30822 Content-Disposition: form-data; name="executor"
王民 -----------------------------7dc16ab30822 Content-Disposition: form-data; name="priority_level"
急 -----------------------------7dc16ab30822 Content-Disposition: form-data; name="textarea"
任務7 -----------------------------7dc16ab30822 Content-Disposition: form-data; name="uploadImg"; filename="pclogo.jpg" Content-Type: image/jpeg
?? JFIF ` ` ? C
? C
? : w ? ? ? 黟 ??O啢?琶因3? k擾蓕?"跚因.灖峏圄赹n桺 腅t 鬮‑'?N聴檣捘VW砰?蟈}zl完漾 i<囑ㄇ?\l揝韘A?b漊鑕色#療4喒鎰7H7蠺f?|v霘5堎Ue庬_u卣dZ蹗匳J 橃裭桐/8卜茮L菤 樼各?Xd_趲軳eGJ? 瑧匯縗眏虆? 濽貇=顗聰}d/論曖 蚮 廊 g? % !0"2? 馯DG凃qK峏嶴{毃潸\ㄔ&VK彮jj瘓鞛鞭湝稼3‑資憒鞤挧窉€磁%闓阜嘡淚+譪氻栝[秥`貶?帔? +x鐗Φ砆液D贈 L?稘梂縹幥 甌e?mr籧帊斡?眥疄G睺tcM腏l玟憈c㏑??aR秿[y5?n瑄?{鉍# 橆欙)衹岾[?~O Y? 挬;攌櫱涵ZXQR橫ZD$??杆?發+ωl@C▌キ%kb臘U腬r?蝧杍Ei???b?? 鬁??憧?? 7 !1AQ "aq?#2亼」?@BCb卵狁? ?鑱i?J?ㄗ?F@=?猐 ;Ff訃b--4~?M[p吞懣鵺,N摱??K犤??鍾貨?>漿Y??輟椰僬攛鱸G挽,qk?覗B? BSt唷蜒?HN崆凵Cz:l驔 [k忳庠~S@鉚P顎⒁в8峜部g ??繇烴%喣G噚?up嚀oX黷X晴`G??!踕趞撾K鮚vw?諸言w?冢c?SAG隯汁bk鏤鏡c?鏡囑qゃA:Z;杕柕蓭痰蓮痰f>w骯e隊b?Q?G?+*禦1?詇v暕LfT#卞=<&:L6憝;淋Qf?S?=萑瓚p櫈魀鳫}y3濵#d2硱?u? < !1AQa?q亼」裂 "2B採3R#04@brs? ?鯥 T脺袰)S罳?i熻譔烡?栧\插[W??ZBV* G驍爛I?榆爎ye4kg鉕}<釭H;&籑▉QP +L孠虻蝪? -----------------------------7dc16ab30822-- |
能夠看到,POST帶附件請求的抓包特徵爲:請求頭信息中Content-Type:的值中包含multipart/form-data,而且請求體中,不一樣的參數經過「------------------------請求id」間隔開。
POST請求帶附件跟帶鍵值對是相似的,只有參數複雜的時候須要重置頭信息,這裏也是不須要重置的。
首先配置MAPP路由:
<!-- 保存任務post帶附件 --> <forward pattern="/app/template/action/taskManagerAction.jsp\?handler=save" path="mTaskManager.jsp"/> |
在處理的JSP文件mTaskManagerAction.jsp中模擬請求,執行結果會在服務端生成臨時文件,能夠從臨時文件中揀選數據。代碼以下:
<%@ page language="java" import="java.util.*,com.fiberhome.bcs.appprocess.common.util.*" contentType="application/uixml+xml; charset=UTF-8" pageEncoding="UTF-8"%> <%@ include file="/client/adapt.jsp"%>
<%-- 提交保存操做 --%> <aa:http id="taskManagerAction"/>
<%-- 根據響應結果進行提示 --%> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.nj.fiberhome.com.cn/exmobi.dtd"> <html type="alert"> <body> <alert title="提示" icontype="alarm"> <%-- 爲了簡化說明,這裏僅做一個提示,通常表單提交完能夠作一些相應的處理 --%> <%-- 響應內容能夠看下臨時文件,也是一個json字符串,若是包含success則提交是正確,不然認爲失敗 --%> <msg><%=aa.regex(".*", "taskManagerAction").indexOf("success")>-1?"保存成功!":"保存失敗!"%></msg> </alert> </body> </html> </html> |
在請完畢後,經過響應內容判斷是否提交正確,效果以下:
POST請求帶非鍵值對是目前WEB開發中比較流行的,其實現方式一般爲AJAX。可是對於ExMobi中間件實現,能夠經過在客戶端發起FORM請求,而後在JSP中重置爲AJAX請求。
回到5.1.3.3的請求結果,能夠看到第二個listitem的點擊效果是觸發一個JS提交一個表單:
<form id="form" action="http://oa/app/template/action/taskManagerAction.jsp?handler=list&dataType=xml&timeStamp=1340797760522" method="post"> <input type="hidden" name="page" value="1"/> <input type="hidden" name="rows" value="5"/> </form> |
咱們的目標是在JSP把POST提交帶鍵值對的參數變換成非鍵值對的請求體。
下面咱們來經過抓包特徵分析爲何要經過表單來作。
5.1.5.1中提到的請求的格式,咱們經過抓包看一下。
首先先看一下PC這一部分的效果:
其對應的抓包爲:
POST /app/template/action/taskManagerAction.jsp?handler=list&dataType=xml&timeStamp=1340951115290 HTTP/1.1 Accept: application/xml, text/xml, */*; q=0.01 Content-Type: multipart/form-data X-Requested-With: XMLHttpRequest Referer: http://miap.cc:1001/app/template/jsp/listTask_xml.jsp Accept-Language: zh-cn Accept-Encoding: gzip, deflate User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0) Host: miap.cc:1001 Content-Length: 47 Connection: Keep-Alive Cache-Control: no-cache Cookie: JSESSIONID=4036B744C5E165D246C42B4334895FAD.worker1
<request><page>1</page><rows>5</rows></request> |
能夠看到,提交的請求體爲一個XML格式的數據。而通常非鍵值對的請求體常見還有JSON或者一段文本內容等。
雖然咱們在WEB上看到的是點擊一個連接,沒有抓包前可能會首先認爲是一個GET請求,單其實是一個POST請求。因此咱們就不能直接用href去觸發一個URL,而是須要創建一個表單。
能夠看到客戶端代碼中Form表單裏的兩個參數page和rows的值對應的就是XML中兩個數字,那咱們的目標很明確,須要把鍵值對的參數轉換成XML字符串。
首先配置MAPP路由:
<!-- 列表展現post帶XML請求體 --> <forward pattern="/app/template/action/taskManagerAction.jsp\?handler=list&dataType=xml.*" path="mTaskManagerListXML.jsp"/> |
而後在mTaskManagerListXML.jsp中重組請求信息,JSP模擬AJAX就是給<aa:content>標籤填充請求體的內容,這時候<aa:param>的默認值自動失效。也就是說在改變請求信息前<aa:http>會以鍵值對的形式提交參數的,可是通過在<aa:content>裏設置請求體後,不會以鍵值對的形式提交參數,而是把<aa:content>裏的內容做爲請求體提交,執行結果會在服務端生成臨時文件,能夠從臨時文件中揀選數據。代碼以下:
<%@ page language="java" import="java.util.*,com.fiberhome.bcs.appprocess.common.util.*" contentType="application/uixml+xml; charset=UTF-8" pageEncoding="UTF-8"%> <%@ include file="/client/adapt.jsp"%>
<%-- mimetype是強制轉換aa:http請求的響應內容格式,因爲在抓包的時候能夠看到響應內容就是標準XML因此強制轉換爲XML格式 --%> <%-- 能夠嘗試去掉看下是什麼效果(可能會致使XML結構亂掉) --%> <aa:http id="taskManagerListXML" mimetype="text/xml"> <%-- 該頭信息的設置是由於抓包的實際content-type爲multipart/form-data;而通常form表單的提交,默認content-type爲application/x-www-form-urlencoded,因此須要重置。固然,若是直接在form表單設置enctype屬性值爲multipart/form-data也是能夠的 --%> <aa:header name="Content-Type" value="multipart/form-data"/> <%-- 非鍵值對的請求正文一概放在aa:content中,當存在該標籤的時候aa:param鍵值對參數自動失效。若是不寫該標籤的話,就本次aa:http請求而言是經過鍵值對的方式page=1&rows=5的方式提交的,將與抓包看到的內容不符,因此須要重置請求正文 --%> <aa:content><request><page><%=aa.getReqParameterValue("page") %></page><rows><%=aa.getReqParameterValue("rows") %></rows></request></aa:content> </aa:http> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.nj.fiberhome.com.cn/exmobi.dtd"> <html> <head> <meta charset="UTF-8"/> <title>列表:post爲XML</title> </head> <body> <aa:for-each dsId="taskManagerListXML" var="item" xpath="//item"> <listitem type="twoline" caption="<%=aa.xpath("./title", "item") %>" sndcaption="<%=aa.xpath("./executor", "item")+"|"+aa.xpath("./update_time", "item") %>"/> </aa:for-each> </body> </html> |
客戶端效果以下:
模擬請求的本質是「要什麼給什麼」,也就是說經過瀏覽器訪問的實際請求包裏包含哪些內容,咱們在JSP裏模擬的時候就設置哪些內容。
因此客戶端提交的數據未必就是最終提交的數據,而是能夠靈活的在JSP中經過重置<aa:http>、<aa:header>、<aa:param>、<aa:content>中的url、method、頭信息、參數、請求體等關鍵信息達到真實模擬實際請求的效果從而獲取第三方系統的正確響應。
對於簡單的數據,儘可能作到在客戶端提交的就是實際的數據,對於複雜的數據能夠放到JSP裏面進行重置。
可是無論怎樣,若是模擬請求響應錯誤,則須要將模擬請求的抓包和PC瀏覽器的實際抓包進行對比,分析哪些地方不一致形成的模擬失敗,而後重置相應的部分。
有時候須要將屢次請求的數據請求到以後一併格式化輸出爲一個頁面,或者獲得某個數據須要進行多步請求才能獲取到,在JSP進行合併請求能夠處理這些狀況。
所謂合併請求就是在一個JSP中向多個URL發起請求(也有多是數據庫請求等),也就是說在JSP中屢次使用<aa:http>對相應的URL進行請求,並根據其id使用取值標籤或者工具集函數揀選出相應的數據。
下面以信息發送的爲例進行講解。
信息發送主頁面能夠輸入文本和附件做爲內容發送出去,其中附件是單獨一個表單提交的,提交成功將附件名稱和ID傳回給主頁面,在主頁面發送信息的時候實際只提交文本內容和附件的id,而不是附件實體自己。
web頁面以下:
經過臨時文件能夠看到其部分源碼以下:
<html> <head> <title>My JSP 'addTask.jsp' starting page</title> <meta content="no-cache" http-equiv="pragma"/> <meta content="no-cache" http-equiv="cache-control"/> <meta content="0" http-equiv="expires"/> <meta content="keyword1,keyword2,keyword3" http-equiv="keywords"/> <meta content="This is my page" http-equiv="description"/> <link href="../css/base.css" rel="stylesheet" type="text/css"/> <link href="../css/css.css" rel="stylesheet" type="text/css"/> <link href="../css/style.css" media="all" rel="stylesheet" rev="stylesheet" type="text/css"/> <link href="../js/jeasyui/themes/default/easyui.css" rel="stylesheet" type="text/css"/> <link href="../js/jeasyui/themes/icon.css" rel="stylesheet" type="text/css"/> <script src="../js/jeasyui/jquery-1.7.2.min.js" type="text/javascript"/> <script src="../js/jeasyui/jquery.easyui.min.js" type="text/javascript"/> <script src="../js/My97DatePicker/WdatePicker.js" type="text/javascript"/> <script language="JavaScript" type="text/javascript"> function doSend(){ var title = document.getElementById("title").value; var to = document.getElementById("to").value; if(title==""){ alert("請填寫標題!"); return; }else if(to==""){ alert("請填寫接收人!"); return; } document.getElementById("form").submit(); }
function doUpload(){ var upload = document.getElementById("upload").value; if(upload==""){ alert("請選擇附件!"); return; }
document.getElementById("uploadFile").submit(); }
</script> <style type="text/css"> <!-- .atten {font-size:12px;font-weight:normal;color:#F00;} --> </style> </head> <body class="ContentBody"> <div class="MainDiv"> <table border="0" cellpadding="0" cellspacing="0" class="CContent" width="99%"> <tr> <th class="tablestyle_title">發送信息</th> </tr> <tr> <td class="CPanel"> <table border="0" cellpadding="0" cellspacing="0" style="width:100%"> <tr> <td align="left"> <input class="button" name="Submit" οnclick="doSend()" type="button" value="發送"/> <input class="button" name="Submit2" οnclick="window.history.go(-1);" type="button" value="返回"/> </td> </tr> <tr> <td width="100%"> <fieldset style="height:100%;"> <legend>添加信息</legend> <form action="sendInfo.jsp" id="form" method="post" target="frame"> <table border="0" cellpadding="2" cellspacing="1" style="width:100%"> <tr> <td align="right" nowrap="" width="13%">任務標題:</td> <td width="41%"> <input class="easyui-validatebox text" id="title" name="title" required="true" size="40" style="width:250px" type="text"/> <span class="red"> *</span> </td> <td align="right" width="19%">接收人:</td> <td width="27%"> <input class="easyui-validatebox text" id="to" name="to" required="true" style="width:154px" type="text"/> </td> </tr>
<tr> <td align="right" height="120px" nowrap="">內容:</td> <td colspan="3"> <textarea class="easyui-validatebox" cols="80" id="textarea" name="content" required="true" rows="7"/> <input id="fileName" name="fileName" type="hidden"/> <input id="fileId" name="fileId" type="hidden"/> </td> </tr> </table>
</form> <form action="uploadFile.jsp" enctype="multipart/form-data" id="uploadFile" method="post" target="frame"> <table border="0" cellpadding="2" cellspacing="1" style="width:100%"> <tr> <td align="right" height="50px" nowrap="">附件:</td> <td colspan="3"> <input class="easyui-validatebox" id="upload" name="upload" required="true" style="width: 50%;" type="file"/> <input οnclick="doUpload()" type="button" value="上傳"/> </td> </tr> </table> </form> </fieldset> </td> </tr> </table> </td> </tr>
</table> </div> <iframe name="frame" style="display:none;"/>
</body></html> |
能夠看到頁面中有兩個form,分別是提交附件和發送消息的。並且因爲都往一個隱藏的iframe提交因此頁面沒有刷新。
再看一下點擊上傳按鈕的時候的請求抓包:
POST /app/template/jsp/uploadFile.jsp HTTP/1.1 Host: 192.168.4.46:8080 Connection: keep-alive Content-Length: 662 Cache-Control: max-age=0 Origin: http://192.168.4.46:8080 User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.63 Safari/535.7 Content-Type: multipart/form-data; boundary=----WebKitFormBoundarya3634AT1cII3H1PN Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Referer: http://192.168.4.46:8080/app/template/jsp/newInfo.jsp Accept-Encoding: gzip,deflate,sdch Accept-Language: zh-CN,zh;q=0.8 Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3 Cookie: JSESSIONID=182F5AA1C6E2B36638F7343AF31E4B20
------WebKitFormBoundarya3634AT1cII3H1PN Content-Disposition: form-data; name="upload"; filename="鍩硅璇劇▼.txt" Content-Type: text/plain
15
上午 |
以及響應抓包:
<script> parent.document.getElementById("fileName").value = "培訓課程.txt"; parent.document.getElementById("fileId").value = "e1fe1a7f-d5c3-44e3-b4c3-35b0e8f245a9"; </script> |
能夠看出上傳只提交了附件,經過了multipart/form-data帶附件方式提交,提交成功後會給父頁面——也就是發送信息頁面的fileName和fileId兩個隱藏域賦值。
最後再看點擊發送的時候的請求抓包:
POST /app/template/jsp/sendInfo.jsp HTTP/1.1 Host: 192.168.4.46:8080 Connection: keep-alive Content-Length: 144 Cache-Control: max-age=0 Origin: http://192.168.4.46:8080 User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.63 Safari/535.7 Content-Type: application/x-www-form-urlencoded Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Referer: http://192.168.4.46:8080/app/template/jsp/newInfo.jsp Accept-Encoding: gzip,deflate,sdch Accept-Language: zh-CN,zh;q=0.8 Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3 Cookie: JSESSIONID=182F5AA1C6E2B36638F7343AF31E4B20
title=ewqe&to=ewq&content=eqweqw&fileName=%E9%8D%A9%E7%A1%85%EE%86%84%E7%92%87%E5%89%A7%E2%96%BC.txt&fileId=e1fe1a7f-d5c3-44e3-b4c3-35b0e8f245a9 |
以及響應抓包:
<script> alert("發送成功"); </script> |
能夠看出發送信息只是提交一些信息參數,並無帶附件實體,並且是簡單的application/x-www-form-urlencoded表單方式提交。響應內容只是提示發送成功或者失敗。
從上面的場景來看,使用JSP進行合併請求其實就至關於只有一個表單,該表單內容應該包含附件上傳的內容實體,也包含消息發送的基本信息。經過一個大而全的表單提交到JSP後在JSP中將信息從新進行拆分到兩個不一樣的請求中進行模擬提交。
因此關鍵點有兩個:一是組成一個大而全的只有一個form的信息發送頁面;二是在JSP中將表單信息拆分爲兩個請求參數進行模擬提交。
經過上面的分析,下面經過代碼來實現。
首先爲發送信息的頁面配置MAPP路由:
<!-- 新建信息頁面 --> <forward pattern="/app/template/jsp/newInfo.jsp" path="mNewInfo.jsp"/> |
其次也就是在,mNewInfo.jsp中建立一個大而全的表單:
<%@ page language="java" import="java.util.*,com.fiberhome.bcs.appprocess.common.util.*" contentType="application/uixml+xml; charset=UTF-8" pageEncoding="UTF-8"%> <%@ include file="/client/adapt.jsp"%>
<aa:http id="newInfo"/>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.nj.fiberhome.com.cn/exmobi.dtd"> <html> <head> <meta charset="UTF-8"/> <title show="false">合併請求</title> <script> <![CDATA[ function doSubmit(){ if(document.getElementById("title").value==""){ alert("標題不能爲空!"); return; }else if(document.getElementById("to").value==""){ alert("接收人不能爲空!"); return; } if(document.getElementsByName("upload")[0].value==""){ document.getElementsByName("uploadUrl")[0].valu = ""; } document.getElementById("form").submit(); } ]]> </script> </head> <header> <titlebar caption="返回" title="信息發送" rcaption="發送" riconhref="doSubmit()" iconhref="script:close"/> </header> <body> <%-- form的action屬性寫了一個虛擬地址,這個地址是不存在的,只是爲了給mapp路由作一個惟一URL進到指定的JSP中處理 --%> <form id="form" action="http://oa/app/allInOne.jsp" method="post" target="_blank" enctype="multipart/form-data"> <%-- 包含有input的table就是登錄須要信息,對該表格tr作循環讀取 --%> <aa:for-each dsId="newInfo" var="node" xpath="//fieldset//td/node()[name()!='span' and not(@onclick)]"> <aa:choose> <%-- 普通文字照常輸出,可是若是所有是空格則不輸出 --%> <aa:when testxpath="name()='' and string-length(normalize-space(.))>0" dsId="node"> <font color="#ff0000" style="width:30%;"><aa:value-of xpath="." dsId="node"/><aa:value-of xpath="" dsId="node"/></font> </aa:when> <%-- 普通textarea照常輸出 --%> <aa:when testxpath="name()='textarea'" dsId="node"> <textarea name="<%=aa.xpath("./@name", "node")%>" id="<%=aa.xpath("./@id", "node")%>" style="width:70%;"></textarea> </aa:when> <%-- 普通hidden照常輸出,可是不設置寬度 --%> <aa:when testxpath="@type='hidden'" dsId="node"> <input type="<%=aa.xpath("@type", "node")%>" id="<%=aa.xpath("@id", "node")%>" name="<%=aa.xpath("@name", "node")%>"></input> </aa:when> <%-- 普通input照常輸出 --%> <aa:when testxpath="name()='input'" dsId="node"> <input type="<%=aa.xpath("@type", "node")%>" id="<%=aa.xpath("@id", "node")%>" name="<%=aa.xpath("@name", "node")%>" style="width:70%;"></input> </aa:when> </aa:choose> </aa:for-each> <%-- 把兩個表單的提交地址做爲隱藏域提交,能夠在JSP中獲取分別進行請求 --%> <input type="hidden" name="formUrl" value="<%=aa.xpath("//form[@id='form']/@action", "newInfo")%>"/> <input type="hidden" name="uploadUrl" value="<%=aa.xpath("//form[@id='uploadFile']/@action", "newInfo")%>"/> </form> </body> </html> |
咱們給form的action虛擬了一個提交的地址http://oa/app/allInOne.jsp,而把實際的兩個請求地址放在隱藏參數中formUrl(發送信息)和uploadUrl(上傳附件)。須要注意的是form必需要設置enctype爲multipart/form-data,不然附件沒法提交。其餘信息的參數和文件控件都放置在form中。在提交的時候判斷若是沒有選擇附件,則把uploadUrl的值置空,主要是爲下一步處理作準備,該值爲空的話就不調用附件上傳的請求了。該頁面只有一個發送按鈕,沒有上傳按鈕。其界面以下:
發送頁面組好後點擊發送按鈕就會將表單頁面提交到虛擬地址,因此給虛擬地址配置MAPP以下:
<!-- 發送信息頁面 --> <forward pattern="/app/allInOne.jsp" path="mAllInOne.jsp"/> |
這一步的關鍵就是在mAllInOne.jsp中將表單數據進行篩選後分別進行提交。第一步是先把附件提交,從響應結果中獲取到fileName和fileId而後在第二步提交的時候設置到請求體中,代碼以下:
<%@ page language="java" import="java.util.*,com.fiberhome.bcs.appprocess.common.util.*" contentType="application/uixml+xml; charset=UTF-8" pageEncoding="UTF-8"%> <%@ include file="/client/adapt.jsp"%> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.nj.fiberhome.com.cn/exmobi.dtd"> <% String base = "http://oa/app/template/jsp/"; String uploadUrl = aa.getReqParameterValue("uploadUrl"); String formUrl = aa.getReqParameterValue("formUrl");
//先判斷是否有附件,若是有附件則先進行附件提交,並將獲取到的文件名和id保存到變量中 //若是沒有上傳附件,則文件名爲空串,不然就會有文件名,長度大於0 if(aa.getReqAttachName("upload").length()>0){ %> <aa:http id="uploadFile" url="<%=base+uploadUrl %>"/> <% } /* 若是有附件提交,則會返回fileName和fileId,下面的賦值就至關於模擬抓包到的響應內容 <script> parent.document.getElementById("fileName").value = "培訓課程.txt"; parent.document.getElementById("fileId").value = "e1fe1a7f-d5c3-44e3-b4c3-35b0e8f245a9"; </script> */ String fileName = aa.regex("fileName\"\\).value = \"([^\"]*)\"", "uploadFile"); String fileId = aa.regex("fileId\"\\).value = \"([^\"]*)\"", "uploadFile"); %> <%-- 一個JSP中屢次使用aa:http若是不設置參數,則從客戶端過來的請求體都會被提交,也就是說一個JSP中多個默認aa:http的請求都是同樣的,url、method、參數等都徹底同樣 能夠經過設置keepreqdata="false"至關於把aa:http請求清零,因此參數所有從新設置,組成一個全新的請求 ---%> <aa:http id="sendInfo" keepreqdata="false" url="<%=base+formUrl %>" method="post"> <% //得到原先提交的參數 Map<String, Object> map = aa.getReqParameters(); //原先在頁面中提交的fileName和fileId是hidden的隱藏域,沒有賦值,這裏是將前面提交附件獲取到的文件名和id賦值給輸入框對應的參數 map.put("fileName", fileName); map.put("fileId", fileId); //去掉輔助/沒必要要的參數(有時候不去掉問題也不大) map.remove("upload"); map.remove("formUrl"); map.remove("uploadUrl"); //從新拼接提交的參數 for(String s : map.keySet()){ %> <aa:param name="<%=s %>" value='<%=map.get(s).toString()%>' /> <% } %> </aa:http> <html type="alert"> <body> <alert title="提示" icontype="alarm"> <msg> <aa:choose> <aa:when testxpath="//script[contains(. , '成功')]" dsId="sendInfo"> 發送成功! </aa:when> <aa:otherwise> 發送失敗! </aa:otherwise> </aa:choose> </msg> </alert> </body> </html> |
咱們知道經過客戶端提交的數據在JSP可直接經過<aa:http>進行原樣提交,可是JSP裏的兩次請求是不一樣的,因此須要在<aa:http>中設置屬性keepreqdata爲false,意思就是不按照客戶端的請求體進行請求,而是發起一個全新的請求。這時候須要本身去設置請求的一些參數,好比mAllInOne.jsp裏的第二個請求(id爲sendInfo)。
通過該JSP請求後的結果爲:
若是把第二個請求的keepreqdata去掉,能夠看到提交是不成功的:
合併請求的使用場景主要有兩種:
第一種是本節示例場景,即完成一個操做,可能須要多個請求才能完成,它是強制性的,須要多少個請求都必須執行。
第二種是頁面數據集成來源自多個請求,而這些請求自己不必定有任何關係,那麼能夠在一個JSP中進行屢次請求,這種場景一般是爲了優化業務須要。
好比:登錄後的首頁一般會有不少信息,好比待辦的個數、重要數據的列表展現等,以下圖:
這裏面的信息就來自於多個頁面的信息,人爲的糅合在一塊兒,自己他們之間未必有關係。
AJAX是目前比較流行的WEB開發模式,在ExMobi應用開發中也是很重要的一種方式。
AJAX請求的特色主要有兩點:無狀態請求和無需配置路由。
在5中提到的幾種常見的抓包請求,對於AJAX其實就是一種,處理方式都是同樣。
從請求的抓包看,抓包四個關鍵部分:請求地址、請求方法、請求頭信息、請求體信息,任何一種請求都具有這四個元素。而AJAX就提供了這幾部分的數據設置。
因此只須要把對應的抓包信息設置好,AJAX就能正確發起。
那麼,在客戶端中要想發起一個AJAX請求必須先new一個AJAX對象,好比:
var ajax = new Ajax(url,method,data,onSuccess,onFail,requestHeader,isShowProgress); ajax.send(); |
其中:
n url爲請求的地址。
n method爲請求的方法。
n data爲請求體,鍵值對和非鍵值對都是支持的,這點很關鍵。
n onSuccess是一個函數,當AJAX取到正常響應後會調用該函數進行下一步處理。
n onFail是一個函數,當AJAX沒有取到正常響應客戶端會調用該函數進行處理。
n requestHeader爲請求頭信息。
n isShowProgress代表是否顯示在客戶端顯示進度條。
能夠看出該方法中跟抓包請求關聯的有四個:url、method、data和requestHeader。下面的圖示能夠了解抓包信息如何對應設置到AJAX對象的參數中。
|
請求方法 |
|
請求體信息 |
Ajax( |
url |
method |
data |
onSuccess |
onFail |
requestHeader |
isShowProgress); |
通過客戶端發起的AJAX請求到達服務端後,若是不配置MAPP路由的話服務端也會發起默認的請求。
對於那些響應格式直接爲JSON或者XML等能夠方便經過JS操做的請求無疑能夠省去配置路由和編寫JSP的工做。
可是若第三方響應的不是JSON或者簡單XML(主要指不帶命名空間的XML)仍強烈建議通過JSP進行數據揀選後返回JSON或者簡單XML。
以ajaxdemo應用爲例,從新把http://miap.cc:1001/app/template/login.jsp的登錄提交採用AJAX請求實現。
建立一個名爲ajaxdemo的應用,以下:
注意homepage這裏設置了一個本地頁面做爲登陸頁面。若是一個頁面的內容是相對固定的,實際上是能夠經過創建一個本地頁面來展現,而不須要採用動態獲取的方式展示,這樣既減小了沒必要要的網絡請求,也使展示效率有較大的提高。
同時,繼續配置mapp路由:
<?xml version="1.0" encoding="UTF-8" ?> <maxml version="2.0" xmlns="http://www.nj.fiberhome.com.cn/map" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.nj.fiberhome.com.cn/map maxml-2.0.xsd"> <config> <htmlformat wellformat="true" /> <!-- 配置僞域名 --> <domain address="miap.cc:1001" name="oa"/> </config> <route baseaddr="http://oa">
</route> </maxml> |
上一節中咱們已經建立好應用,接下來就須要建立login.xhtm頁面來展示登錄效果。
通過分析,登錄頁面主要有用戶名和密碼兩個元素,因此咱們在頁面中須要構造這兩個內容,而且提供一個button按鈕點擊後進行AJAX請求,代碼以下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.nj.fiberhome.com.cn/exmobi.dtd"> <html> <head> <meta charset="UTF-8"/> <title>ajax登錄</title> <script> <![CDATA[ function doSubmit(){ //設置請求的URL var url = 'http://oa/app/template/checkLogin.jsp'; //設置請求的方法method var method = 'post'; //設置提交的參數,這裏是鍵值對的參數 var username = document.getElementById('username').value; var password = document.getElementById('password').value; var data = 'username='+username+'&password='+password; //頭信息以json格式存放,鍵值對的參數頭信息一般爲application/x-www-form-urlencoded var contentType = '{"Content-Type": "application/x-www-form-urlencoded"}'; //是否顯示阻塞進度條 var isShowProgress = true; //構造AJAX函數 var ajax = new Ajax(url, method, data, doSuccess, doFail, contentType, isShowProgress); //設置傳給回調函數的參數 ajax.setStringData('username', username); //發送AJAX ajax.send(); }
//ajax的回調函數(成功和失敗回調均有)有一個默認的參數,經過該參數能夠得到ajax請求的響應結果 function doSuccess(data){ //因爲返回的內容是JSON字符串,能夠直接轉爲JSON對象更適合JS操做 //登錄成功響應爲{"status":"success"},登錄失敗響應爲{"status":"用戶名或密碼錯誤!"} var result = eval('('+data.responseText+')'); if(result.status=='success'){ //若是登錄成功,則取出以前傳過來的username做爲提示信息 alert('歡迎您:'+data.getStringData('username')+'!'); }else{ alert(result.status); } }
function doFail(data){
}
]]> </script> </head> <body>
<!-- 構造登錄元素:用戶名 --> <font color="red" style="width:30%">用戶名:</font> <input type="text" id="username" name="username" style="width:70%"></input>
<!-- 構造登錄元素:密碼 --> <font color="red" style="width:30%">密碼:</font> <input type="password" id="password" name="password" style="width:70%"></input> <!-- 點擊登錄按鈕觸發ajax請求 --> <input type="button" value="登錄" style="width:100%;" onclick="doSubmit()"></input> </body> </html> |
效果以下:
在頁面加載的完畢後,點擊「登錄」按鈕便可調用doSubmit函數發起AJAX請求。AJAX對象中設置信息爲:
n url:http://oa/app/template/checkLogin.jsp。
n method:post。
n data:'username='+username+'&password='+password。
n onSuccess:doSuccess。
n onFail:doFail。
n requestHeader:{"Content-Type": "application/x-www-form-urlencoded"}。
n isShowProgress:true。
設置完參數後調用send方法便可發送AJAX請求。
因爲該請求響應爲一個JSON格式數據,方便直接進行JS處理,因此不須要在MAPP路由中配置JSP處理對數據進行特別的格式化。
當用戶名密碼錯誤的時候提示信息以下:
當用戶名密碼正確的時候會提示「歡迎+用戶名」的字樣,以下所示:
這裏用到了回調函數的傳參,將username做爲參數傳遞,在回調函數中獲取。
第6章節講解的表單提交方式屬於同步模式,這種方式的特色是每次都要新開一個頁面,因此一般返回的內容都是一個HTML頁面。
而AJAX方式是異步模式,即在一個頁面裏發起異步請求,只是改變原頁面的內容,因此返回的不是一個完整的HTML頁面,通常返回的是JSON、XML或者一個XHTML代碼片斷甚至直接是文本。
另外,AJAX提交能夠不一樣配置MAPP而能夠直接對第三方響應的內容進行操做,而表單提交則必須配置MAPP路由使用JSP進行第三方響應內容的操做。
ExMobi的客戶端能夠支持在Form提交中使用AJAX,使得表單提交能夠進行異步處理。
這裏須要說明的是,Form表單的AJAX提交跟AJAX對象請求有個很大的不一樣在於,Form表單的提交比較配置MAPP路由。
下面咱們繼續以登錄爲例,採用Form表單進行AJAX提交。
要使用Form表單提交,很重要的一點是表單控件要置於Form控件中。而login.xhtml頁面中並無把登錄的元素包在Form控件中,因此咱們首先須要構建一個Form。同時,咱們增長一個按鈕來提交表單,代碼以下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.nj.fiberhome.com.cn/exmobi.dtd"> <html> <head> <meta charset="UTF-8"/> <title>ajax登錄</title> <script> <![CDATA[
//AJAX對象提交開始===================================== function doSubmit(){ //設置請求的URL var url = 'http://oa/app/template/checkLogin.jsp'; //設置請求的方法method var method = 'post'; //設置提交的參數,這裏是鍵值對的參數 var username = document.getElementById('username').value; var password = document.getElementById('password').value; var data = 'username='+username+'&password='+password; //頭信息以json格式存放,鍵值對的參數頭信息一般爲application/x-www-form-urlencoded var contentType = '{"Content-Type": "application/x-www-form-urlencoded"}'; //是否顯示阻塞進度條 var isShowProgress = true; //構造AJAX函數 var ajax = new Ajax(url, method, data, doSuccess, doFail, contentType, isShowProgress); //設置傳給回調函數的參數 ajax.setStringData('username', username); //發送AJAX ajax.send(); }
//ajax的回調函數(成功和失敗回調均有)有一個默認的參數,經過該參數能夠得到ajax請求的響應結果 function doSuccess(data){ //因爲返回的內容是JSON字符串,能夠直接轉爲JSON對象更適合JS操做 //登錄成功響應爲{"status":"success"},登錄失敗響應爲{"status":"用戶名或密碼錯誤!"} var result = eval('('+data.responseText+')'); if(result.status=='success'){ //若是登錄成功,則取出以前傳過來的username做爲提示信息 alert('歡迎您:'+data.getStringData('username')+'!'); }else{ alert(result.status); } }
function doFail(data){
}
//表單AJAX提交提交開始===================================== function doFormSubmit(){ document.getElementById('form').submit(); } //表單提交成功回調函數 function onSuccess(data){ var result = eval('('+data.responseText+')'); if(result.status=='success'){//登錄成功分支 alert('登錄成功!'); }else{//登錄失敗分支 alert(result.status); } } //表單提交失敗回調函數 function onFail(data){
} ]]> </script> </head> <body> <!-- form表單的ajax請求關鍵在於設置success屬性(必須)和fail屬性(可選) --> <form success="onSuccess" fail="onFail" id="form" action="http://oa/app/template/checkLogin.jsp" method="post"> <!-- 構造登錄元素:用戶名 --> <font color="red" style="width:30%">用戶名:</font> <input type="text" id="username" name="username" style="width:70%"></input>
<!-- 構造登錄元素:密碼 --> <font color="red" style="width:30%">密碼:</font> <input type="password" id="password" name="password" style="width:70%"></input> <!-- 點擊ajax登錄按鈕觸發ajax請求 --> <input type="button" value="ajax登錄" style="width:50%;" onclick="doSubmit()"></input>
<!-- 點擊表單登錄按鈕觸發表單的ajax提交 --> <input type="button" value="表單登錄" style="width:50%;" onclick="doFormSubmit()"></input>
</form> </body> </html> |
其效果以下:
這時候若是點擊「表單登錄」按鈕,就會提示響應碼5017的信息:
這是提示咱們須要給form的action請求地址配置MAPP路由。
因此,接下來咱們須要給form的action配置MAPP路由,以下:
<!-- form的ajax提交 --> <forward pattern="/app/template/checkLogin.jsp" path="mCheckLogin.jsp"/> |
雖然一樣是登錄的處理,這裏的處理有一個技巧。咱們在介紹頁面抓取集成的時候,登錄提交後的頁面咱們輸出的是XHTML頁面;而本例中咱們響應回來的內容是給JS進行處理的,因此如何可以讓JS操做方即是須要考慮的。
咱們已經知道登錄提交的響應結果爲JSON數據,因此咱們如今只須要把響應結果原樣輸出便可,mCheckLogin.jsp代碼以下:
<%@ page language="java" import="java.util.*,com.fiberhome.bcs.appprocess.common.util.*" contentType="application/uixml+xml; charset=UTF-8" pageEncoding="UTF-8"%> <%@ include file="/client/adapt.jsp"%>
<%-- 發起默認請求便可,由於基本信息已經在form中組好 --%> <aa:http id="checkLogin"/>
<%-- 直接經過正則獲取所有內容(請思考爲何不用xpath?),.*表明所有內容 --%> <%=aa.regex(".*", "checkLogin")%> |
當用戶名密碼錯誤的時候就會提示:
當用戶名和密碼正確的時候就會提示:
Form表單的AJAX請求是不能夠設置回調函數的參數的,因此若是要得到用戶名、密碼等信息,須要從新經過dom對象獲取。
在「頁面抓取集成」章節中,咱們瞭解到了「合併請求」的概念。其實瞭解了AJAX,咱們就能夠很天然的考慮到是否能夠使用表單的AJAX來解決「合併請求」的場景?答案固然是確定的,咱們能夠經過兩個AJAX請求分別處理附件上傳的請求和信息發送的請求。若是上傳了附件,能夠觸發上傳的表單提交,並從響應中獲取到fileName和fileId重置到信息發送的表單中,在信息發送的時候提交。
Web Service是一種構建應用程序的廣泛模型,能夠在任何支持網絡通訊的操做系統中實施運行,是自包含、自描述、模塊化的應用,能夠發佈、定位、經過web調用。各應用程序經過網絡協議和規定的一些標準數據格式(HTTP、XML、SOAP)來訪問Web Service。
Web Service提供一種可被調用的服務,該服務必須經過WSDL定義接口。理論上經過WSDL描述的Web Service能夠有不少種不一樣的綁定(好比:HTTP、RMI、JMS),可是實際上常用SOAP HTTP綁定。
因此Web Service的集成基本原理是模擬SOAP HTTP請求。
Soap UI提供了直接將WSDL導成項目,而且模擬SOAP HTTP請求的功能。Soap UI能夠經過模擬SOAP HTTP請求而不須要編碼便可方便檢測Web Service接口是否正確,而且能夠經過抓包工具抓到該請求的包。
以目標WSDL文件http://miap.cc:1001/app/services/webServiceTest?wsdl爲例說明使用方法:
SoapUI主界面以下:
點擊菜單「File」,選擇「New WSDL Project」,以下圖:
彈出新建的面板,粘貼WSDL地址到指定地方,以下圖:
點擊「OK」後就會「loading wsdl」,加載成功便可成功建立一個WSDL工程,以下圖:
在請求參數區域的「username」填寫「admin」,「password」填寫「111」,而後點擊「發送請求」按鈕,便可在響應數據區域看到響應數據(切換到「XML」視圖),以下圖所示:
打開抓包工具,從新進行8.1中的操做,也就是發送Web Service請求,能夠看到以下請求信息:
POST /app/services/webServiceTest HTTP/1.1 Content-Type: text/xml;charset=UTF-8 SOAPAction: "" User-Agent: Jakarta Commons-HttpClient/3.0.1 Host: miap.cc:1001 Content-Length: 664
<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://server.webservice.app.fh.com"> <soapenv:Header/> <soapenv:Body> <ser:checkUserIsExist soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <username xsi:type="soapenc:string" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">admin</username> <password xsi:type="soapenc:string" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">111</password> </ser:checkUserIsExist> </soapenv:Body> </soapenv:Envelope> |
以及響應信息:
HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Content-Type: text/xml;charset=utf-8 Transfer-Encoding: chunked Date: Sat, 27 Oct 2012 16:06:32 GMT
<?xml version="1.0" encoding="UTF-8"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><soapenv:Body><ns1:checkUserIsExistResponse soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="http://server.webservice.app.fh.com"><checkUserIsExistReturn xsi:type="soapenc:string" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"><?xml version="1.0" encoding="UTF-8"?> <response><status>login success,welcome admin</status></response></checkUserIsExistReturn></ns1:checkUserIsExistResponse></soapenv:Body></soapenv:Envelope> |
SOAP HTTP抓包特色是:
1) 請求方法爲POST。
2) 請求頭中固定有兩個Content-Type: text/xml;charset=UTF-8和SOAPAction: ""必需要設置。注意SOAPAction的值通常至少包含一對引號,引號中能夠有一些特定的值。
3) 請求體是一個帶命名空間的XML,格式固定不變,某些屬性值或者文本值須要動態設置。
4) 響應體也是帶命名控件的XML,格式固定不變。
下面咱們經過兩種開發模式介紹如何進行Web Service集成。
咱們能夠看到,提交的請求體內容
同步模式模擬的思路是在客戶端把動態參數做爲表單項可進行修改,而後在處理的JSP中將這些參數拼接成最終提交的XML請求體。
在客戶端構造代碼以下:
<!-- 由於ws --> <form id="form" action="http://domain/app/services/webServiceTest" method="post"> <input type="text" name="username" value="admin" prompt="請輸入用戶名"/> <br/> <input type="password" name="password" value="111" prompt="請輸入密碼"/> <br/> <input type="submit" value="經過form登錄" style="width:50%"/> <input type="button" value="經過ajax登錄" onclick="doAjax()" style="width:50%"/> </form> |
頁面效果以下:
點擊「經過form登錄」會將請求信息發送給服務端。
MAPP路由配置一個處理的JSP文件,以下:
<!-- web service --> <forward pattern="/app/services/webServiceTest" path="webservice.jsp"/> |
在webservice.jsp中將請求重組,關鍵設置兩個頭信息(Content-Type和SOAPAction)和重組請求體爲XML。通過<aa:http>後會在服務端生成臨時文件,而後根據臨時文件揀選數據,代碼以下:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8" contentType="application/uixml+xml; charset=UTF-8"%> <%@ include file="/client/adapt.jsp"%> <aa:http id="ws"> <aa:header name="Content-Type" value="text/xml;charset=UTF-8"/> <aa:header name="SOAPAction" value="\"\""/> <aa:content> <soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://server.webservice.app.fh.com"> <soapenv:Header/> <soapenv:Body> <ser:checkUserIsExist soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <username xsi:type="soapenc:string" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"><%=aa.getReqParameterValue("username") %></username> <password xsi:type="soapenc:string" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"><%=aa.getReqParameterValue("password") %></password> </ser:checkUserIsExist> </soapenv:Body> </soapenv:Envelope> </aa:content> </aa:http> <!DOCTYPE html SYSTEM "http://www.nj.fiberhome.com.cn/exmobi.dtd"> <html> <head> <meta charset="UTF-8"/> <title>高級課程實例</title> </head> <body id="content"> 登錄信息: <% String msg = aa.xpath("//checkUserIsExistReturn", "ws"); if(msg.indexOf("login success")>-1){ out.println("登錄成功。"); }else{ out.println("登錄失敗。"); } %> </body> </html> |
效果以下:
客戶端自己能夠直接模擬第三方請求,可是因爲Web Service無論是請求體仍是響應體都是比較龐大的XML,因此通常不建議這麼作。
一般的作法也是在AJAX中將動態參數做爲AJAX的data參數提交到服務端在JSP中從新拼接請求體。
在客戶端構造代碼以下,爲了區別上一個請求,咱們在JS發起AJAX請求時給url加了一個特殊標識「?ajax」:
<html> <head> <meta charset="UTF-8"/> <title>web service登錄</title> <script> <![CDATA[ function doAjax(){ var data = "username="+document.getElementsByName("username")[0].value+"&password="+document.getElementsByName("password")[0].value; var ajax = new Ajax(document.getElementById("form").action+"?ajax", "post", data, onSuccess, onError, '{"Content-Type": "application/x-www-form-urlencoded"}', true); ajax.send(); }
function onSuccess(data){ var rs = data.responseText; alert(rs); }
function onError(){ alert("請求錯誤!"); } ]]> </script> </head> <body> <form id="form" action="http://domain/app/services/webServiceTest" method="post"> <input type="text" name="username" value="admin" prompt="請輸入用戶名"/> <br/> <input type="password" name="password" value="111" prompt="請輸入密碼"/> <br/> <input type="submit" value="經過form登錄" style="width:50%"/> <input type="button" value="經過ajax登錄" onclick="doAjax()" style="width:50%"/> </form> </body> </html> |
點擊「經過ajax登錄」便可調用doAjax函數發起ajax,能夠看到JS函數中將表單中的輸入做爲ajax的提交參數。
須要說明的是,因爲咱們模擬的是同一個請求,可是處理的JSP不一樣,因此給URL添加了?ajax做爲標識在MAPP路由的時候能進行區分,實際開發中不須要這麼作。
MAPP路由配置以下,因爲咱們自行給第三方地址加了「?ajax」,主要是爲了區分前面的非AJAX請求的URL,可以讓不一樣的請求走不一樣的路由配置,這是一個處理技巧,因此MAPP的配置中也要加上,注意問號的轉義:
<forward pattern="/app/services/webServiceTest\?ajax" path="webserviceAjax.jsp"/> |
在webserviceAjax.jsp中處理以下:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8" contentType="application/uixml+xml; charset=UTF-8"%> <%@ include file="/client/adapt.jsp"%> <aa:http id="ws"> <aa:header name="Content-Type" value="text/xml;charset=UTF-8"/> <aa:header name="SOAPAction" value="\"\""/> <aa:content> <soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://server.webservice.app.fh.com"> <soapenv:Header/> <soapenv:Body> <ser:checkUserIsExist soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <username xsi:type="soapenc:string" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"><%=aa.getReqParameterValue("username") %></username> <password xsi:type="soapenc:string" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"><%=aa.getReqParameterValue("password") %></password> </ser:checkUserIsExist> </soapenv:Body> </soapenv:Envelope> </aa:content> </aa:http> 登錄信息: <% String msg = aa.xpath("//checkUserIsExistReturn", "ws"); if(msg.indexOf("login success")>-1){ out.println("登錄成功。"); }else{ out.println("登錄失敗。"); } %> |
效果以下圖所示:
數據庫集成是在服務端的JSP中經過在抽取標籤<aa:sql-excute>中編寫SQL腳本操做數據。
目前ExMobi.0版本內置了mysql、postgresql、oracle、mssqlserver、db2數據庫驅動能夠直接使用。
下面均以postgresql數據庫爲例說明。操做其中的tbl_task表,其結構爲:
在MAPP.xml中,根節點<maxml>下的<config>標籤節點增長一個<database>的標籤,該標籤主要是配置數據庫鏈接的基本信息。配置以下:
<?xml version="1.0" encoding="UTF-8" ?> < maxml version="2.0" xmlns="http://www.nj.fiberhome.com.cn/map" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.nj.fiberhome.com.cn/map ../../maxml-2.0.xsd"> <config> <!—數據庫--> <database id="postgresql" dbtype="postgresql" ip="miap.cc" port="1006" dbname="app" user="sitest" password="123456" maxconn="10" minconn="2" defconn="5" /> </config> </ maxml> |
通過配置後,在JSP中就能夠根據<database>的id屬性獲取對相應的數據庫進行操做。
因爲數據庫集成是直接與數據庫對接,實際是沒有URL的,因此爲了能讓客戶端的請求可以走到服務端的JSP進行處理,須要虛擬一個第三方的地址URL。爲了方便,咱們給這個第三方地址統一一個任意的域名,好比爲sql,不一樣的請求爲其命名爲一個jsp文件,因此虛擬的地址相似於http://sql/ query.jsp。這樣就能夠爲這個地址配置JSP,好比:
<route baseaddr="http://sql"> <!-- 查詢操做 --> <forward pattern="/query.jsp" path="dbQuery.jsp"/> </route> |
在客戶端構建一個查詢頁面search.xhtml請求地址爲:http://sql/select.jsp,查詢條件是title。
http://sql/select.jsp是一個虛擬地址。因爲作數據庫集成是沒有URL地址的,爲了可以通過咱們的JSP進行處理,因此須要構造相似的虛擬地址。
代碼以下:
<!DOCTYPE html SYSTEM "http://www.nj.fiberhome.com.cn/exmobi.dtd"> <html> <head> <meta charset="UTF-8"/> <title>數據庫-查詢</title> <script> <![CDATA[
]]> </script> </head> <body id="content"> <form id="form" action="http://sql/query.jsp" method="post" target="_self"> <input type="text" name="title" value="" prompt="請輸入標題"/> <input type="submit" value="查詢" style="width:100%"/> </form> </body> </html> |
客戶端效果以下圖:
咱們給http://sql/select.jsp這個虛擬地址配置一個處理的JSP,以下:
<!-- 查詢操做 --> <forward pattern="/query.jsp" path="dbQuery.jsp"/> |
dbQuery.jsp中須要獲取到URL中傳遞過來的參數title,而後拼接要執行的SQL語句,而後執行後會在服務端生成臨時文件,而後能夠從臨時文件中揀選想要的數據,以下:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8" contentType="application/uixml+xml; charset=UTF-8"%> <%@ include file="/client/adapt.jsp"%>
<% String title = aa.getReqParameterValue("title"); %>
<aa:sql-excute id="selectAll" dbId="postgresql" sql="select * from tbl_task where title like ?"> <aa:sql-param type="String" value='<%="%"+title+"%"%>'/> </aa:sql-excute> <!DOCTYPE html SYSTEM "http://www.nj.fiberhome.com.cn/exmobi.dtd"> <html> <head> <meta charset="UTF-8"/> <title>數據庫-select</title> </head> <body id="content"> <aa:for-each var="list" dsId="selectAll" xpath="//datarow"> <listitem type="twoline" href="http://sql/show.jsp?id=<%=aa.xpath("./datacol[@name='id']", "list")%>" caption="<%=aa.xpath("./datacol[@name='title']", "list")+"|"+aa.xpath("./datacol[@name='executor']", "list") %>" sndcaption="<%=aa.xpath("./datacol[@name='end_time']", "list") %>"/> </aa:for-each>
</body> <footer> <input type="button" onclick="res:page/sql/add.xhtml" value="新增"style="width:50%"/> <input type="button" onclick="http://sql/query.jsp?title=<%=title%>" target="_self" value="刷新" style="width:50%"/> </footer> </html> |
須要注意的是,JSP發起的select查詢語句返回的結果是一個XML,這個XML的一個datarow節點記錄一條符合條件的數據,datarow下面的datacol節點記錄的是該條數據包含的列信息和它的值。全部datarow組成了整個查詢結果。
因此若是要展現查詢的結果,能夠對datarow作循環,而後取裏面的datacol節點的值。
生成的XML文件格式以下:
客戶端查詢效果以下:
在9.2.1.3中已經準備了一個「新增」按鈕,它是一個本地頁面sql目錄下的add.xhtml,其代碼以下:
<!DOCTYPE html SYSTEM "http://www.nj.fiberhome.com.cn/exmobi.dtd"> <html> <head> <meta charset="UTF-8"/> <title>數據庫-insert</title> <script> <![CDATA[ function doSubmit(){ if(document.getElementById("title").value==""){ alert("標題不能爲空!"); return; }else if(document.getElementById("begin_time").value==""){ alert("開始時間不能爲空!"); return; }else if(document.getElementById("end_time").value==""){ alert("結束時間不能爲空"); return; } document.getElementById("form").submit(); } ]]> </script> </head> <body id="content"> <form id="form" action="http://sql/insert.jsp" method="post" target="_self"> 標題: <br/> <input type="text" id="title" name="title" value=""/> <br/> 開始時間:<br/> <object type="date" id="begin_time" name="begin_time" value="" style="width:100%"/> <br/> 結束時間:<br/> <object type="date" id="end_time" name="end_time" value="" style="width:100%"/> <br/> 任務執行人: <br/> <select name="executor" id="executor"> <option selected="selected">==請選擇==</option> <option value="李勇">李勇</option> <option value="陶萬鵬">陶萬鵬</option> <option value="陳文勝">陳文勝</option> <option value="梅璇">梅璇</option> <option value="黃清華">黃清華</option> <option value="鄭桂端">鄭桂端</option> <option value="李偉強">李偉強</option> <option value="潘家華">潘家華</option> <option value="盧雅輝">盧雅輝</option> <option value="黃偉豐">黃偉豐</option> <option value="史長春">史長春</option> <option value="陳麗娟">陳麗娟</option> </select> <br/> 優先級: <br/> <select name="priority_level" id="priority_level"> <option selected="selected">==請選擇==</option> <option value="暫不">暫不</option> <option value="通常">通常</option> <option value="須要">須要</option> <option value="急">急</option> <option value="很急">很急</option> </select> <br/> 任務說明: <br/> <input type="text" name="remark" value=""/> <br/> </form> </body> <footer> <input type="button" onclick="doSubmit()" value="新增" style="width:100%"/> </footer> </html> |
其客戶端效果以下:
點擊「新增」按鈕,會將表單提交到http://sql/insert.jsp這個虛擬地址,因此須要爲該地址配置處理的JSP。以下:
<!-- 插入操做 --> <forward pattern="/insert.jsp.*" path="dbInsert.jsp"/> |
dbInsert.jsp中須要將提交過來的表單內容插入到數據庫表中,代碼以下:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8" contentType="application/uixml+xml; charset=UTF-8"%> <%@ include file="/client/adapt.jsp"%> <% String id = aa.getReqParameterValue("id"); //獲取全部參數MAP映射 Map<String, Object> pmap = aa.getReqParameters(); //插入的列 String set = "id"; //插入的列對應的值,惟一標示id經過UUID自動生成 String values = "'"+UUID.randomUUID().toString()+"'"; if (null != pmap){ //遍歷全部參數,並拼接成SQL片斷 for (String s : pmap.keySet()){ values += ",'"+pmap.get(s).toString()+"' "; set += ","+s; } } String sql = "insert into tbl_task ("+set+") values ("+values+")"; %> <aa:sql-excute id="update" dbId="postgresql" sql="<%=sql %>"/> <html type="alert"> <body> <alert title="提示" icontype="alarm"> <msg><%=aa.xpath("//result", "update").equals("1")?"新增成功!":"新增失敗!"%></msg> <nextaction>script:close</nextaction> </alert> </body> </html> |
須要注意的是,JSP通過insert、update、delete的命令執行後的結果爲一個XML,該XML只有一個節點,即:<result>,該節點下的值若是爲1則說明執行成功,爲-1則說明執行失敗。執行結果會在服務端生成臨時文件,能夠從臨時文件中揀選數據。
該JSP執行後的結果,以下圖所示:
點擊「刷新」按鈕後能夠在列表中看到新數據以下:
在9.2.1.3中已經爲沒一個listitem的href添加了修改的虛擬地址:http://sql/dbShow.jsp?id=參數,經過將id傳遞到JSP中指定能夠修改哪條數據,這個過程其實也是一個select的語句,這裏不作贅述,其處理的JSP爲dbShow.jsp:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8" contentType="application/uixml+xml; charset=UTF-8"%> <%@ include file="/client/adapt.jsp"%> <% String id = aa.getReqParameterValueFromUrl("id"); String sql = "select * from tbl_task where id='"+id+"'"; %> <aa:sql-excute id="selectOne" dbId="postgresql" sql="<%=sql %>"/> <!DOCTYPE html SYSTEM "http://www.nj.fiberhome.com.cn/exmobi.dtd"> <html> <head> <meta charset="UTF-8"/> <title>數據庫-select</title> </head> <body id="content"> <form id="form" action="http://sql/update.jsp" method="post" target="_self"> <input type="hidden" name="id" value="<%=id %>"/> 標題: <br/> <input type="text" name="title" value="<%=aa.xpath("//datacol[@name='title']", "selectOne") %>"/> <br/> 開始時間:<br/> <object type="date" name="begin_time" value="<%=aa.xpath("//datacol[@name='begin_time']", "selectOne") %>" style="width:100%"/> <br/> 結束時間:<br/> <object type="date" name="end_time" value="<%=aa.xpath("//datacol[@name='end_time']", "selectOne") %>" style="width:100%"/> <br/> 任務執行人: <br/> <input type="text" name="executor" value="<%=aa.xpath("//datacol[@name='executor']", "selectOne") %>"/> <br/> 優先級: <br/> <input type="text" name="priority_level" value="<%=aa.xpath("//datacol[@name='priority_level']", "selectOne") %>"/> <br/> 任務說明: <br/> <input type="text" name="remark" value="<%=aa.xpath("//datacol[@name='remark']", "selectOne") %>"/> <br/> </form> </body> <footer> <input type="button" onclick="document.getElementById('form').submit()" value="修改" style="width:50%"/> <input type="button" onclick="http://sql/delete.jsp?id=<%=id%>" target="_self" value="刪除" style="width:50%"/> </footer> </html> |
這個JSP中能夠修改表單的內容,並提交到http://sql/update.jsp這個虛擬地址進行實際的修改。這就是本節中要講解的實例。
須要注意的是,修改數據都是有條件的,這裏將id做爲修改的數據的依據,因此做爲hidden參數不能修改。
點擊第二條數據,其客戶端效果以下:
點擊「修改」按鈕,會將表單提交到http://sql/update.jsp這個虛擬地址,因此須要爲該地址配置處理的JSP。以下:
<!-- 更新操做 --> <forward pattern="/update.jsp" path="dbUpdate.jsp"/> |
dbUpdate.jsp中須要將提交過來的表單內容更新到數據庫表中,代碼以下:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8" contentType="application/uixml+xml; charset=UTF-8"%> <%@ include file="/client/adapt.jsp"%> <% String id = aa.getReqParameterValue("id"); //獲取全部參數MAP映射 Map<String, Object> pmap = aa.getReqParameters(); //修改的鍵值對 String set = ""; if (null != pmap){ for (String s : pmap.keySet()){ //將不爲id的參數拼接起來 if(!id.equals(pmap.get(s).toString())) set += ","+s+"='"+pmap.get(s).toString()+"' "; } } String sql = "update tbl_task set "+set.replaceFirst(",", "")+" where id='"+id+"'"; %> <aa:sql-excute id="update" dbId="postgresql" sql="<%=sql %>"/> <html type="alert"> <body> <alert title="提示" icontype="alarm"> <msg><%=aa.xpath("//result", "update").equals("1")?"修改爲功!":"修改失敗!"%></msg> <nextaction>script:close</nextaction> </alert> </body> </html> |
該JSP經過傳遞過來的id值爲判斷條件對符合id值的記錄進行修改。執行結果會在服務端生成臨時文件,能夠從臨時文件中揀選數據。
此處好比修改告終束時間爲2012-06-30,以下圖所示:
點擊「修改」按鈕後並在列表中「刷新」數據以下:
在9.2.3.1中除了「修改」按鈕,還有準備了一個「刪除」按鈕,經過虛擬地址http://sql/delete.jsp?id=參數指明刪除符合id值的記錄,id是從查詢列表傳遞過來的參數,按鈕代碼以下:
<input type="button" onclick="http://sql/delete.jsp?id=<%=id%>" target="_self" value="刪除" style="width:50%"/> |
其客戶端效果以下:
點擊「刪除」按鈕,會將表單提交到http://sql/delete.jsp這個虛擬地址,因此須要爲該地址配置處理的JSP。以下:
<!-- 刪除操做 --> <forward pattern="/delete.jsp.*" path="dbDelete.jsp"/> |
dbDelete.jsp中須要將數據庫表中id值等於傳遞過來的id值的數據刪除,代碼以下:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8" contentType="application/uixml+xml; charset=UTF-8"%> <%@ include file="/client/adapt.jsp"%> <% String id = aa.getReqParameterValueFromUrl("id"); String sql = "delete from tbl_task where id='?'"; %> <aa:sql-excute id="update" dbId="postgresql" sql="<%=sql %>"></aa:sql> <html type="alert"> <body> <alert title="提示" icontype="alarm"> <msg><%=aa.xpath("//result", "update").equals("1")?"刪除成功!":"刪除失敗!"%></msg> <nextaction>script:close</nextaction> </alert> </body> </html> |
點擊「刪除」按鈕並刷新列表,會發現數據已經不存在,以下圖:
推送功能在移動應用中是比較常見的功能,它主要起到對移動設備用戶的一個提醒做用。
PUSH推送功能是當第三方系統有新消息須要在ExMobi客戶端提醒的時候,由第三方系統主動調用ExMobi服務端的推送接口,ExMobi服務端在推送接口中對第三方系統提交的信息格式化爲客戶端可以識別的推送信息,而後再推送給客戶端來接收和展現推送消息。
要實現推送,關鍵要素有兩個:推送的來源、推送的途徑。
推送的信息通常包括:要推送的內容和推送內容的接收人。
這就跟發送短信很相似,好比A(第三方系統)要發送消息給B(ExMobi客戶端),須要把短信內容(推送消息)和B的手機號碼(接收人)告訴(調用接口)運營商的短信網關(ExMobi服務端),這樣B就能夠接收到A的信息了。
只是在細節上稍有不一樣,發送短信的內容是一些文本或者圖片、視頻等多媒體格式,而推送的內容通常是一個客戶端的本地頁面地址和一些接收參數,以該頁面做爲載體獲取參數進行相應的邏輯處理;發送短信的接收人是手機號碼,而推送的接收人實際上是ESN和clientId。這是由於ESN是移動設備的惟一標識,clientId是客戶端的惟一標識,只有經過ESN和clientId才能夠惟一肯定消息推送給哪一個設備的哪一個客戶端(同一個客戶端能夠安裝在不一樣移動設備)。
這裏面有一個值得思考的問題——推送內容和接收人的來源是什麼?
仍是繼續拿短信來講明,A給B發短信,A做爲發送方應該自己知道發短信的內容,重點是在A怎麼知道目標接收人B的手機號碼,A不可能直接告訴短信網關接收人是某我的的姓名,由於短信網關並不知道這個接收人的手機號碼是多少。這裏說的「來源」就是這個意思。一般要麼B主動給了A,或者A問B甚至是從那些知道B手機號碼的人獲取到的,這就是來源。若是A和B是在同一個企業中,那麼他們的手機號碼可能會存在某個系統中(好比OA的通信錄功能),A就能夠經過B的姓名、部門等信息搜索到B的手機號碼,這也是一種來源。
經過前面的拋磚引玉,推送也是同樣的,一般知道是否有新消息、要推送什麼消息內容的是第三方系統,推送給誰一般第三方系統也會知道(好比用戶名、密碼等用戶信息),可是具體接收人的ESN和clientId是什麼它就未必知道(由於這兩個在ExMobi中才有)。因此這裏還有一個關鍵就是如何可以把ESN和clientId信息與第三方系統中的用戶信息關聯起來。
前面瞭解到無論是在ExMobi的客戶端仍是服務端均可以獲取ESN和clientId(JS或者JSP都有方法獲取)。因此一般的作法就是,當用戶使用ExMobi客戶端登錄的時候,就能夠獲取當前登錄的用戶名、ESN和clientId,把這幾個基本信息經過接口(web service、數據庫等)方式儲存起來。這裏的接口能夠由第三方提供,也能夠存儲於任意的一個能夠獲取的地方。這就相似於上面發短信的例子,A和B的信息在企業的通信錄中都有記錄,互相能夠查詢到。
從前面的例子能夠看出,發送短信的途徑是經過短信網關;而推送信息的途徑是ExMobi的服務端。
ExMobi服務端概念比較抽象,再具體一點其實就是ExMobi服務端提供的推送接口。這個接口跟短信網關同樣,須要接收第三方系統調用接口時傳遞過來的一些跟推送有關的參數,好比:用戶名、ESN、clientId等等信息。
推送開發其實是一個業務邏輯的肯定和實現的過程,這個過程能夠很靈活。這裏提出了一種經常使用的方法作拋磚引玉。
進行推送開發的時候,其實就是要解決關鍵要素的問題及其之間的業務邏輯:
一、 用戶名、ESN、clientId存儲於何處,提供何種接口來存儲,ExMobi什麼時候調用?(解決「來源」問題)
二、 第三方調用ExMobi的什麼接口來觸發推送,傳遞什麼參數給ExMobi,什麼時候調用?(解決「途徑」問題)
一般能夠與第三方協商開放一個公用的數據庫(存在於雙方均可以操做的服務器中),創建用戶信息關聯表。當用戶在ExMobi客戶端登錄成功後,獲取用戶名、ESN和clientId等信息保存到該表中。這樣就完成了第三方系統的用戶信息和ExMobi客戶端信息的關聯。
這裏咱們沿用第9章數據庫集成使用的數據庫信息做爲公用的數據庫,建立表tsbbm_user包含esn、username、clientid三列,其信息以下:
途徑問題的解決一般有兩種方案:
方案一:
因爲tsbbm_user表中已經記錄了用戶名username與esn和clientId的關係,因此當第三方系統在有新的推送消息要推送的時候,只須要告訴ExMobi服務端的推送接口推送內容和接收人的用戶名便可,在接口中再去數據庫中獲取該用戶名對應的esn和clientId。
該方案的好處在於第三方系統改動不大,可是須要ExMobi的推送接口作必定的查詢工做,會致使資源的浪費。也就是說無論用戶有沒有使用ExMobi客戶端登錄成功,都會調用ExMobi接口去推送。很顯然,若是沒有使用過ExMobi客戶端的用戶的推送將不會成功,可是仍然耗費資源。
方案二:
第三方系統能夠經過tsbbm_user表中已經存在的用戶關聯查詢本身系統中該用戶是否有推送消息,若是有的話則調用ExMobi的推送接口。由於已經進行關聯查詢,因此在調用接口的時候不須要傳遞username,只需把消息內容、ESN和clientId等信息傳遞給接口便可。
該方案有針對性的調用推送接口,能夠較大程度的減小資源的浪費,可是對第三方系統改造較大。
實際的開發過程方案毫不止這兩種,應該根據具體的實際狀況肯定合理的方案。
爲了方便講解,此處選擇方案一。
爲了跟前面的內容達到相互照應,咱們這裏仍然經過「來源」和「途徑」來講明如何進行示例的開發。
咱們在第7章ajaxdemo示例的基礎上,先建立一個相似的應用pushdemo,因爲須要調用數據庫存儲用戶關聯信息,因此先增長一個數據庫配置。
因爲此處內容前面章節均有涉及,下面簡單把相關代碼說明一下:
MAPP路由中的配置以下:
<?xml version="1.0" encoding="UTF-8" ?> <maxml version="2.0" xmlns="http://www.nj.fiberhome.com.cn/map" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.nj.fiberhome.com.cn/map maxml-2.0.xsd"> <config> <htmlformat wellformat="true" /> <!-- 配置僞域名 --> <domain address="miap.cc:1001" name="oa"/> <!-- 配置數據庫鏈接參數 --> <database id="postgresql" dbtype="postgresql" ip="miap.cc" port="1006" dbname="app" user="sitest" password="123456" maxconn="10" minconn="2" defconn="5" /> </config> <route baseaddr="http://oa">
<!-- form的ajax提交 --> <forward pattern="/app/template/checkLogin.jsp" path="mCheckLogin.jsp"/>
</route> </maxml> |
應用的homepage地址爲一個本地頁面login.xhtml,以下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.nj.fiberhome.com.cn/exmobi.dtd"> <html> <head> <meta charset="UTF-8"/> <title>ajax登錄</title> <script> <![CDATA[
//表單AJAX提交提交開始===================================== function doFormSubmit(){ document.getElementById('form').submit(); } //表單提交成功回調函數 function onSuccess(data){ var result = eval('('+data.responseText+')'); if(result.status=='success'){//登錄成功分支 alert('登錄成功!'); }else{//登錄失敗分支 alert(result.status); } } //表單提交失敗回調函數 function onFail(data){
} ]]> </script> </head> <body> <!-- form表單的ajax請求關鍵在於設置success屬性(必須)和fail屬性(可選) --> <form success="onSuccess" fail="onFail" id="form" action="http://oa/app/template/checkLogin.jsp" method="post"> <!-- 構造登錄元素:用戶名 --> <font color="red" style="width:30%">用戶名:</font> <input type="text" id="username" name="username" style="width:70%"></input>
<!-- 構造登錄元素:密碼 --> <font color="red" style="width:30%">密碼:</font> <input type="password" id="password" name="password" style="width:70%"></input>
<!-- 點擊表單登錄按鈕觸發表單的ajax提交 --> <input type="button" value="表單登錄" style="width:100%;" onclick="doFormSubmit()"></input>
</form> </body> </html> |
點擊「表單登錄」按鈕會進入JSP中進行處理,這個咱們在下一節中繼續。
ajaxdemo中咱們僅僅是把登錄的信息返回給客戶端進行提示。如今須要解決來源問題,那麼從前面的分析來看,咱們就須要在JSP中根據登錄結果來判斷是否登錄成功,若是登錄成功則操做數據庫,把用戶名username、esn和clientId信息入庫。
下面對登錄提交的處理JSP文件mCheckLogin.jsp進行改造以下:
<%@ page language="java" import="java.util.*,com.fiberhome.bcs.appprocess.common.util.*" contentType="application/uixml+xml; charset=UTF-8" pageEncoding="UTF-8"%> <%@ include file="/client/adapt.jsp"%>
<%-- 發起默認請求便可,由於基本信息已經在form中組好 --%> <aa:http id="checkLogin"/>
<% //直接經過正則獲取所有內容(請思考爲何不用xpath?),.*表明所有內容 String result = aa.regex(".*", "checkLogin"); %> <%-- 判斷是否爲登錄成功 --%> <aa:if test='<%=result.indexOf("success")>-1 %>'>
<% //username是在請求正文中以鍵值對方式存在,因此使用getReqParameterValue獲取 String username = aa.getReqParameterValue("username"); //esn和clientid存在於頭信息中,因此使用getReqHeaderValue獲取 String esn = aa.getReqHeaderValue("esn"); String clientid = aa.getReqHeaderValue("clientid");
//組成sql語句 String sql = "insert into tsbbm_user (username, esn, clientid) values ('"+username+"', '"+esn+"', '"+clientid+"')"; %> <%-- 執行sql --%> <aa:sql-excute dbId="postgresql" sql="<%=sql %>"></aa:sql-excute>
</aa:if>
<%-- 把響應內容回給客戶端 --%> <%=result%> |
這時候咱們若是登錄成功會在數據庫中看到有新紀錄。
客戶端效果:
數據庫信息以下:
途徑問題其實就是ExMobi如何建立接口。
ExMobi的MAPP中經過config根節點-services節點-http-service節點來發布接口,每一個接口都有一個forward轉向配置,設定接口的地址(pattern)和對應處理的JSP(path)。以下:
<services> <!-- http-service用於發佈http接口 --> <http-service> <!-- 每個接口對應一個forward配置, 其中pattern是接口的被調用的名稱,完整的調用地址爲:http://{ip}:{port}/process/notify/{appid}/{pattern} 因此針對此配置,本機的調用接口應該爲http://127.0.0.1:8001/process/notify/pushdemo/itask 調用接口的時候能夠傳遞一些參數,參數能夠在url中也能夠在請求頭或者請求正文中,參數能夠是鍵值對也能夠是非鍵值對 path是當接口被調用時服務端的處理JSP地址,在JSP中就能夠獲取調用接口時傳遞的參數 --> <forward pattern="/itask" path="push/itask.jsp"/> </http-service>
</services> |
配置好接口以後就須要在接口對應的處理JSP中獲取來源來觸發推送。
根據前面的方案一的介紹,第三方系統調用接口的時候傳遞一個用戶名和推送的相關信息,在對應的JSP中經過用戶名獲取其esn和clientid格式化成客戶端識別的格式。假設咱們約定用戶名等推送相關信息咱們經過URL傳遞,好比:
http://127.0.0.1:8001/process/notify/pushdemo/itask?username=admin&title=新任務 |
一般有可能消息的內容是一個表單,那麼可能須要把表單內容做爲參數傳遞,也以給一個URL可以獲取到表單的內容。
itask.jsp頁面就須要獲取到這些參數,並查詢數據庫獲取esn和clientid,其代碼以下:
<%@ page language="java" import="java.util.*,com.fiberhome.bcs.appprocess.common.util.*" contentType="application/uixml+xml; charset=UTF-8" pageEncoding="UTF-8"%> <%@ include file="/client/adapt.jsp"%>
<% //推送接口傳遞的鍵值對參數無論是在url中仍是在請求正文中買都是經過getReqParameterValue獲取 String username = aa.getReqParameterValue("username");//獲取用戶名 String title = aa.getReqParameterValue("title");//獲取title參數
%> <%-- 若是用戶名不爲空則執行sql查詢相關信息,並對信息格式化 --%> <aa:if test="<%=username.length()>0 %>">
<% //組成sql語句 String sql = "select * from tsbbm_user where username='"+username+"'"; %>
<%-- 執行sql查詢 --%> <aa:sql-excute id="select" dbId="postgresql" sql="<%=sql %>"></aa:sql-excute> <%-- 使用直推方式格式化數據,是一種固定格式 --%> <%-- directPushType:app,本應用的直推消息,收到後打開page/task.html頁面 --%> <%-- immediately:是否後續操做馬上執行,1標示該消息的後續操做馬上執行,0標示用戶點擊後執行page:用戶點擊後打開的本地頁面。此參數PC、android有效 --%> <%-- page:directPushType=app時有效,用戶點擊後打開的本地頁面task.xhtml,在該頁面中能夠獲取<aa:push-param>設置的參數--%> <aa:direct-push directPushType="app" title="<%=title %>" titleHead="ExMobi消息" page="page/task.xhtml"> <%-- 設置接收人 --%> <%-- 根據查詢到的信息組成格式化的消息,使用for循環是考慮用戶可能在多個設備登錄,具體視實際場景而定,也能夠給其中一個推 --%> <aa:for-each var="list" xpath="//datarow" dsId="select"> <% //獲取esn和clientid String esn = aa.xpath("./datacol[@name='esn']", "list"); String clientid = aa.xpath("./datacol[@name='clientid']", "list"); %>
<aa:push-receiver esn="<%=esn %>" clientid="<%=clientid %>"></aa:push-receiver>
</aa:for-each>
<%-- 指定直推頁面參數列表,task.xhtml頁面須要的參數,這裏傳遞username和title --%> <aa:push-params> <aa:push-param name="username" value="<%=username %>" /> <aa:push-param name="title" value="<%=title %>" /> </aa:push-params> </aa:direct-push>
</aa:if> |
< aa:direct-push>標籤爲直推標籤,是把第三方傳遞過來的參數格式化爲ExMobi能夠識別的信息格式。
其中:
directPushType是推送的類型,若是取值爲notify則該消息推送到客戶端後僅進行提示,點擊消息無後續處理;當取值爲app時,必須設置page屬性指明一個應用的本地頁面,當消息推送到客戶端後點擊消息能夠打開page指定的頁面,在該頁面中,能夠獲取<aa:push-param>設置的參數。
這裏指定的是task.xhtml頁面。接下來咱們就來編寫,其代碼以下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.nj.fiberhome.com.cn/exmobi.dtd"> <html> <head> <meta charset="UTF-8"/> <title show="false">title</title> <script> <![CDATA[ function doLoad(){ //獲取傳遞的參數 var username = window.getPushParameter("usernmae"); var title = window.getPushParameter("title");
//對獲取的參數進行相應的處理,這裏修改標題內容 document.getElementById("titleBar").title = title;
/* 因爲推送信息不能過多,不少信息是在推送頁面中再獲取的(蘋果的推送通道就限制推送的內容大小) 好比:第三方調用的時候常傳遞的參數可能還有任務的id或者url等信息,是具體狀況而定 好比傳遞一個id,能夠獲取id組成url發起ajax繼續拉取數據,並進行相應的操做 var id = window.getPushParameter("id"); var url = "http://oa/getDetail.jsp?id="+id; var ajax = new Ajax(url ......);
*/ } ]]> </script> </head> <header> <titlebar id="titleBar" caption="返回" iconhref="script:close" title="" hidericon="true"></titlebar> </header> <body onload="doLoad()"> This is the page content. </body> </html> |
在這個頁面中,能夠經過JS函數window.getPushParameter(key);獲取推送接口中<aa:push-param name=」」 value=」」>的參數,其中key對應的是name屬性。
因爲一些推送通道對推送消息的內容大小有限制(好比蘋果的APNS通道),不能傳參太多,因此一般有時候傳遞一些惟一標識做爲參數,在push.xhtml頁面獲取後再次請求更多的信息。
ExMobi的推送功能開發好以後就可讓第三方系統來調用接口或進行測試。
咱們這裏就用瀏覽器來模擬ExMobi推送接口的調用,在瀏覽器中輸入以下地址:
127.0.0.1:8001/process/notify/pushdemo/itask?username=admin&title=這是新任務 |
這裏傳遞了兩個參數username和title,瀏覽器中能夠看到推送接口返回的信息:
這時候客戶端就會看到有推送消息:
點擊「肯定」按鈕,就會打開push.xhtml頁面展現內容:
這就完成推送功能的調試。一樣,第三方系統經過代碼來調用該接口的效果也是同樣的。若是達不到效果多是因爲前面的溝通不順暢,致使一些參數等信息不匹配,須要繼續互相調試。
烽火中間件是集成第三方工具進行轉換附件,預覽查看,有openoffice和永中能夠選擇,openoffice是免費的,永中是收費的,兩種工具中必須裝一個。
(一) 性能比較
轉換方式 |
Openoffice(只能單線程) |
永中(2線程) |
永中(5線程-未忽略網絡請求時間) |
50頁word純文字(166k) |
90089 |
23676 |
13292 |
5頁word帶圖(5M) |
301793 |
67047 |
50890 |
openoffice在轉換一個50頁的word文檔所須要的時間是永中(2線程)的4倍左右、是永中(5線程)的7倍左右。
openoffice在轉化一個5頁帶圖的文檔所須要的時間是永中(2線程)的4倍,永中(5線程)的6倍。
顯然在性能上永中office要優於open office。
(二) 可靠性比較
Openoffice在轉換office的時候,會出現一頁變兩頁,內容沒法轉換,亂碼等現象,可靠性較差。在永中office測試過程當中,出現的機率相對來講小一些。
尤爲在WPSoffice轉化中,永中office的轉化效果遠好於openoffice。
第三方工具openoffice、永中,必須裝一個,下面介紹下二者對於附件預覽的支持程度。
1) 圖片文檔文件類型
bmp;icon;ico;jpg;jpeg;psd;png;gif;tif;tiff;sep;gd
2) 壓縮文檔文件類型
zip;rar;7z;tar;gz;bz2;cab
3) 文本文檔文件類型
txt;bat;sh;xml;html;xhtml;htm;mht;xsl;jsp;ini;inf
doc;docx;ppt;pptx;xls;xlsx;rtf;wps;ett;dps;pdf;html;ceb
doc;docx;ppt;pptx;xls;xlsx;rtf;pdf;ceb;wps;ett;dps
首先須要再Mbuilder的首選項「Preference」中配置第三方服務,以下圖所示:
肯定後須要重啓Tomcat服務。
一、能夠直接寫附件控件,
控制菜單項,「下載|預覽|打開,1表示顯示,0表示不顯示。支持格式以下:
000:無
001:打開
010:預覽
011:預覽 & 打開
100:下載
101:下載 & 打開
110:下載 & 預覽
111:下載 & 預覽 & 打開
代碼示例以下:
<fileset caption="附件"> <item text="開發手冊.chm" options=」111」 href="實際下載地址"/> <item text="java學習教程.ppt" options=」111」 href="實際下載地址"/> </fileset> |
二、也能夠在有連接的地方,配urltype進行預覽、下載、打開,支持以下取值:
normal: 普通打開頁面(默認)
preview: 預覽方式打開
download: 執行下載請求,相似fileset點擊下載菜單
openfile: 下載後直接打開,相似fileset點擊打開菜單
代碼示例以下:
<a href=」」 urltype=」 preview」 >預覽文檔</a> <a href=」」 urltype=」download」 >下載文檔</a> <a href=」」 urltype=」openfile」 >打開文檔</a> |
ExMobi對特殊控件的預覽支持還包括:金格控件加密的文件、書生SEP文件、方正CEB文件、點聚API文件的支持。
其中書生、方正和點聚的文件格式是依賴於原廠商的轉換軟件,因此須要購買相應的受權軟件方能支持。
ExMobi對文檔格式的識別,是經過文檔請求的響應頭信息Content Type來匹配的,以下:
application/msword=doc application/vnd.ms-word=doc application/rar=rar application/pdf=pdf application/vnd.ms-excel=xls application/vnd.ms-powerpoint=ppt application/x-javascript=js application/x-sh=sh application/x-tar=tar application/xhtml+xml=xml application/xml=xml application/zip=zip image/bmp=bmp image/gif=gif image/ief=ief image/jpeg=jpg image/png=png image/tiff=tif image/x-portable-pixmap=ppm text/css=css text/html=html text/plain=txt text/xml=xml text/uixml=xml image/vnd.png=png application/vnd.openxmlformats-officedocument.wordprocessingml.document=docx application/vnd.openxmlformats-officedocument.presentationml.presentation=pptx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet=xlsx wps/doc=wpt wps/ppt=dpt wps/excel=ett application/sep=sep |
若是標準文檔的響應格式不是標準文檔格式,好比一個word文檔響應的不是application/msword 而是text/plain或者編碼錯誤或者文件名亂碼等,那就須要輕質轉換響應的Content Type,讓ExMobi知道實際的文檔格式。
在<aa:http/>請求中增長屬性mimetype爲相應的類型能夠設置請求響應的Content Type,好比:
<aa:http mimetype="application/msword"/> |
這樣該請求的響應結果就強制轉換類型爲doc。
有不少具備附件預覽或者下載的控件,好比img控件、fileset控件、input:button控件都有filename屬性,該屬性爲文件名+後綴名格式。經過該屬性能夠重命名文件名,也能夠指明文件的類型。好比:
<img src="http://domain/a.do" href="http://domain/a.do" urltype="preview" filename="test.jpg"/> |
上面的代碼運行的結果就是點擊這個圖片的時候就會預覽該圖片,預覽的時候圖片的名字被強制命名爲test.jpg,而且content-type強制設置爲image/jpg。
該方法須要給文檔請求地址配置MAPP路由,走jsp進行強制處理。當<aa:http>請求完畢後處理完畢後能夠使用<aa:file-download>進行下載處理或者使用<aa:file-preview>進行預覽。如何肯定當前請求過來是下載請求仍是預覽請求,能夠經過aa.isDownLoad()函數來肯定返回true則爲下載,不然爲其餘方式。
好比:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@ include file="/client/adapt.jsp"%> <aa:http> … </aa:http>
<aa:choose> <aa:when test='<%=aa.isDownLoad()%>'> <aa:file-download filename=」測試模板.doc」/> </aa:when> <aa:otherwise> <aa:file-preview filename=」測試模板.doc」/> </aa:otherwise> </aa:choose> |
這兩個標籤設置filename的結果也是除了重命名文件名,同時也會按照後綴格式從新設置content-type。
JSP的reponse內置對象中自己也能夠設置響應的content-type,好比:
response.setContentType("application/x-tar"); |
第4篇ExMobi安裝部署
應用開發完畢後,若是已經部署好工程版的ExMobi(工程版有爲正式安裝部署的版本,可一鍵安裝),就能夠將應用導出併發布到後臺管理系統,發佈成功後就能夠使用真機安裝客戶端使用。
MBuilder集成開發工具提供了將開發版的應用導出爲發佈版的應用。
在MBuilder中選中要發佈的應用的根目錄,並找到發佈的快捷圖標點擊進入發佈對話框,以下圖所示:
選擇第二項「Export Zip Packpage」指明打包一個要發佈的應用包,這裏是把包保存到桌面,點擊「Finish」後能夠在桌面看到*.zip格式的應用包,以下圖所示:
發佈應用前須要把ExMobi工程版本部署好。將導出的應用上傳到工程版的EMP管理平臺中。如何下載和安裝ExMobi工程版本請訪問ExMobi官網。
下面以培訓服務器部署好的ExMobi工程版環境爲例,說明應用發佈過程。
1) 打開EMP管理平臺,好比:http://miap.cc:1001/,其界面以下:
輸入默認用戶名/密碼:admin/admin111,便可登陸EMP系統。
2) 經過菜單「應用管理」-》「應用版本發佈」-》「新增」,以下圖所示:
打開發布界面,選擇MBuilder生成的*.zip格式的包,並點擊「上傳」,以下圖所示:
3) 上傳完畢後的界面以下圖所示,點擊「下一步」:
4) 進入「應用信息確認」對話框,若是信息無誤,點擊「確認」後便可完成應用發佈,以下圖所示:
應用成功發佈後,便可使用基座客戶端訪問測試,設置ip爲服務器的ip地址,端口默認爲8001,以下圖所示:
配置好後便可使用客戶端下載應用,並使用。
在第一章中上一節講到的是使用基座客戶端測試,若是測試經過後便可到EDN門戶中打包,成爲打包客戶端,做爲開發交付的一部分。
打包客戶端能夠在EDN門戶的「項目打包管理」中進行,以下圖所示:
打包前須要建立項目,而後在項目中建立應用,最後再建立的應用中能夠打包,以下圖所示:
該應用的歷史打包記錄能夠在「歷史打包」中查看,而且能夠在裏面下載已經成功打包的客戶端。
第5篇ExMobi設計與擴展
ExMobi應用中支持主題的使用。而且在集成開發環境MBuilder已經內置了一些主題,在建立應用的時候能夠選擇使用某個模板主題。也能夠開發好一個主題以後在任意ExMobi應用中使用這個主題。
MBuilder中已經內置了一些主題,內置主題的使用方法是在建立應用的時候進行選擇的,以usetheme應用爲例。
首先建立一個usetheme的應用,以下:
接着連續點擊多個「Next」以後,直到進到「選擇模板主題」窗口,以下:
勾選使用模板,而且選好模板和主題,點擊「Finish」便可,以下圖所示:
能夠看出主題目錄下會自動生成剛纔選擇的主題的文件內容。其中cdf.xml文件的id爲skin-colorful:
同時config.xml中也把主題自動配好:
只有選擇了內置主題的狀況才能在建立文件的時候使用模板,由於MBuilder的內置模板設定了一些配套的模板,快速生成界面元素方便開發。本身開發的主題建立的文件只能使用默認模板,頁面內容須要手動書寫,可是主題仍然會生效。
usetheme應用的首地址爲login.xhtml,下面就以該文件爲例說明如何建立文件使用模板。
如上圖所示,在須要建立文件的文件夾中點擊右鍵-「New」-「XHMTL Page Template」便可彈出選擇模板窗口。以下:
這裏能夠看出總共有9個模板頁面,選擇「登錄」模板並點擊「Next」會彈出模板的預覽窗口:
點擊「Finish」便可使用該模板,能夠看到page下建立login.xhtml文件成功:
同步應用後能夠在PC基座模擬器上查看效果:
若是頁面內容須要調整和改動,繼續編輯login.xhmtl文件便可。
除了使用MBuilder中的內置主題,開發者也能夠根據本身的須要開發本身的主題。
開發主題以前須要先了解ExMobi的單位dp,能夠參考附錄16.1。
主題是有主題配置文件cdf.xml和圖片文件組成。圖片的根目錄名爲image,而且與cdf.xml文件同級。主題適用哪一個分辨率就置於那個分辨率下,以下所示:
cdf.xml文件中能夠使用主題內的圖片,引用的方法爲:
theme:圖片路徑 |
其中,圖片路徑爲主題內的image目錄下的文件路徑,從image目錄開始,好比image目錄下的icon子目錄有checkbox_overlay.png圖片,則寫法爲:
theme:image/icon/checkbox_overlay.png |
開發默認主題首先要建立一個測試應用,下面以maketheme應用爲例。
首先建立一個空白應用(不使用MBuilder默認主題),以下:
能夠看到空白應用默認也會有一個主題目錄,可是對應的cdf.xml文件只有沒有具體的配置。開發主題的過程實際就是完善cdf.xml的過程。
cdf.xml文件的節點和層級結構以下:
節點 |
節點描述 |
節點屬性及屬性描述 |
必選項 |
||
cdf |
應用主題配置文件的根節點 |
無屬性 |
是 |
||
應用主題基本信息 |
|||||
id |
應用主題標識,只能由英文字母、數字、下劃線、@、連字符組成。id屬性值與應用主題文件夾名稱必須保持一致 |
無屬性 |
是 |
||
name |
應用主題名稱 |
無屬性 |
否 |
||
description |
應用主題描述 |
無屬性 |
否 |
||
date |
應用主題發佈日期 |
無屬性 |
否 |
||
應用主題開發者信息 |
|||||
vendor |
應用主題開發者信息節點 |
包含屬性:url,email |
否 |
||
url |
應用主題開發者主頁,屬性值可爲空 |
否 |
|||
|
應用主題開發者郵箱地址,屬性值可爲空 |
否 |
|||
應用頁面元素配置信息 |
|||||
setting |
應用頁面元素配置信息節點 |
無屬性 |
是 |
||
attrs |
定義應用中頁面元素的通用屬性。優先級低於控件內部自定義樣式。 |
包含border-color,border-radius,font-size,color,margin屬性 |
否 |
||
border-color |
邊框顏色 |
否 |
|||
border-radius |
邊框弧度 |
否 |
|||
font-size |
文字大小 |
否 |
|||
color |
文字顏色 |
否 |
|||
margin |
控件四周間隔 |
否 |
具體各個控件能夠設置的默認樣式能夠到ExMobi官網下載二次開發手冊瞭解,下面以input:button按鈕控件爲例說明cdf文件的開發過程。
在login.xtml文件中先增長一個按鈕,以下所示:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.nj.fiberhome.com.cn/exmobi.dtd"> <html> <head> <meta charset="UTF-8"/> <title>主題開發</title> <script> <![CDATA[
]]> </script> </head> <body> <input type="button" value="默認樣式的button"/> </body> </html> |
其效果以下:
能夠看到顯示的按鈕樣式是默認的樣式。
如今須要給按鈕增長主題以設置默認樣式,就要先了解按鈕有哪些能夠設置的cdf默認樣式,須要先查閱手冊。
在手冊中找到<input:button>按鈕,能夠看到「CDF設置」的索引,以下:
點擊後便可看到按鈕控件在CDF中能夠設置的默認樣式以下圖所示:
瞭解了能夠設置的樣式,下面就能夠編寫CDF文件,以下所示:
爲了方便查看,主題的目錄和主題的id均設置爲「testtheme」。cdf.xml配置文件內容以下:
<cdf> <id>testtheme</id><!-- 主題的id,用於config.xml中應用該主題 --> <name>測試主題</name><!-- 主題的名稱 --> <description>給按鈕增長默認樣式</description><!-- 主題的基本描述 --> <date>2013-12-12</date><!-- 主題的開發日期 --> <vendor url="http://www.exmobi.cn" email="edn@exmobi.cn"></vendor><!-- 開發者信息 --> <!-- setting能夠配置全部控件的默認屬性以及各個控件的默認樣式 --> <setting> <button overlay="none" overlay-click="none"border-color="#007a57" border-size="0" background-color="#eb9b34" background-click-color="#db7722" border-click-color="transparent" color="white" click-color="white"/> </setting> </cdf> |
在config.xml中使用testtheme的主題,以下所示:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <config theme="testtheme" clientversion="4" scope="client" devicetype="all"> <appid>maketheme</appid> <appname>主題開發</appname> <description></description> <version>1.0</version> <date>2013-12-15</date> <homepage src="res:page/login.xhtml"/> <faultconfig src=""/> <vendor url="" email=""></vendor> <access orientation="port" land="false" network="true" gps="true" camera="true" certificate="true"/> <icon selectedlogo="res:image/selectedlogo.png" main="res:image/main.png" logo="res:image/logo.png"/> </config> |
從新打開login.xhtml頁面就會看到默認效果已經變爲:
這樣就完成了一個控件的默認樣式的開發,其餘控件以此類推。
開發好的主題能夠用於任意的ExMobi應用中。
值須要把主題的整個目錄拷貝到相應的應用中,而後在config.xml中使用該主題便可。
下面繼續在usetheme應用的基礎上改爲使用testtheme的主題。
首先須要把maketheme應用的testtheme的整個目錄原樣拷貝到usetheme應用的主題目錄下,目錄結構同樣,以下所示:
而後編輯config.xml設置theme的值爲testtheme,以下所示:
這樣就完成新主題的使用。
爲了知足更多的應用場景,有時候須要擴展ExMobi的能力。
Native插件提供了這樣一種擴展方式:熟悉原生開發的人員根據ExMobi的接口要求開發插件,嵌入到ExMobi應用中,提供給應用開發人員使用。
ExMobi支持合做夥伴開發Android、IOS Native插件,經過EDN打包可方便集成Native插件。烽火星空會封裝一些經常使用插件供選用,合做夥伴也可開發本身的插件上傳至EDN,打包時選擇需集成插件便可輕鬆生成集成插件版本的Apk安裝包或Ipa安裝包。
下面簡單描述下插件開發流程:
1: EDN上獲取Android/IOS 插件開發示例工程;
2:按照插件開發說明構建Native插件,開發完成生成.jar包/.a包;
3:將.jar包/.a包與工程所需資源壓縮爲zip包;
4:登陸EDN門戶,上傳插件zip包,選擇所需基準版本打包;
5:打包完畢後下載便可獲得包含插件功能的Apk/Ipa安裝包。
開發Native插件的步驟以下:
1) 從Mbuilder中獲取Android工程AppPlugin.zip,以這個工程爲基礎,參考其中的示例進行開發
能夠看到項目內文件結構以下:
每構建一個native插件,均需建立以com.appplugin +.插件名的package包,如上圖所示的
com.appplugin.SmsComponent, 該package包內須要包含子stub包,stub包包含Component.java,
ComponentContext.java, ResManager.java三個固定橋接類,這三個類切記不要修改。
Component.java:組件基類,自定義插件類需繼承於該類,切勿修改;
ComponentContext.java:組件橋接類,切勿修改;
ResManager.java:資源處理類,包含getResourcesIdentifier,getResource兩個函數
int getResourcesIdentifier(Context context, String tag):用於獲取工程內資源,包括圖片,佈局,字符串定義文件等,使用工程內資源時用於代替系統R.layout.xml, R.drawable.icon, R.string.stringname等。
Drawable getResource( String filename):用於讀取ExMobi應用內圖片文件,返回drawable對象。
2) 修改ComponentFactory.java文件,在其中註冊開發的Native插件:
3)開發繼承於Component的插件XXXComponent,實現下列方法
各方法說明以下:
方法 |
說明 |
init |
初始化相關資源 |
release |
釋放相關資源 |
getView |
返回用於顯示的Android View(能夠是LinearLayout,RelativeLayout等容器View) 對於不可見插件,返回null |
set |
設置插件屬性值,此方法會在2處被調用: 1) 頁面中插件xml節點中定義的屬性,會經過此方法設置給插件 2) 頁面中Javascript調用插件的set方法 (Javascript中int會被自動轉換爲String類型傳遞給插件) |
addChildElement |
增長子節點數據,此方法會在1處被調用: 1)頁面中插件xml節點定義了子節點,頁面會調用addChildElement傳遞節點數據給插件,根據子節點數量調用屢次 |
get |
獲取插件的屬性值,此方法會在1處被調用: 1)頁面中Javascript調用插件的get方法 |
call |
調用插件的方法,此方法會在1處被調用: 1)頁面中Javascript調用插件的call方法 |
下圖以CityComponent描述了插件的初始化過程:
4)編譯工程,編譯經過後導出工程jar包,導出工程包時須要注意,只需選中src文件便可,其餘目錄如assets目錄,res目錄,libs目錄導出時切勿勾選,即保證導出的jar包只有插件代碼文件。
SmsComponent的代碼以下:
public class SmsComponent extends Component { private String phonenumber = ""; private boolean attachSignature = false; private String signature = ""; private long sentMsgs = 0; private String onSent = "";
Handler handler;
@Override public void init() { this.phonenumber = "19909900001"; this.handler = new Handler() { @Override public void handleMessage(Message msg) {
} }; }
@Override public void release() {
}
@Override public View getView() { return null; } @Override public void setViewSize(int w, int h){
}
@Override public void set(String name, String value) { if ("attachSignature".equals(name)){ this.attachSignature = "true".equals(value); }else if ("signature".equals(name)){ this.signature = value; }else if ("onSent".equals(name)){ this.onSent = value; }
}
@Override public String get(String name) { if ("phonenumber".equals(name)){ return this.phonenumber; }else if ("attachSignature".equals(name)){ return "" + this.attachSignature; }else if ("signature".equals(name)){ return this.signature; } return null;
}
@Override public String call(String functionName, String param1, String param2, String param3, String param4, String param5, String param6, String param7) { if ("sendSms".equals(functionName)){ // comp.call("sendSms", "今天下午點開會", 10); String content = param1; int delaySeconds = Integer.parseInt(param2); this.sendSms(content, delaySeconds);
return null; }else if ("getSent".equals(functionName)){ return "" + this.sentMsgs; }else{ Log.i("SmsComponent", "ERROR: unsupported function call : " + functionName); return null; }
}
private void sendSms(String content, int delaySeconds){ String smsText = content; if (this.attachSignature){ smsText += this.signature; } smsText += " [發送自" + this.phonenumber + "]";
final String msg = smsText; this.handler.postDelayed(new Runnable(){ public void run(){ Log.i("SmsComponent", msg); Toast.makeText(helper_getAndroidContext(), msg, 5000).show(); sentMsgs++;
if (onSent != null && onSent.length() > 0){ String script = String.format("%s( '%s' )" , onSent, msg); SmsComponent.this.helper_callJsScript(script); }
} }, 1000*delaySeconds);
}
}
|
其中幾個重要的點解釋以下:
>> SmsComponent.this.helper_callJsScript(script);
能夠經過調用Component的helper_callJsScript來調用頁面中的腳本函數。
>> else if ("onSent".equals(name)){ this.onSent = value; }
onSent做爲事件屬性,和普通的屬性沒有區別。
插件事件觸發時,經過onSent保存的函數名稱,拼裝js腳本,進行調用。
CityComponent的代碼以下:
public class CityComponent extends Component {
static class City{ String name; String code; City(String name1, String code1){ this.name = name1; this.code = code1; } }
String icon; String onSelected; List<City> cities = new ArrayList<City>(); IndexedTable mIndexedTable;
@Override public void init() { ResManager.getInstance().init(false, super.helper_getAndroidContext(), super.helper_getAppId()); }
@Override public void release() { this.cities = null; }
@Override public View getView() { if (this.mIndexedTable == null){ initView(); } return mIndexedTable; }
private void initView(){ IndexedTable table = new IndexedTable(super.helper_getAndroidContext()); if (this.icon != null && this.icon.length() > 0){ table.icon = this.icon; }
table.initView();
Collections.sort(cities, new java.util.Comparator<City>() {
public int compare(City o1, City o2) { return o1.code.compareTo(o2.code); }
});
List<Content> list = new ArrayList<Content>(); for (City city : this.cities){ list.add((new Content(city.code, city.name))); } table.setContent(list);
this.mIndexedTable = table; this.mIndexedTable.listener = new OnItemSelectedListener(){
@Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { selectCity(position); }
@Override public void onNothingSelected(AdapterView<?> parent) {
}
}; }
private void selectCity(int index){ if (onSelected != null && onSelected.length() > 0){ City city = this.cities.get(index); String script = String.format("%s( '%s', '%s'); ", onSelected, city.code, city.name); super.helper_callJsScript(script); } }
@Override public void setViewSize(int w, int h) {
}
@Override public void set(String name, String value) { if ("icon".equals(name)){ this.icon = value; }else if ("onSelected".equals(name)){ this.onSelected = value; } }
@Override public String get(String name) { if ("icon".equals(name)){ return this.icon; }else if ("onSelected".equals(name)){ return this.onSelected; } return null; }
@Override public void addChildElement(String tag, Object/*Map<String, String>*/ attributes){ Map<String, String> attrs = (Map<String, String>)attributes; String name = attrs.get("name"); String code = attrs.get("code"); this.cities.add(new City(name, code)); }
@Override public String call(String functionName, String param1, String param2, String param3, String param4, String param5, String param6, String param7) {
return null; }
}
|
其最複雜的函數是initView,在這個函數中,構造Android的View插件,進行初始化。
其它關鍵代碼點:
>> ResManager.getInstance().init(false, super.helper_getAndroidContext(), super.helper_getAppId());
>> imageView.setImageDrawable(ResManager.getInstance().getResource(icon));
初始化圖片文件管理。此處經過ResManager.getInstance().getResource(icon)方法使用ExMobi應用目錄image/下的圖片。
>> public void setViewSize(int w, int h) { }
ExMobi客戶端會自動設置好getView返回的Android View的尺寸。因此通常不須要在setViewSize函數中作什麼操做。只有特殊狀況,須要根據View的大小進行相關處理時,能夠從這個函數獲得View的大小。
l 可否在插件工程中引入第三方jar包
能夠,同普通Android工程同樣導入便可,注意導入jar包須要放置到工程lib目錄裏面。
l 工程目錄結構如何組織
同普通Android工程相似,包含assets,res,libs目錄。
assets:放置打包在apk中的資源,如較小的圖片,較小的音頻文件等,打包時assets目錄下的文件將不作任何處理被打包,不會被編譯爲R.java;
res:放置應用圖片,應用佈局文件,應用文本xml等,放置於res的文件打包時候參與編譯,被編譯爲R.java格式數據。
libs:放置插件工程引用的jar包。
l 插件工程package包結構如何組織?
以com.appplugin(固定) +.插件名(建議採用惟一標識防止與其餘插件重複)的package包,如:
com.appplugin.SmsComponent, 該package包內須要包含子stub包,stub包包含Component.java,
ComponentContext.java, ResManager.java三個固定橋接類。用戶其餘自定義類及package須要放置於com.appplugin(固定) +.插件名包內。以下圖結構:
l 可否使用插件工程中assert目錄下的圖片
能夠,採用android標準讀取方式便可。
如:AssetManager a = getAssets() ;
//fileName爲assets目錄下須要訪問的文件的名稱
InputStream is = a.open(fileName) ;
l 可否使用插件工程中res目錄下的圖片
能夠,但注意資源id不能直接用R.drawable.xxid 引用,須要使用ResManager.java類方法 public int getResourcesIdentifier(String tag)
如:Bitmap originalImage = BitmapFactory.decodeResource(mContext.getResources(), ResManager.getInstance().getResourcesIdentifier("R.id.fiberhomeplugin_gallery_flow"));
l 可否使用插件工程中res/layout目錄下的佈局
能夠,但注意資源id不能直接用R.layout.xxid 引用,須要使用ResManager.java類方法 public int getResourcesIdentifier(String tag)
如:LayoutInflater mInflater = LayoutInflater.from(mContext_);
View currView = mInflater.inflate(
ResManager.getInstance().getResourcesIdentifier("R.layout.fiberhomeplugin_galleryflow_main"), null) ;
l 插件工程assets, res目錄下文件命名有什麼規則
全部文件建議增長特殊前綴避免打包時資源衝突,如fiberhomeplugin_galleryflow_main.xml, fiberhomeplugin_gallery_flow.png。
l 如何獲取ExMobi應用下的圖片
參考CityComponent中ResManager的實現,經過ResManager.getInstance().getResource(「res:/image/XXX.png」)來獲得圖片,注意僅支持res:前綴的應用內圖片。
如:Drawable iconDraw = ResManager.getInstance().getResource("res:/image/mm_contact_title");
l 如何獲得當前界面的Android Context
經過super.helper_getAndroidContext()方法獲取。
l 如何調用ExMobi頁面中的腳本
經過super.helper_callJsScript(script)方法來調用。
l 一個插件工程可否編寫多個插件類
能夠,創建多個package包,按照工程建立說明構建多個插件類便可,以下圖所示,該插件工程包含了
MyHttpComponent(自定義http插件),CityComponent(列表插件),GalleryComponent(滑動容器插件)等多個插件,只需導出一個jar包便可。
l 如何測試
因爲插件須要在EDN打包後才能實際使用,爲了更好的開發測試,提供了native-common.jar測試包,將測試jar導入工程中,能夠很容易針對本身的組件編寫測試用例並進行測試,詳見自測章節描述。
自測Native插件步驟以下:
1)AppPlugin工程libs文件夾下有個native-common.jar,拷貝jar到插件的libs下,文件結構以下:
2)在插件工程中建立一個測試窗口TestActivity,經過這個窗口測試插件view的展現和具體操做,好比插件數據注入,JS方法調用,獲取應用的圖片等
具體結構以下:
3)測試獲取應用圖片:
在手機的SD卡中建立應用資源目錄(Exmobi/apps/newclientapp/image),如圖:
TestActivity中調用插件時設置appID爲newclientapp,設置工程目錄名稱爲Exmobi,圖片路徑爲:res:/image/xxx
TestActivity的代碼以下:
public class TestActivity extends Activity { com.exmobi.applguin.Component com; String comType = "CityComponent"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.requestWindowFeature(Window.FEATURE_NO_TITLE); //建立插件管理 PluginManager com.exmobi.applguin.PluginManager plugin = new com.exmobi.applguin.PluginManager(); /** * 經過插件TYPE加載插件 * @param appId 應用ID 測試用例中能夠寫具體的應用ID,也能夠爲空 * @param type 插件type 插件的type,好比CityComponent */ plugin.loadNativeComponents("newclientapp", comType); /** * 經過插件TYPE建立插件 * * @param type * 插件類型 ,好比CityComponent * @param view * 插件對應EXMOBI VIEW,這裏能夠爲null * @param activity * EXMOBI 窗口 */ com = plugin.createComponentByType(comType, null, this); /** * 設置插件屬性 * @param type 插件類型 ,好比CityComponent */ setNativeComAttributes(comType); //設置工程建立的目錄名稱 com.setProjectName("Exmobi"); //設置測試數據 HashMap<String, String> map = new HashMap<String, String>(); map.put("name", "北京"); map.put("code", "BEIJING"); com.addChildElement("city", map); //調用插件中的init()方法 com.init(); //獲取插件view View nativeCompVew = com.getView(); //將插件view經過TestActivity顯示 this.setContentView(nativeCompVew); }
private void setNativeComAttributes(String type) { com.set("type", type); com.set("id", "city"); com.set("onSelected", "onCitySelected"); com.set("icon", "res:/image/big1.jpg"); } }
|
l 如何模擬控件xml初始載入
調用com.exmobi.applguin.Component類的loadxml()方法,能夠模擬ExMobi頁面控件的加載,如:
l 控件的屬性測試
調用com.exmobi.applguin.Component類的set()和get()方法,能夠測試插件屬性設置是否成功
如:
exmobi調用是先解析XML獲得控件的屬性值,這裏的測試用例省略瞭解析過程,直接設置屬性值
l 控件回調的JS方法測試
調用helper_callJsScript方法測試控件回調的JS方法名和參數設置是否正確
如:
l Exmobi調插件方法測試
調用com.exmobi.applguin.Component類的call方法,具體調用以下:
/** * 插件中的call方法調用 * * @param params * 參數數組 * @return */ public String call() { button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { String[] params = { "addChildElement", "上海", "SHANGHAI" }; com.call(params); } }); return ""; } |
在插件中接收參數,驗證參數是否正確,以下:
@Override public String call(String functionName, String param1, String param2, String param3, String param4, String param5,String param6, String param7) { android.util.Log.d("call", "functionName: " + functionName + ",param1:" + param1 + ",param2:" + param2); if ("addChildElement".equals(functionName)) { HashMap<String, String> map = new HashMap<String, String>(); map.put("name", param1); map.put("code", param2); addChildElement("city", map); return null; } else { return null; } } @Override public void addChildElement(String tag, Object/* Map<String, String> */attributes) { Map<String, String> attrs = (Map<String, String>) attributes; String name = attrs.get("name"); String code = attrs.get("code"); this.cities.add(new City(name, code)); if(mIndexedTable != null) { List<Content> list = new ArrayList<Content>(); for (City city : this.cities) { list.add((new Content(city.code, city.name))); } mIndexedTable.setContent(list); } } |
l 打包的時候須要將測試用到的native-common.jar和TestActivity刪除避免類衝突
1)切換到插件工程文件夾目錄,選中assert文件夾,libs文件夾,res文件夾,生成的plugin.jar文件,添加到zip壓縮文件中,生成插件zip壓縮文件。
2)打開EDN打包頁面,上傳插件zip包,選擇所需基準版本打包,打包完畢後下載便可獲得包含插件功能的Apk安裝包。
l 上傳包包含哪些數據?
包含插件工程中的assert文件夾,libs文件夾,res文件夾,以及插件jar包,以下圖所示。
l 上傳包格式能夠爲rar嗎
不能,必須爲zip格式。
l 安裝包集成時可否加載多個插件zip包
能夠,建多個插件工程,構建native插件完畢後,導出插件jar包,同資源一塊兒壓縮爲zip包提交EDN打包便可。
l 常見打包錯誤說明
Native插件分爲不可見插件、可見插件兩種,兩種插件的XML表示方法、Javascript函數是一致的。
不可見插件通常用於支持某種算法、硬件能力;
可見插件用於提供UI插件,嵌入到應用頁面中。
Native插件用<nativecomponent type=」XxxComponent」 …./>來嵌入到頁面中。
插件具備屬性、方法、事件,一個插件具體能夠調用哪些方法和屬性,是Native插件的開發人員來提供說明的。
下面用兩個例子,分別進行講解:
SmsComponent插件提供發送短信的能力。
SmsComponent爲不可見插件,經過<nativecomponent …/>元素將其嵌入到頁面中,其中type=」SmsComponent」屬性指明瞭其插件類型。
一個頁面中能夠嵌入任意數目的不可見插件。
插件能夠有數據屬性和事件屬性;SmsComponent具備下列屬性:
屬性 |
意義 |
attachSignature |
短信內容是否附加簽名(數據屬性) |
signature |
簽名內容(數據屬性) |
onSent |
短信發送成功時,回調的javascript函數。(這是一個事件屬性,通常是事件觸發時,插件回調的Javascript函數名稱) |
插件的屬性,能夠經過javascript的get、set方法來獲取和修改。
SmsComponent的能力是經過javascript來調用的,點擊「test」按鈕後,觸發test1函數,在test1函數中,調用SmsComponent的 call(‘sendSms’, content, delay)來發送內容。
須要注意的是:調用插件的方法名稱,例如sendSms,是做爲首參數傳遞的。
若是須要更加友好的調用方式,能夠用Javascript進行二次封裝。下面是一個參考的封裝方式:
在短信發送成功後,插件會觸發onSent事件:
下面是界面截圖:
CityComponent提供了選擇某個城市的功能,能夠根據拼音進行排序快速選擇:
CityComponent插件是可見插件,一個頁面中只能有一個。
從上面XML能夠看到,插件還能夠有子節點(根據需求可選)。
CityComponent的屬性以下:
屬性 |
意義 |
icon |
列表左側的png圖標,圖片位置和格式和exmobi應用一致。 |
onSelected |
選擇了一個城市後,觸發的javascript函數 |
選擇城市後,插件回調Javascript函數:
l 如何實現ExMobi頁面至插件的屬性設置?
兩種方式:
1:插件控件構建時經過屬性設置;
如:<nativecomponent id="mysms" type="SmsComponent" attachSignature="true" signature="Fiberhome" />
2:JS獲取插件控件對象設置;
如:
//獲取插件對象
var mysms = document.getElementById("mysms");
//設置signature屬性
mysms.set("signature", "StarrySky");
l 如何實現ExMobi頁面至插件的函數調用?
頁面中JS獲取Native控件對象,經過控件對象call方法調用插件提供函數方法名便可,支持字符串類型多參數傳遞,即call方法第一個參數爲需調用插件方法,後面參數爲方法入參。
如:
ExMobi頁面:
<nativecomponent id="myhttp" type="MyHttpComponent" onSent="onHttpSent"/>
//獲取插件對應的控件對象
var myHttp = document.getElementById("myhttp");
//調用插件sendHttp方法,參數爲"send http request"
myHttp.call("sendHttp","send http request");
Native插件代碼:
@Override
public String call(String functionName, String param1, String param2,
String param3, String param4, String param5, String param6,
String param7) {
if ("sendHttp".equals(functionName)){
String content = param1;
this.sendHttp(content);
return null;
}else{
return null;
}
}
l 如何實現插件至ExMobi頁面的函數調用?
ExMobi頁面經過插件控件屬性設置回調函數名,JS中設置回調函數處理。Native插件類經過MyHttpComponent.this.helper_callJsScript方法調用定義控件屬性定義回調函數名便可,支持字符串類型多參數傳遞。
如:
ExMobi頁面:
<nativecomponent id="myhttp" type="MyHttpComponent" onSent="onHttpSent"/>
function onHttpSent(rsp){
var myHttpRsp = document.getElementById("rspMessage");
myHttpRsp.innerHTML = rsp;
}
Native插件代碼:
//發送完畢需回調ExMobi頁面
if (onSent_ != null && onSent_.length() > 0){
String script = String.format("%s( 'http 請求發送成功,迴應數據:%s' )" , onSent_, strRsp);
MyHttpComponent.this.helper_callJsScript(script);
}
l 如何獲取插件控件表單提交值
經過Component 基類的public void helper_getValue (String value)方法來調用。
如: public void getNativeValue(String value)
{
String value = super.helper_getValue();
}
l 如何設置插件控件表單提交值
經過Component 基類的public void helper_setValue(String value)方法來調用。
如: public void setNativeValue (String value){
super.helper_setValue(value);
}
開發Native插件的步驟以下:
1) 從烽火星空獲取iOS 工程AppPlugin.zip,以這個工程爲基礎,參考其中的示例進行開發
能夠看到項目內文件結構以下圖所示:
其中,XKPlugin_Component.h,XKPlugin_ComponentFactory.h爲默認類,請勿修改。XKPlugin_Test2_ComponentFactory.h繼承於XKPlugin_ComponentFactory.h,命名規則建議爲XKPlugin_+插件工廠類名(惟一標識)+_ComponentFactory,該類對外僅需提供createComponent方法,以下圖所示:
2)修改XKPlugin_Test2_ComponentFactory.mm文件createComponent方法,在其中註冊開發的Native組件,以下圖所示,示例插件工程註冊了ABC_RefreshListComponent(下拉刷新列表插件),ABC_AnimationComponent(滑動容器插件),ABC_SliderMenuComponent(3D菜單插件),SmsComponent(模擬短信發送插件),CityComponent(類通信錄列表插件),MyHttpComponent(第三方http直連插件)。
3)開發繼承於XKPlugin_Component的組件XXXComponent,實現下列方法
各方法說明以下:
方法 |
說明 |
initComponent |
初始化相關資源 |
releaseComponent |
釋放相關資源 |
getView |
返回用於顯示的UIView 對於不可見組件,返回nil |
setViewSize |
告訴組件佔用的空間大小,通常不用處理 對於不可見組件,此方法不會被調用 |
set |
設置組件屬性值,此方法會在2處被調用: 2) 頁面中組件xml節點中定義的屬性,會經過此方法設置給組件 3) 頁面中Javascript調用組件的set方法 (Javascript中int會被自動轉換爲String類型傳遞給組件) |
addChildElement |
增長子節點數據,此方法會在1處被調用: 1)頁面中組件xml節點定義了子節點,頁面會調用addChildElement傳遞節點數據給組件,根據子節點數量調用屢次 |
get |
獲取組件的屬性值,此方法會在1處被調用: 1)頁面中Javascript調用組件的get方法 |
call |
調用組件的方法,此方法會在1處被調用: 1)頁面中Javascript調用組件的call方法 |
下圖以CityComponent描述了組件的初始化過程:
4)在iOS Device模式下編譯工程,會生成的libAppPlugin.a靜態庫文件,以下圖所示。
需選擇IOS Device
Xcode 頂部工具欄,Product->Archive,點擊開始build生成.a包。
Build成功後,能夠看到.a庫已生成,xcode 4.x在Organizer視圖中能夠看到生成的 .a庫,xcode 5.x有些時候會沒法跳轉到Organizer視圖,咱們能夠從下圖所示位置看到生成 .a庫路徑
輸入路徑,在文件管理器可看到生成的.a包
#import "ABC_SmsComponent.h"
@implementation ABC_SmsComponent{ NSString* phonenumber; BOOL attachSignature; NSString* signature; long sentMsgs; NSString* onSent; }
-(void)dealloc{ NSLog(@"ABC_SmsComponent dealloc"); }
-(void) initComponent{ phonenumber = @"135000000001"; }
-(void) releaseComponent{
}
-(UIView*) getView{ return nil; }
-(void) setViewSize:(int)width height:(int) h{
}
-(void) set:(NSString*) name value:(NSString*)value{ if ([@"attachSignature" isEqualToString:name]){ attachSignature = [@"true" isEqualToString:value];
}else if ([@"signature" isEqualToString:name]){ signature = [NSString stringWithString:value];
}else if ([@"onSent" isEqualToString:name]){ onSent = [NSString stringWithString:value]; } }
-(NSString*) get:(NSString*) name{ if ([@"phonenumber" isEqualToString:name]){ return phonenumber;
}else if ([@"attachSignature" isEqualToString:name]){ return [NSString stringWithUTF8String: attachSignature ? "true" : "false"];
}else if ([@"signature" isEqualToString:name]){ return signature; } return nil; }
-(void) addChildElement: (NSString*)tag attributes:(NSDictionary*)attributes{
}
-(NSString*) call:(NSString*)functionName par1:(NSString*)param1 par2:(NSString*)param2 par3:(NSString*)param3 par4:(NSString*)param4 par5:(NSString*)param5 par6:(NSString*)param6 par7:(NSString*)param7{ if ([@"sendSms" isEqualToString: functionName]){ // comp.call("sendSms", "今天下午10點開會", 10); NSString* content = param1; NSString* par2 = param2; int delaySeconds = [par2 intValue]; [self sendSms:content delay:delaySeconds]; return nil;
}else if ([@"getSent" isEqualToString:functionName]){ return [NSString stringWithFormat:@"%ld", sentMsgs];
}else{ NSLog(@"SmsComponent ERROR: unsupported function call : %@" , functionName); return nil; } }
-(void) sendSms:(NSString*)content delay:(int)delaySeconds{ NSString* smsText = content; if (attachSignature){ smsText = [smsText stringByAppendingString:signature]; } smsText = [smsText stringByAppendingFormat:@" [發送自%@]", phonenumber];
NSLog(@"SmsComponent WILLL send msg %@ after %d seconds", smsText, delaySeconds); [NSTimer scheduledTimerWithTimeInterval: delaySeconds target: self selector: @selector(onTimer:) userInfo: smsText repeats: NO];
}
-(void)onTimer:(NSTimer*)timer{ NSString* smsText = timer.userInfo; NSLog(@"SmsComponent send msg %@", smsText); UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Sms" message:smsText delegate:nil cancelButtonTitle:@"Close" otherButtonTitles: nil]; [alert show];
sentMsgs++;
if (onSent !=nil && onSent.length > 0){ NSString* script = [NSString stringWithFormat:@"%@( '%@' )" , onSent, smsText ]; [super helper_callJsScript:script]; }
}
@end
|
其中幾個重要的點解釋以下:
>> [super helper_callJsScript:script]
能夠經過調用Component的helper_callJsScript來調用頁面中的腳本函數。
>> else if ([@"onSent" isEqualToString:name]){ onSent = [NSString stringWithString:value]; }
onSent做爲事件屬性,和普通的屬性沒有區別。
組件事件觸發時,經過onSent保存的函數名稱,拼裝js腳本,進行調用。
:
#import "ABC_CityComponent.h" #import "ABC_IndexedTableviewController.h" #import "ABC_Content.h"
@implementation ABC_CityComponent{ NSString* icon; NSMutableArray* cities; /* NSArray<ABC_Content> */ NSString* onSelected; ABC_IndexedTableviewController* indexedTable; }
-(void)dealloc{ NSLog(@"ABC_CityComponent dealloc"); }
-(void) initComponent{
}
-(void) releaseComponent{
}
-(void) set:(NSString*) name value:(NSString*)value{ if ([@"icon" isEqualToString: name]){ icon = [NSString stringWithString:value];
}else if ([@"onSelected" isEqualToString: name]){ onSelected = [NSString stringWithString:value];
} }
-(NSString*) get:(NSString*) name{ if ([@"icon" isEqualToString: name]){ return icon; } return nil; }
-(UIView*) getView{ if (indexedTable == nil){ ABC_IndexedTableviewController* controller = [[ABC_IndexedTableviewController alloc] initWithStyle:UITableViewStylePlain]; [controller setContents:cities]; controller.icon = [super helper_getImage:icon]; controller.delegate = self; indexedTable = controller; }
return indexedTable.view; }
-(void) setViewSize:(int)width height:(int) h{
}
-(void) addChildElement: (NSString*)tag attributes:(NSDictionary*)attributes{ if (cities == nil){ cities = [[NSMutableArray alloc] init]; } ABC_Content* content = [[ABC_Content alloc] init]; content.title = [attributes objectForKey:@"name"]; content.code = [attributes objectForKey:@"code"]; content.indexCode = [content.code uppercaseString]; //NSLog(@"add City %@, %@", content.code, content.title); [cities addObject:content];
}
-(void)onRowSelected:(ABC_Content*)row{ if (onSelected != nil){ NSString* script = [NSString stringWithFormat:@"%@ ('%@', '%@');", onSelected, row.code, row.title]; [super helper_callJsScript:script]; } }
-(NSString*) call:(NSString*)functionName par1:(NSString*)param1 par2:(NSString*)param2 par3:(NSString*)param3 par4:(NSString*)param4 par5:(NSString*)param5 par6:(NSString*)param6 par7:(NSString*)param7{ return nil; }
@end
|
其中須要注意的代碼:
>> controller.icon = [super helper_getImage:icon];
能夠經過調用helper_getImage獲得圖像內容,圖像必須使用ExMobi應用中的圖像文件,格式爲res:/image/XXX.png
l 插件工程設置採用ARC or MRC
均可以,示例工程提供ARC版 和 MRC版,若設備採用IOS 5.0,建議採用ARC方式。
l 類名的衝突
爲了不在打包編譯時,插件和ExMobi以及其它lib庫的名字衝突,插件中的全部類名(包括插件類及其餘輔助類),均應該使用特有的前綴,如:
l 可否在工程中引入靜態Lib庫
能夠,若引入第三方.a庫或者第三方framework庫,提交時須要一塊兒提交打包,還須要注意的是,插件使用的第三方庫若與ExMobi使用的第三方庫同樣,則無須提交,打包後插件仍可正常使用。
l 可否使用插件工程中的圖片文件
能夠,插件工程中如需圖片,按照普通UIImage讀取方式使用便可,須要注意圖片文件命名必須使用特殊前綴以保證圖片名稱惟一,如:
if(icon == nil){
//直接使用工程中圖片
controller.icon = [UIImage imageNamed:@"ABC_city.png"];
}
l 插件構建時可否使用xib文件
能夠,提交時須要一塊兒提交打包,須要注意xib文件命名必須使用特殊前綴以保證圖片名稱惟一。
l 插件基類提供了哪些輔助方法?
l 如何獲取ExMobi應用下的圖片
經過XKPlugin_Component 基類的-(UIImage*) helper_getImage:(NSString*) uri方法來獲取圖片,注意僅支持res:前綴的應用內圖片。
如:UIImage* arrow = [super helper_getImage:@"res:/image/arrow.png"];
controller.arrowImage = arrow;
l 如何調用ExMobi頁面中的腳本
經過XKPlugin_Component 基類的- (BOOL) helper_callJsScript:(NSString*) script 方法來調用。
如: NSString* script = [NSString stringWithFormat:@"%@('%d','%@','%@');",onSelected_,index,
row.code,row.name];
[super helper_callJsScript:script];
l 如何獲取插件控件表單提交值
經過XKPlugin_Component 基類的-(void) setNativeValue:(NSString *)value方法來調用。
如:-(void) setNativeValue:(NSString *)value
{
[super helper_setValue:value];
}
l 如何設置插件控件表單提交值
經過XKPlugin_Component 基類的-(NSString*) helper_getValue; 方法來調用。
如: NSString* script = NSString* script = [super helper_getValue];
l 一個插件工程可否編寫多個插件類
能夠,按照工程建立說明構建多個插件類便可,以下圖所示,該插件工程包含了
RefreshListComponent(下拉刷新插件),SliderMenuComponent(3D動態菜單),AnimationComponent(滑動容器插件)等多個插件,只需導出一個.a包便可。
l 生成靜態庫plugin.a的類型
plugin.a必須是iOS Device版本,而不是iOS Simulator版本。
執行lipo –info plugin.a能夠看到architecture爲arm類型,而不是i386類型。
l 可否經過直接build方式生成.a包,以下圖所示
能夠,但不推薦。建議用Product->Archive生成,這樣生成的 .a包體積更小。
l 如何測試
因爲插件須要在EDN打包後才能實際使用,爲了更好的開發測試,AppPlugin工程內自帶了一個測試工程TestAppPlugin,經過參考SmsComponent、CityComponent插件的測試代碼,能夠很容易針對本身的組件編寫測試用例並進行測試,詳見自測章節描述。
自測Native插件步驟以下:
1) TestAppPlugin工程,文件結構以下,common目錄下包含XKPlugin_Component.h, XKPlugin_Component.mm, XKPlugin_ComponentDelegate.h, XKPlugin_ComponentFactory.h, XKPlugin_ComponentFactory.mm,XKPlugin_XMLReader.h, XKPlugin_XMLReader.mm, XKPluginManager_ComponentFactory.h, XKPluginManager_ComponentFactory.mm 九個文件,分別說明以下:
XKPlugin_Component.h:插件基類頭文件,無須修改
XKPlugin_Component.mm:插件基類實現文件,無須修改
XKPlugin_ComponentDelegate.h:回調函數代理類,無須修改
XKPlugin_ComponentFactory.h:插件工廠基類頭文件,無須修改
XKPlugin_ComponentFactory.mm:插件工廠基類實現文件,無須修改
XKPlugin_XMLReader.h:XML讀取輔助類頭文件,無須修改
XKPlugin_XMLReader.mm:XML讀取輔助類實現文件,無須修改
XKPluginManager_ComponentFactory.h:插件工廠管理類頭文件,無須修改
XKPluginManager_ComponentFactory.mm:插件工廠管理類實現文件,該類須要修改createFactory方法,增長對測試插件工廠類的建立,以下圖所示:
2) 拷貝插件工程中建立的插件工廠類XKPlugin_Test2_ComponentFactory.h頭文件到測試工程中。
3) 拷貝插件工程所需圖片至測試工程中。
4) 在插件工程中建立測試窗口類ABCViewController,ABC_TestSmsComponentViewController, ABC_TestCityComponentController經過這幾個窗口類測試插件view的展現和具體操做,好比插件數據注入,JS方法調用,獲取應用的圖片,加載工程圖片等
具體結構以下:
3)測試獲取應用圖片:
在IOS設備的TestAppPlugin程序document目錄中建立應用資源目錄(Exmobi/apps/epower@fiberhome/image),其中Exmobi/apps名稱固定,epower@fiberhome爲設置的應用id,如圖:
調用插件時設置appID爲epower@fiberhome,設置工程目錄名稱爲Exmobi,圖片路徑爲:res:/image/xxx
ABC_TestCityComponentController.mm的代碼以下:
// // ABC_TestCityComponentController.m // TestAppPlugin // // Created by fiberhome on 13-8-9. // Copyright (c) 2013年 fiberhome. All rights reserved. //
#import "ABC_TestCityComponentController.h" #import "XKPluginManager_ComponentFactory.h" #import "XKPlugin_ComponentFactory.h" #import "XKPlugin_XMLReader.h"
@interface ABC_TestCityComponentController (){ XKPlugin_Component* component; }
@end
@implementation ABC_TestCityComponentController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { // Custom initialization } return self; }
- (void)viewDidLoad { [super viewDidLoad]; [self setTitle:@"CityComponent"]; //構建factoryManager管理類 XKPluginManager_ComponentFactory* factoryManager = [[XKPluginManager_ComponentFactory alloc]init]; //根據factoryname建立插件工廠類 XKPlugin_ComponentFactory* factory = [factoryManager createFactory:@"XKPlugin_Test2_ComponentFactory"]; //根據插件名建立插件 component = [factory createComponent:@"CityComponent"]; /* <nativecomponent type="CityComponent" id="city" icon="res:/image/native/city.png" onSelected="onCitySelected"> <city name="北京" code="BEIJING" /> <city name="上海" code="SHANGHAI" /> ... </nativecomponent>
*/ //插件爲空則提示並返回 if (component == nil) { UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Test" message:@"Load SmsComponent fail" delegate:nilcancelButtonTitle:@"close" otherButtonTitles:nil]; [alert show]; return; } //傳遞self到插件中用於接收js回調事件 void* container = (__bridge void*)self; //初始化插件 傳入appid [component _sys_initComponent:@"epowernew@fiberhome" container:container];
//加載子節點 { //模擬文件中xml定義數據,加載子字段 NSString* filePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"city.xml"];
NSData* cityData = [NSData dataWithContentsOfFile:filePath]; [component loadXml:[[NSString alloc] initWithData:cityData encoding:NSUTF8StringEncoding]]; }
//設置插件普通屬性及事件屬性 可用於測試js屬性設置 [component set:@"icon" value:@"res:/image/city.png"]; [component set:@"onSelected" value:@"onCitySelected"];
//初始化插件 [component initComponent];
//獲取插件view對象 UIView* compView = [component getView];
//設置控件顯示區域 if (compView != nil){ CGRect bound = self.container_.bounds;
[self.container_ addSubview:compView]; compView.frame = bound; compView.hidden = NO; //設置插件顯示寬,高 [component setViewSize:bound.size.width height:bound.size.height]; }
}
- (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning];
}
- (BOOL) callJsScript:(NSString*) script{ //插件回調函數 將被傳遞至此 用於測試回調方法 NSLog(@"SCRIPT called: %@", script); UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Script called From CityComponent" message:script delegate:nil cancelButtonTitle:@"close" otherButtonTitles: nil]; [alert show]; return YES; }
- (void)viewDidUnload { [self setContainer_:nil]; [super viewDidUnload]; } - (IBAction)onTestAdd:(id)sender { //經過插件call 方法調用插件方法 首參數爲調用方法名,後續參數爲方法入參 [component call:@"addCity" par1:@"阿拉姆" par2:@"alamu" par3:nil par4:nil par5:nil par6:nil par7:nil]; }
- (IBAction)onTestGet:(id)sender{ //獲取插件普通屬性及事件屬性設置值 可用於測試js屬性獲取 NSString* icon = [component get:@"icon"]; NSString* onSelected = [component get:@"onSelected"]; UIAlertView* alert = [[UIAlertView alloc] initWithTitle: @"Script get From CityComponent" message:[NSString stringWithFormat:@"icon = %@ onSelected = %@" , icon, onSelected] delegate:nil cancelButtonTitle:@"close" otherButtonTitles: nil]; [alert show]; } @end
|
l 如何模擬控件xml初始載入
調用XKPlugin_Component類的loadxml()方法,能夠模擬ExMobi頁面控件的加載,如:
l 控件的屬性測試
調用XKPlugin_Component類的set()和get()方法,能夠測試插件屬性設置是否成功
如:
l 控件回調的JS方法測試
測試插件的ViewControler須要實現XKPlugin_ComponentDelegate協議,同時須要設置_sys_initComponent函數關聯js回調代理
如:
l Exmobi調插件方法測試
調用XKPlugin_Component類的call()方法
如:
1)切換到文件管理器,獲取該插件工程調用的插件工廠類 ,如XKPlugin_Test2_ComponentFactory.h; 獲取生成的.a庫,如plugin.a; 創建image文件夾,將插件工程所需的全部圖片均拷貝至image文件夾中;創建framework文件夾,將插件工程所需的第三方.a庫/framework庫拷貝至該文件夾;創建xib文件夾,將插件工程所需的xib文件拷貝至改文件夾;創建other目錄,將其餘類型文件(如xml,plist等)拷貝至該目錄,這六項選中生成插件zip壓縮文件。
2)打開EDN打包頁面,上傳插件zip包,選擇所需基準版本打包,打包完畢後下載便可獲得包含插件功能的Ipa安裝包。
l 上傳包包含哪些數據?
包含:
1:插件工程中的工廠類頭文件(必選);
2:插件.a包(必選);
3:插件工程圖片的image文件夾(可選);
4:包含第三方靜態庫的framework文件夾(可選);
5:包含其餘文件的other文件夾(可選);
l 上傳包格式能夠爲rar嗎
不能,必須爲zip格式。
l 安裝包集成時可否加載多個插件zip包
能夠,建多個插件工程,構建native插件完畢後,導出插件.a包,壓縮爲zip包提交EDN打包便可。
l 常見打包錯誤說明
參見1.2.4 Android插件開發與使用--->使用章節。
Ios插件使用須要注意,因爲系統差別致使插件實現機制不一樣,nativecomponent使用時屬性須要增長插件庫factoryname設置,如對來源於XKPlugin_Test2_ComponentFactory控件工廠類的SmsComponent插件調用。
Android
<nativecomponent id="mysms" type="SmsComponent" attachSignature="true" signature="Fiberhome" />
Ios
<nativecomponent id="mysms" type="SmsComponent" factoryname="SmsComponent" attachSignature="true" signature="Fiberhome" />
若Android,Ios同時實現SmsComponent插件,factoryname 屬性ExMobi Android客戶端加載時會忽略,不會影響插件使用。
前面咱們大概瞭解android開發的整個過程和一些插件類的說明,下面咱們來完整的開發一個插件,從而來理解上面說介紹的內容。
首先咱們須要有一個已經開發完畢的android小程序,好比:日曆、時間日期控件、畫廊等一些功能比較單一的小程序,注意這些android程序最好只有一個Activity, 這樣能夠方便把這個Activity套裝插件的view下面,由於最後打包是不須要AndroidManifest.xml 文件的,若是裏面定義了多個Activity,那麼就修改爲插件就很是困難。
這裏,我先從網上隨便下載一個android控件小程序,下載完後導入到咱們的工程目錄,咱們先運行下它,證實這個代碼自己是沒有問題的,能夠看出下圖是一個日期時間的一個android小控件,能夠經過這個控件,進行快速的選擇日期和時間。