AppCan官方網站:http://www.appcan.cn/
AppCan最大的一個特點,就是其多窗口機制,筆者之前寫過一篇關於AppCan UI框架的文字,主要是介紹如何使用UI2.0框架進行頁面構建。這篇要闡述的是關於AppCan頁面之間串聯,通訊,交互等方面的內容。
多窗口,其對應用底層代碼,就是多個webview。AppCan引擎有個webview管理機制:多個webview是放到一個堆棧中的,並對其生命周期進行管理;以及何時創建新webview,何時釋放webview等優化控制。為什么有多窗口的概念呢,大家試回憶客戶端的使用體驗,一個新聞列表頁,用戶用手指翻了幾屏的內容,突然看到一條感興趣的新聞,那么用戶會點此條新聞,看具體內容。看完詳情后,返回到列表內容時,應該保證其保留之前的狀態,用戶可以繼續翻滾屏幕。在具體開發的時候,我們知道,最方便的寫法是列表頁是一個html頁面,詳情頁又是一個html頁面,這樣,如果是單個webview去加載兩個html頁面,在頁面切換時必然會重新刷新網頁,如此就達不到上述所說的效果了。因此,跟瀏覽器的多標簽頁功能類似,AppCan引入了多webview的機制。下述截圖是AppCan多窗口機制的demo:

多窗口之間的通訊:
這里的多窗口包括主窗口和浮動窗口,主窗口之上可以有多個浮動窗口。主窗口關閉后,其上所有浮動窗口也都會關閉。所有的窗口都是有名字的,在config.xml文件中配置的起始頁所在的窗口名字為”root”,而其它窗口都是通過uexWindow.open打開的,需要定義一個唯一的名字;uexWindow.open 打開的是一個主窗口,浮動窗口則通過uexWindow.openPopover創建。一個主窗口上的多個浮動窗口名字是唯一的,但不同主窗口上的浮動窗口名字可以相同。浮動窗口可以有彈動效果,可以有數學變化:放大,旋轉,移動等。給開發提供了一個創作空間,制作出體驗好的效果。在應用的開發過程中,經常會涉及到窗口之間的通訊,比如從網絡獲取一個數據,根據返回的數據,讓其它窗口執行相應的變化,這就需要用到窗口間通訊機制:
- 主窗口之間通訊:uexWindow.evaluateScript(inWindowName,inType,inScript)
- 主窗口與浮動窗口之間通訊:
uexWindow.evaluateScript(inWindowName,inType,inScript)
uexWindow.evaluatePopoverScript(inWndName,inPopName,inScript)
在上述窗口通訊方法中,最后一個參數是目標窗口的執行腳本:inScript,有開發者會問,這個腳本是否可以攜帶形參的內容,答案是可以的,但是有限制,一般少量的純數字和常用字符串可以傳遞。如果是特殊字符和漢字,傳輸不過去。那么如何傳輸呢,一種做法是通過window.localStorage傳遞,在此方法中設置一個localStorage,在彼窗口中獲取這個localStorage。
窗口切換動畫:
客戶端中的頁面切換多少都會有動畫效果,這在單webview中,div之前切換可以通過css去實現動畫效果,當然這個效果會差些。而AppCan的多webview機制,可以有條件的把切換動畫效果交由原生去實現,使用方法也很簡單,只要配置uexWindow.open的第四個參數即可,其具體數字代表什么效果可在官網開發文檔中找到。但是浮動窗口的動畫卻不是通過配置參數實現的,浮動窗口提供一套數學變換方法,需要再浮動窗口頁面上自行寫變換效果,由事件觸發器去觸發這些方法執行:uexWindow.makeTranslation、uexWindow.makeScale、uexWindow.makeRotate、uexWindow.makeAlpha,通過組合這些變換,能夠寫出更多的動畫效果。
浮動窗口:
浮動窗口能夠解決的事情很多,比如解決手機上不支持局部div滾動;解決oauth機制;解決上下拉刷新效果等問題。甚至解決在pad上布局的問題。
在手機上,原生滾動是整個webview的行為,一般的css寫滾動效果是:style="overflow:scroll;",但這個在手機webview上不支持。浮動窗口,作為疊加在主窗口之上的一塊區域,其大小位置是可設定的,其背景初始也是透明的,因此,可以當做主窗口的一部分進行展示。
很常見的手機上的加載數據的效果是下拉,或上拉刷新數據。這個原生實現比較簡單,如果用js+css去實現,也是可以模擬的,但效果不佳。此時,浮動窗口如果可以實現上拉下拉,而且是用原生實現的,其效果會提升一個檔次。而且通過uexWindow.setBounceParams方法,還可以設置上拉或下拉的參數配置:刷新箭頭、不同狀態下顯示的文字等。
還有一種很常見的設計是:點擊左上角或右上角某個按鈕,從側邊滑出一個菜單欄,即所謂的側邊菜單欄。這種效果也可以通過浮動窗口去實現:通過設定浮動窗口的顯示位置,按鈕點擊后,浮動窗口執行橫向滑動動畫。
在第三方開放平台中,基本上用的都是OAuth驗證機制,比如新浪微博,騰訊微博,開發者在開發微博分享功能時,需要實現OAuth機制,一種是通過開放平台提供的sdk進行集成,這個可用AppCan的插件擴展機制實現。還有就是通過服務器去實現OAuth驗證機制。但是,通過對OAuth機制的研究,AppCan還有更方便的方式去實現:uexWindow.onOAuthInfo(windowName,url)方法,是能夠監聽某一個窗口(windowName)的url變化的,即當監聽的窗口url發生變化時,會觸發uexWindow.onOAuthInfo方法。比如說,在主窗口中uexWindow.openPopover浮動窗口時,flag參數設為1,那么主窗口就能監聽此浮動窗口的url變化:加載第三方開放平台授權界面,授權成功返回token值,token值過期時間等都能在url中體現,這就提供了一個實現OAuth驗證的方式。
開發者首先了解了AppCan的多窗口機制,那么進行app開發時,就能省去不少的麻煩事。常用的效果實現,頁面布局的問題,數據交互的問題等,都可以利用多窗口機制去實現。說到數據交互,這里順便提下客戶端與服務器端的數據交互問題:
客戶端與服務器端數據交互:
如果Hybrid App的框架是把html,css,js等資源文件放到本地,那么此種架構與服務器數據通訊就屬於跨域請求。又由於Hybrid App一般用到瀏覽器引擎作為base,勢必會聯想到同源策略問題。然而,同源策略是瀏覽器的行為,而非瀏覽器引擎的行為,諸如Hybrid App這種采用瀏覽器引擎為base的架構,其實是可以直接跨域請求。
AppCan提供服務器數據交互接口: uexXmlHttpMgr,其模擬了form表單提交,可get/post提交,可用於文件和文字表單域同時上傳:
uexXmlHttpMgr.open(inXmlHttpID,inMethods,inUrl,inTimeOut)
uexXmlHttpMgr.setPostData(inXmlHttpID,inDataType,inKey,inValue)
uexXmlHttpMgr.send(inXmlHttpID)
uexXmlHttpMgr.onData(inOpCode,inStatus,inResult)
uexXmlHttpMgr.close(inXmlHttpID)
我們還是拿寫UI框架時的例子來說明,
如果請求的url為:http://192.168.1.163/case/
返回數據如下:
{
"1": {
"title": "惠特曼任職期被指時日不多:或被惠普解職",
"time": "2012-11-30"
},
"2": {
"title": "息稱微軟Surface平板銷量不佳 訂單已減半",
"time": "2012-11-30"
},
"3": {
"title": "iPad mini被指自相殘殺:蠶食iPad銷量 ",
"time": "2012-11-30"
},
"4": {
"title": "消息稱微軟Surface平板銷量不佳 訂單已減半",
"time": "2012-11-30"
},
"5": {
"title": "蘋果推出iTunes 11:十年來最大更新 ",
"time": "2012-11-30"
}
}
獲取數據后,需要解析到界面中,其效果如:

代碼如何實現呢?以下是具體的包括請求,數據返回進行解析,並拼裝到頁面中的代碼:
var tmp = '<ul ontouchstart="zy_touch(\'btn-act\')" class="${first:"uc-t"} ${last:"uc-b"} ub ubb b-gra c-m2 t-bla ub-ac lis">'+
'<li class="ui-icon-link"></li>'+
'<ul class="ub-f1 ub ub-ver" style="margin-left:0.5em;">'+
' <li class="ulev1">${title} </li>'+
'<ul class="ub ub-ac t-gra ulev-2">'+
' <li class="ub-f4"></li>'+
'<li class="ub-f1">${time}</li>'+
'</ul>'+
'</ul>'+
'</ul>';
var url = "http://192.168.1.163/case/";
function getdata(){
uexXmlHttpMgr.open("1","get",url,6000);
uexXmlHttpMgr.onData=function(inOpCode,inStatus,inResult){
if(inStatus==1){
var json = JSON.parse(inResult);
var html = zy_tmpl(tmp,json,zy_tmpl_count(json),null);
$$("lisid").innerHTML = html;
}
}
uexXmlHttpMgr.send("1");
}
