如何用HTML5+PhoneGap做個(gè)山寨Path應(yīng)用?
坦白的講,OPath比Path差得不是一點(diǎn)半點(diǎn),但是比很多國(guó)產(chǎn)的原生應(yīng)用體驗(yàn)要好,下邊是演示視頻。
看完視頻如果你對(duì)效果還滿意的話,請(qǐng)接著往下看。我會(huì)和大家分享如何做一個(gè)這樣的應(yīng)用,包括整個(gè)前端(HTML5)和后端(PHP)。
這個(gè)項(xiàng)目也是在MIT協(xié)議下完全開(kāi)源的(同樣包括前端和后端),項(xiàng)目鏈接在文章最末尾。
PS:我只在iPod Touch4的iOS5系統(tǒng)上進(jìn)行了測(cè)試,其他平臺(tái)可能存在兼容性問(wèn)題,需要自行測(cè)試和修復(fù)。
框架選擇
PhoneGap就不用說(shuō)了,有了它才能打包。我們要選的里邊的前端框架。雖然之前我已經(jīng)做了一個(gè)基于jQuery Mobile的Tab類(lèi)模板,但是很明顯,Path并沒(méi)有采用Tab方式的菜單。
加上Path的控件都是自己的風(fēng)格,所以自己渲染樣式是逃不掉的,于是***我選擇了采用Mobile-boilerplate + iScroll4 來(lái)做這個(gè)項(xiàng)目。
Mobile-boilerplate
Mobile-boilerplate 是一個(gè)移動(dòng)設(shè)備用的HTML5空白模板,它處理掉了非常多的兼容細(xì)節(jié),比如viewport之類(lèi)的。想了解詳情的同學(xué)可以去看Mobile-boilerplate里邊的注釋?zhuān)瑢?xiě)得非常詳細(xì),還有相關(guān)issue的鏈接。
下載Mobile-boilerplate將解壓出來(lái)的目錄作為我們項(xiàng)目的根目錄。Mobile-boilerplate已經(jīng)包含了js和css目錄,其中js下的libs里邊有JQuery。
我首先在Mobile-boilerplate的模板基礎(chǔ)上做了下登錄頁(yè)面,完成后的效果是這樣的:
這個(gè)界面很簡(jiǎn)單,直接用CSS來(lái)實(shí)現(xiàn)就可以了,遵守Mobile-boilerplate的結(jié)構(gòu),在css/style.css中部200行左右的位置開(kāi)始寫(xiě)入自己的css。
API接口的用戶認(rèn)證
接下來(lái)我們說(shuō)說(shuō)API方式用戶認(rèn)證的實(shí)現(xiàn)。在OPath項(xiàng)目中,我們采用用戶名+密碼換token,以后操作通過(guò)token鑒權(quán)的方式。
因?yàn)檫@種方式實(shí)現(xiàn)起來(lái)很方便。做PHP的同學(xué)都知道,PHP的Session機(jī)制是通過(guò)PHP SESSION ID來(lái)標(biāo)示用戶的,一般情況下這個(gè)標(biāo)示通過(guò)Cookie存儲(chǔ)在瀏覽器中。
我們的思路就是,將這個(gè)SESSION ID直接作為token就好啦。于是我們實(shí)現(xiàn)了get_token接口:
最核心的邏輯就這幾行
- session_start();
- $token = session_id(); // 將Session id作為token
- $_SESSION['token'] = $token; // 在Session中存儲(chǔ)用戶信息,供以后的操作認(rèn)證使用。
- $_SESSION['uid'] = $user['id'];
- $_SESSION['name'] = $user['name'];
- $_SESSION['email'] = $user['email'];
- $_SESSION['level'] = $user['level'];
token在生成后,通過(guò)json格式返回給客戶端。
客戶端發(fā)送AJAX請(qǐng)求和解析參數(shù)
現(xiàn)在回到客戶端這邊來(lái),當(dāng)用戶在登錄頁(yè)面填好賬號(hào)后,我們需要將這些數(shù)據(jù)發(fā)送到服務(wù)器端,換取token。使用JQuery,這個(gè)很簡(jiǎn)單:
我們用 jQuery.parseJSON 解析返回的JSON數(shù)據(jù),然后在登錄正確后,將賬號(hào)和token保存到本地。這里的kset其實(shí)是我寫(xiě)的一個(gè)快捷函數(shù),它只是簡(jiǎn)單封裝了下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)閉后依舊存在。順便說(shuō)下,在Chrome和Safari的調(diào)試工具里邊,Resource的Tab里邊可以直接看到當(dāng)前應(yīng)用的LocalStorage還有IndexedDB的數(shù)據(jù),不用去找其他的工具來(lái)查看這些值。這在調(diào)試應(yīng)用的時(shí)候非常方便。
由于開(kāi)發(fā)的應(yīng)用是HTML5的,我首先會(huì)實(shí)現(xiàn)標(biāo)準(zhǔn)瀏覽器支持的部分,用Safari來(lái)進(jìn)行調(diào)試;在***才實(shí)現(xiàn)需要PhoneGap的部分,進(jìn)行真機(jī)調(diào)試,這樣可以節(jié)省很多調(diào)試時(shí)間。
PATH主頁(yè)面
Path的主頁(yè)面很帥,實(shí)現(xiàn)細(xì)節(jié)也很多,我挑重點(diǎn)說(shuō)。先放一張做完后的效果:
整體的布局上,其實(shí)我們可以直接沿用iScroll4的Demo,頂欄固定,將原來(lái)的Footer換成那個(gè)加號(hào)按鈕就可以了。加號(hào)按鈕的實(shí)現(xiàn)網(wǎng)上有 CSS版本的,但是在Android上會(huì)出現(xiàn)嚴(yán)重的毛邊,所以我直接用圖片代替了。(Android上CSS圓角毛邊的問(wèn)題非常煩人,從這個(gè)地方可以一眼認(rèn)出是否是WebAPP;iOS上則非常干凈。)考慮到iPod Touch(我主要用這個(gè))的杯具性能,我只簡(jiǎn)單做了個(gè)位置移動(dòng)效果,覺(jué)得細(xì)節(jié)不夠的同學(xué)可以自己加旋轉(zhuǎn)和彈簧效果,用JQuery很容易做。說(shuō)實(shí)話我覺(jué)得原版Path的那個(gè)加號(hào)按鈕展開(kāi)后很難按準(zhǔn) T__T
頁(yè)面上方的Profile Picture部分放到iScroll的wrapper內(nèi),scroll最上方;下邊的【加載更多】按鈕,放到wrapper內(nèi),scroll最下方。均通過(guò)CSS指定固定高。
其他的布局細(xì)節(jié)可以查看path.html和style.css源文件。
RETINA屏幕下的圖片模糊問(wèn)題
在iScroll的基礎(chǔ)上,我很快就完成了主頁(yè)面的布局,但是當(dāng)我放到頭像和圖片后,杯具發(fā)生了!在Android上看的時(shí)候很正常,但是在Touch上圖片會(huì)變得非常模糊。
按Mobile-boilerplate的viewport設(shè)定,整個(gè)頁(yè)面寬度應(yīng)該會(huì)變成 設(shè)備寬,對(duì)Touch來(lái)說(shuō)就是320px。
很快我就意識(shí)到這應(yīng)該是Retina屏幕帶來(lái)的問(wèn)題,因?yàn)镽etina屏將標(biāo)準(zhǔn)屏幕一個(gè)像素改用4個(gè)像素顯示,所以圖像和周?chē)氖噶繄D對(duì)比起來(lái)就模糊了。
而在Android上都采用一個(gè)像素顯示,所以沒(méi)有這個(gè)問(wèn)題。
Google了下,網(wǎng)上的解決方案是這樣的:
對(duì)于直接的圖片應(yīng)用,比如說(shuō)
- <img src=”image.png”/>
采用Retina屏幕的iOS設(shè)備會(huì)去找同目錄下的 image@2x.png進(jìn)行顯示。
對(duì)于通過(guò)CSS引用的圖片,比如說(shuō)
- <div id=”avatar”></div>
則需要使用link標(biāo)簽按條件載入專(zhuān)用的CSS。
- <link rel="stylesheet" media="only screen and (-webkit-min-device-pixel-ratio: 2)" type="text/css" href="../iphone4.css" />
我測(cè)試了下,沒(méi)有成功,更主要的還是覺(jué)得這個(gè)方案不爽,額外CSS什么的弱爆了。然后自己試出來(lái)了一個(gè)方案:
因?yàn)槟:脑硪呀?jīng)很清楚了,那么只要朝這個(gè)方向去想就行。
對(duì)于直接引用圖片的情況,很容易想到解決方案:原本100*100的圖片,我做成200*200,然后在img標(biāo)簽中指定高和寬為100*100。這樣在Retina屏幕上可以按像素點(diǎn)進(jìn)行顯示,在其他屏幕的設(shè)備上,瀏覽器會(huì)自己先縮放后顯示,測(cè)試效果很清晰。
通過(guò)CSS引用的情況比較麻煩,我睡了一覺(jué)才想出來(lái),如果div#avatar要顯示100*100的背景,那么將它的高和寬指定為200*200,配上200*200的背景圖片,***,Zoom:0.5。
其他頁(yè)面需要注意的地方
其他頁(yè)面基本上都是體力勞動(dòng)了,Path Feed列表渲染時(shí)有兩個(gè)需要注意的細(xì)節(jié):
一是我們用的模板本身是用<script>標(biāo)簽的,所以模板里邊就不能再有這個(gè)標(biāo)簽了。在顯示每條Feed時(shí),需要顯示對(duì)應(yīng)的用戶頭像,這個(gè)頭像當(dāng)做背景顯示的,由于不能用script標(biāo)簽,只好把url先放到標(biāo)簽里,渲染完后統(tǒng)一處理。
二是當(dāng)Feed里邊有圖片的時(shí)候,iScroll的高度會(huì)受影響。需要在圖片加載完全后,再重新調(diào)用iScroll的refresh方法。在Feed中圖片過(guò)多時(shí),F(xiàn)eed頁(yè)面會(huì)卡,這個(gè)問(wèn)題可以通過(guò)串行載入圖片資源的方式來(lái)解決,在當(dāng)前這個(gè)版本里邊,沒(méi)有實(shí)現(xiàn)。
Thought頁(yè)面這部分沒(méi)有太多問(wèn)題,采用了之前Tab模板的Div切換方式,從而逼近原生應(yīng)用的切換速度。
通過(guò)PHONEGAP實(shí)現(xiàn)拍照和頭像設(shè)置
頭像和拍照使用PhoneGap調(diào)用了本地設(shè)備,按PhoneGap的說(shuō)明,加載PhoneGap的JS文件并在頁(yè)面初始化時(shí)注冊(cè)好事件。
在點(diǎn)擊了拍照按鈕或者頭像按鈕后,調(diào)用攝像頭,并通過(guò)PhoneGap提供的的文件傳輸對(duì)象FileTransfer進(jìn)行上傳。FileTransfer可以模擬一個(gè)完全的HTTP請(qǐng)求,所以服務(wù)器端并不需要特殊出來(lái),按帶file標(biāo)簽的標(biāo)準(zhǔn)From請(qǐng)求處理即可。
需要額外處理的是,iOS拍攝的圖片方向很可能不對(duì)。這是因?yàn)閕OS本地的相冊(cè)在顯示圖片時(shí),根據(jù)拍攝時(shí)的方向自動(dòng)做了調(diào)整。要在服務(wù)器端正確的顯示圖像,必須根據(jù)圖片中的Exif信息調(diào)整方向。在將家里的小浪擺好Pose并通過(guò)各個(gè)方向的拍攝后,我寫(xiě)好了調(diào)整方向的函數(shù)。
所有的代碼,我已經(jīng)放到google code上,大家可以下載。這些代碼是MIT協(xié)議,可以隨意商用。
開(kāi)發(fā)以外
兼容性
由于各個(gè)平臺(tái)對(duì)CSS和HTML5 的支持差異很大,所以很難在全部平臺(tái)做到***,像之前提到過(guò)的,Android的CSS圓角毛邊問(wèn)題,Div切換時(shí)部分圖層不定期隱藏的問(wèn)題;另外 PhoneGap還有各種BUG和問(wèn)題,比如iOS應(yīng)用從后臺(tái)呼出時(shí)有短暫的白屏閃爍問(wèn)題;比如OPath里邊我使用了1.2版本,這個(gè)版本在iOS下拍照正常,但是在Android下呼叫不出攝像頭,換成1.0版本就可以,這說(shuō)明PhoneGap在平臺(tái)兼容性上問(wèn)題依然不少。
個(gè)人感覺(jué),在現(xiàn)階段,一個(gè)PhoneGap應(yīng)用要想做到***的體驗(yàn)并在各個(gè)平臺(tái)保持體驗(yàn)一致,難度非常大。不過(guò)如果能忍受一些小細(xì)節(jié),或者能做好優(yōu)雅降級(jí)的話,PhoneGap應(yīng)用是能超過(guò)很多原生應(yīng)用的。
代碼安全性
采用PhoneGap打包的應(yīng)用,不管是APK還是IPA,只要將擴(kuò)展名改為zip,解壓后在www目錄就可以得到這個(gè)應(yīng)用的全部源代碼。
這使得盜版成本非常的低,必要的時(shí)候需要對(duì)js進(jìn)行混淆。最可靠的方式是將部分核心邏輯放到云端,通過(guò)api使用。