用HTML5和PhoneGap寫個Path項目(視頻)
坦白的講,OPath比Path差得不是一點半點,但是比很多國產(chǎn)的原生應(yīng)用體驗要好,下邊是演示視頻。
看完視頻如果你對效果還滿意的話,請接著往下看。我會和大家分享如何做一個這樣的應(yīng)用,包括整個前端(HTML5)和后端(PHP)。這個項目也是在MIT協(xié)議下完全開源的(同樣包括前端和后端),項目鏈接在文章最末尾。PS:我只在iPod Touch4的iOS5系統(tǒng)上進行了測試,其他平臺可能存在兼容性問題,需要自行測試和修復(fù)。
框架選擇
PhoneGap就不用說了,有了它才能打包。我們要選的里邊的前端框架。雖然之前我已經(jīng)做了一個基于Jquery Mobile的Tab類模板,但是很明顯,Path并沒有采用Tab方式的菜單。加上Path的控件都是自己的風(fēng)格,所以自己渲染樣式是逃不掉的,于是最后我選擇了采用 Mobile-boilerplate + iScroll4 來做這個項目。

(Mobile-boilerplate)
Mobile-boilerplate 是一個移動設(shè)備用的HTML5空白模板,它處理掉了非常多的兼容細節(jié),比如viewport之類的。想了解詳情的同學(xué)可以去看Mobile-boilerplate里邊的注釋,寫得非常詳細,還有相關(guān)issue的鏈接。
下載Mobile-boilerplate將解壓出來的目錄作為我們項目的根目錄。Mobile-boilerplate已經(jīng)包含了js和css目錄,其中js下的libs里邊有JQuery。

我首先在Mobile-boilerplate的模板基礎(chǔ)上做了下登錄頁面,完成后的效果是這樣的:

這個界面很簡單,直接用CSS來實現(xiàn)就可以了,遵守Mobile-boilerplate的結(jié)構(gòu),在css/style.css中部200行左右的位置開始寫入自己的css。

API接口的用戶認(rèn)證
接下來我們說說API方式用戶認(rèn)證的實現(xiàn)。在OPath項目中,我們采用用戶名+密碼換token,以后操作通過token鑒權(quán)的方式。因為這種方式實現(xiàn)起來很方便。做PHP的同學(xué)都知道,PHP的Session機制是通過PHP SESSION ID來標(biāo)示用戶的,一般情況下這個標(biāo)示通過Cookie存儲在瀏覽器中。
我們的思路就是,將這個SESSION ID直接作為token就好啦。于是我們實現(xiàn)了get_token接口:

最核心的邏輯就這幾行token在生成后,通過json格式返回給客戶端。
- session_start();
- $token = session_id(); // 將Session id作為token
- $_SESSION['token'] = $token; // 在Session中存儲用戶信息,供以后的操作認(rèn)證使用。
- $_SESSION['uid'] = $user['id'];
- $_SESSION['name'] = $user['name'];
- $_SESSION['email'] = $user['email'];
- $_SESSION['level'] = $user['level'];
客戶端發(fā)送Ajax請求和解析參數(shù)
現(xiàn)在回到客戶端這邊來,當(dāng)用戶在登錄頁面填好賬號后,我們需要將這些數(shù)據(jù)發(fā)送到服務(wù)器端,換取token。使用JQuery,這個很簡單:
我們用 jQuery.parseJSON 解析返回的JSON數(shù)據(jù),然后在登錄正確后,將賬號和token保存到本地。這里的kset其實是我寫的一個快捷函數(shù),它只是簡單封裝了下HTML5的LocalStorage。
- function kset( key , value )
- {
- window.localStorage.setItem( key , value );
- }
- function kget( key )
- {
- return window.localStorage.getItem( key );
- }
- function kremove( key )
- {
- window.localStorage.removeItem( key );
- }
LocalStorage里邊的數(shù)據(jù)是持久化的,在應(yīng)用被關(guān)閉后依舊存在。順便說下,在Chrome和Safari的調(diào)試工具里邊,Resource的Tab里邊可以直接看到當(dāng)前應(yīng)用的LocalStorage還有IndexedDB的數(shù)據(jù),不用去找其他的工具來查看這些值。這在調(diào)試應(yīng)用的時候非常方便。
由于開發(fā)的應(yīng)用是HTML5的,我首先會實現(xiàn)標(biāo)準(zhǔn)瀏覽器支持的部分,用Safari來進行調(diào)試;在最后才實現(xiàn)需要PhoneGap的部分,進行真機調(diào)試,這樣可以節(jié)省很多調(diào)試時間。

Path主頁面 Path的主頁面很帥,實現(xiàn)細節(jié)也很多,我挑重點說。先放一張做完后的效果:

整體的布局上,其實我們可以直接沿用iScroll4的Demo,頂欄固定,將原來的Footer換成那個加號按鈕就可以了。加號按鈕的實現(xiàn)網(wǎng)上有CSS版本的,但是在Android上會出現(xiàn)嚴(yán)重的毛邊,所以我直接用圖片代替了。(Android上CSS圓角毛邊的問題非常煩人,從這個地方可以一眼認(rèn)出是否是WebAPP;iOS上則非常干凈。)考慮到iPod Touch(我主要用這個)的杯具性能,我只簡單做了個位置移動效果,覺得細節(jié)不夠的同學(xué)可以自己加旋轉(zhuǎn)和彈簧效果,用JQuery很容易做。說實話我覺得原版Path的那個加號按鈕展開后很難按準(zhǔn) T__T
頁面上方的Profile Picture部分放到iScroll的wrapper內(nèi),scroll最上方;下邊的【加載更多】按鈕,放到wrapper內(nèi),scroll最下方。均通過CSS指定固定高。

其他的布局細節(jié)可以查看path.html和style.css源文件。
Retina屏幕下的圖片模糊問題
在iScroll的基礎(chǔ)上,我很快就完成了主頁面的布局,但是當(dāng)我放到頭像和圖片后,杯具發(fā)生了!在Android上看的時候很正常,但是在Touch上圖片會變得非常模糊。按Mobile-boilerplate的viewport設(shè)定,整個頁面寬度應(yīng)該會變成 設(shè)備寬,對Touch來說就是320px。很快我就意識到這應(yīng)該是Retina屏幕帶來的問題,因為Retina屏將標(biāo)準(zhǔn)屏幕一個像素改用4個像素顯示,所以圖像和周圍的矢量圖對比起來就模糊了。而在Android上都采用一個像素顯示,所以沒有這個問題。
Google了下,網(wǎng)上的解決方案是這樣的:對于直接的圖片應(yīng)用,比如說:
- <img src=”image.png”/>
采用Retina屏幕的iOS設(shè)備會去找同目錄下的 image@2x.png進行顯示。對于通過CSS引用的圖片,比如說:
- <div id=”avatar”></div>
則需要使用link標(biāo)簽按條件載入專用的CSS。
- <link rel="stylesheet" media="only screen and (-webkit-min-device-pixel-ratio: 2)" type="text/css" href="../iphone4.css" />
我測試了下,沒有成功,更主要的還是覺得這個方案不爽,額外CSS什么的弱爆了。然后自己試出來了一個方案:
因為模糊的原理已經(jīng)很清楚了,那么只要朝這個方向去想就行。對于直接引用圖片的情況,很容易想到解決方案:原本100*100的圖片,我做成200*200,然后在img標(biāo)簽中指定高和寬為100*100。這樣在Retina屏幕上可以按像素點進行顯示,在其他屏幕的設(shè)備上,瀏覽器會自己先縮放后顯示,測試效果很清晰。通過CSS引用的情況比較麻煩,我睡了一覺才想出來,如果div#avatar要顯示100*100的背景,那么將它的高和寬指定為200*200,配上200*200的背景圖片,最后,Zoom:0.5。
其他頁面需要注意的地方
其他頁面基本上都是體力勞動了,Path Feed列表渲染時有兩個需要注意的細節(jié):一是我們用的模板本身是用<script>
標(biāo)簽的,所以模板里邊就不能再有這個標(biāo)簽了。在顯示每條Feed時,需要顯示對應(yīng)的用戶頭像,這個頭像當(dāng)做背景顯示的,由于不能用script標(biāo)簽,只好把url先放到標(biāo)簽里,渲染完后統(tǒng)一處理。
二是當(dāng)Feed里邊有圖片的時候,iScroll的高度會受影響。需要在圖片加載完全后,再重新調(diào)用iScroll的refresh方法。在Feed中圖片過多時,F(xiàn)eed頁面會卡,這個問題可以通過串行載入圖片資源的方式來解決,在當(dāng)前這個版本里邊,沒有實現(xiàn)。
Thought頁面這部分沒有太多問題,采用了之前Tab模板的Div切換方式,從而逼近原生應(yīng)用的切換速度。

通過PhoneGap實現(xiàn)拍照和頭像設(shè)置
頭像和拍照使用PhoneGap調(diào)用了本地設(shè)備,按PhoneGap的說明,加載PhoneGap的JS文件并在頁面初始化時注冊好事件。

在點擊了拍照按鈕或者頭像按鈕后,調(diào)用攝像頭,并通過PhoneGap提供的的文件傳輸對象FileTransfer進行上傳。FileTransfer可以模擬一個完全的HTTP請求,所以服務(wù)器端并不需要特殊出來,按帶file標(biāo)簽的標(biāo)準(zhǔn)From請求處理即可。

需要額外處理的是,iOS拍攝的圖片方向很可能不對。這是因為iOS本地的相冊在顯示圖片時,根據(jù)拍攝時的方向自動做了調(diào)整。要在服務(wù)器端正確的顯示圖像,必須根據(jù)圖片中的Exif信息調(diào)整方向。在將家里的小浪擺好Pose并通過各個方向的拍攝后,我寫好了調(diào)整方向的函數(shù)。

所有的代碼,我已經(jīng)放到上,大家可以下載 http://code.google.com/p/o-path/ 。這些代碼是MIT協(xié)議,可以隨意商用。
開發(fā)以外
兼容性
由于各個平臺對CSS和HTML5 的支持差異很大,所以很難在全部平臺做到完美,像之前提到過的,Android的CSS圓角毛邊問題,Div切換時部分圖層不定期隱藏的問題;另外PhoneGap還有各種BUG和問題,比如iOS應(yīng)用從后臺呼出時有短暫的白屏閃爍問題;比如OPath里邊我使用了1.2版本,這個版本在iOS下拍照正常,但是在Android下呼叫不出攝像頭,換成1.0版本就可以,這說明PhoneGap在平臺兼容性上問題依然不少。
個人感覺,在現(xiàn)階段,一個PhoneGap應(yīng)用要想做到完美的體驗并在各個平臺保持體驗一致,難度非常大。不過如果能忍受一些小細節(jié),或者能做好優(yōu)雅降級的話,PhoneGap應(yīng)用是能超過很多原生應(yīng)用的。
代碼安全性
采用PhoneGap打包的應(yīng)用,不管是APK還是IPA,只要將擴展名改為zip,解壓后在www目錄就可以得到這個應(yīng)用的全部源代碼。

這使得盜版成本非常的低,必要的時候需要對js進行混淆。最可靠的方式是將部分核心邏輯放到云端,通過api使用。