[轉] 移動開發中如何整合HTML 5和原生代碼

最近業界關於移動開發中的究竟使用原生代碼仍是HTML 5代碼的爭論依然持續。目前來講有三種方式去進行移動開發html

•    原生代碼,
•    hybrid(混合型)移動應用
•    移動Web app(使用HTML5)
使用HTML 5開發應用比起爲每一種不一樣的平臺從頭開始編寫代碼,是一種能跨不一樣平臺而代碼量較少的一種方式。在這種狀況下,大部分的用戶界面,甚至所有的界面均可以經過HTML 實現。html5

「Hybrid應用」一詞,指的是移動應用大部分是用HTML 5去編寫界面,而部分須要訪問特定設備功能的則使用原生代碼。大部分這些原生代碼並非天然生成可視的,而僅僅經過特定的轉換將數據返回給應用的HTML 5界面,再通過渲染返回給用戶。PhoneGap這個框架就提供了這樣的功能。android

大部分關於HTML 5的爭論並非着眼於討論它是否能勝任移動應用中的用戶體驗。Facebook創始人 Mark Zuckerberg在他的對Facebook在使用HTML 5中遇到的困難一文中做了相關評論。Facebook的一些問題能夠經過由Sencha Touch框架進行解決(參考SenchaTouch本身的一個例子Fastbook),也能夠經過LinkedIn在其infinite scrolling post一文中解決。此外,LinkedIn隨後還提供了使用更多的原生代碼解決關於轉換方向的問題。git

爲什麼不進行整合?github

與其使用所有的原生代碼或者HTML 5代碼去開發,爲何不將二者進行整合呢?使用混合型開發移動應用,能夠同時利用原生和HTML 5代碼開發用戶界面。這就能讓開發者在設計用戶界面的時候能使用最適合的工具。web

很明顯,對於開發用戶應用界面使用兩種或者更多的技術是有其缺點的。最重要的是必須讓開發者既掌握原生的開發又掌握HTML 5。使用原生代碼開發的用戶界面不大容易在其餘平臺使用,並須要從新開發。因爲這些技術須要更普遍的知識面和上面提到的困難,爲何還有人嘗試努力使用這 它?後端

HTML 富文檔界面瀏覽器

可能你的用戶體驗告訴你文檔能夠以富文本格式進行顯示更爲精彩。雖然在iOS上可使用NSAttributedString進行富本文格式化,但 這種文檔的結構並不適合遷移到其餘設備平臺中,也不能完整由HTML再現。若是文檔中須要使用如表格進行顯示,則象NSAttributedString 的解決方案是不能奏效的。考慮下面的一家基因檢測公司的iPad應用,以下圖,是一個混合型界面的例子:安全

請注意看上面的圖,很難從表面上分辨這到底哪部分是用原生代碼編寫的,哪部分是用HTML 5編寫的。這個界面中的右邊部分是可變的長度。表格是包含可擴展的行,徹底使用CSS3動畫效果,而頁面中其餘文字部分都是相同的高度。服務器

Documents such as this are what HTML does best. Trying to create something like this in native code would have entailed considerably greater effort, with little benefit to the user. In this application, the HTML5 is generated with a mustache template, via theGRMustache library for iOS.

像這樣的頁面使用HTML 5實現是最好的。若是嘗試使用原生代碼去實現則要花費很大功夫,並且對用戶好處很少。在這個應用中,經過使用爲iOS而設的GRMustache類庫去生 成相關的HTML 5模板。下圖中用顏色區別代表了哪一個部分是使用HTML5,哪一個部分使用原生代碼。

注意當用戶移動滾動條的時候,在右側頂部的文檔頭部會動態改變大小以顯示更多的內容,從而說明用戶界面之間是如何互動的。用戶能夠點在HTML 5實現部分的鏈接和按鈕,以彈出一個內置的Web瀏覽器以加載相關內容。在本文接下來的部分,會看到在原生代碼的界面和HTML 5的界面之間互相鏈接是很容易的事情。

一箭雙鵰的解決方法

正如咱們所看到的,能夠在同一個應用的屏幕中整合兩種不一樣的技術以提供無縫的體驗。你可能但願在iOS上使用 UINavigationController和與之相關的 UINavigationBar控件,或者在Android應用中使用action bar,而剩下的部分則使用HTML 5渲染。

下面是一個使用HTML 5設計的購物車應用體驗,但依然有保留原生的導航控件和用於頁面導航的tab工具條:

這裏,HTML 5內容其實是從服務端加載的,它在整個用戶體驗中控制着流程。有相關的API容許服務端生成的HTML去修改導航控件中的內容以適應瀏覽的過程。

你可能會發現原生的控件,如iOS中的MapKit等都提供了比HTML 5控件更好的性能體驗,在這種狀況下,應該使用HTML 5代碼去和iOS原生代碼層通訊,讓其返回內容並呈現。

上面的兩個圖描述了一個綜合運用原生和HTML 5的購物應用。用戶首先在地圖中選擇了商店地址,而後數據將已HTML 5的方式呈現,用戶並繼續購物。

一旦用戶完成了和地圖的交互,結果則會立刻傳遞到HTML 5頁面中以做進一步處理。原生「控件」能夠在任何平臺(iOS、Android、Windows等)中實現,這取決於應用是如何架構,而且能從HTML5 應用中調用。使用這個策略,HTML 5代碼將提供對整個應用流程的控制,而原生代碼則在後臺運行等到須要的時候提供數據,若是處理恰當,用戶是很難區分應用的哪個部分是使用HTML5,哪 一個部分是使用原生代碼。

更容易的應用更新

大多數人都知道爲了在iOs或者Android應用中更新應用,都必須從新經過相應平臺正常的渠道去從新提交。對於Ios,可能須要等待七天去得到 蘋果的批准,這就很難在當今移動應用的狂潮中知足用戶的迫切須要。若是經過HTML和Javascript的方法去給用戶移動體驗,則意味着能夠經過 Web去動態知足用戶的須要。

正如前文說的,因爲能夠經過Web的方法調用應用中的原生代碼,則看上去這實際上是一種」橋樑」的機制。PhoneGap就是經過PhoneGap plugin interface去實現。以前的購物商店的例子就是使用這個方法。使用這個方法可使得購物的流程若是改變後再也不須要走繁瑣的原生應用的發佈流程。若是 一個設計良好的界面可使得頁面和原生容器之間的通信變爲可能。

關於內容方面

對於來自Web的內容,一般最好能限制其經過這種橋接技術的流量。相似PhoneGap的內置的功能提供了不少設備的信息,其中一些可能會遠遠超出 應用程序的所須要。若是因爲某種緣由Web內容被泄露,好比跨站點腳本攻擊,你可能會忽然發現這些內容會以某種方式調用本地代碼。

基於這個緣由,當須要使用混合型移動應用的時候,應該使用你本身的橋接代碼,以嚴格控制哪些代碼能夠動態加載什麼樣的內容。一些平臺有本身的控制方 法去限制從Web viewer中對原生代碼的調用。在Android中,類必須明確本身註冊經過JavaScript訪問。在Android 4.2中,能夠進一步經過在須要從Web viewer中訪問的方法前添加@ JavaScriptInterface註解去限制其調用。

須要考慮的要點

•     若是用戶在線,直接從Web服務器容是很好的。請記住,移動應用的網絡鏈接問題。若是網絡鏈接中斷,會發生什麼事? 有的時候能夠經過AppCache實現一些離線功能,但這須要規劃。另外一種方法在稍後介紹,使用的是本地存儲。

•     託管在Web服務器上的內容須要必定的基礎設施。如今有更多的須要被視爲移動應用組成部分的「組件」。它再也不僅僅是一些經過AppStore部署的原生 代碼和一些後端的服務。必須當心的是在網站更新的時候,不要移動應用程序與現有的接口間發生衝突。請記住,網站的內容發生了變化,則必須更新移動應用程序 自己。出於這個緣由,可能須要對網站的內容進行必要的版本標記以區分其內容。

•   
蘋果公司對僅僅包裹在原生應用中的Web view這樣類型的應用持悲觀見解。若是你只是在一個原生移動應用中,僅僅包含一些Web內容,則可能在提交iOS的AppStore審批時不會獲得批 準。蘋果公司但願你的應用程序將使用的一些設備自身的功能,或者好歹使用下Mobile Safari。。想一想看,你的應用程序其實能夠集成相機功能、聯繫人列表、本地數據存儲,離線操做等,還要記得,這些Web view中提供的內容必須和原來應用提交的樣式等保持一致。

若是你建立了一個詞搜索遊戲,而後忽然開始在webview瀏覽器中有一些風馬牛不相及的內容,這將違反蘋果的條款,並可能在AppStore中下架。根據iOS開發者協議,每次的更新不能改變在提交appStore審覈時闡述的該應用的主要功能。

體驗應用更新過程

正如上文提到的,從web服務器加載內容再刷新本地的內容。在這種方法中,只要web內容更新,服務器上的Web內容(HTML,CSS和 JavaScript)會下載到移動應用程序客戶端。更新代碼就能夠改變用戶的界面,應用流程等,這無需開發人經過使用iOS App Store或Google Play去處理。

注意的是隻能更新HTML、CSS和Javascript,是不能更新原生代碼的。蘋果公司容許只要在iOs應用中在UIWebView控件的上下文中運行就能夠下載Javascript。

上圖的應用是運行在Android中。HTML的內容是下載到用戶的移動設備中並存儲的。注意應用提供了導航條的用戶體驗。下圖則是運行在iOs的情景,使用的是UINavigationController :

除了加快更新過程,這種傳輸應用程序的方式能確保用戶得到最新的代碼。在傳統的方法中,應用程序的更新是經過應用程序商店分發,並通知用戶有可用的 更新,儘管他們並不須要安裝最新的更新。不是每一個人都按期去更新應用程序。而在用戶瀏覽應用的時候,經過更新本地的HTML,CSS和 JavaScript正好解決這個問題。

動態更新本地內容的功能,其實是商業混合型移動應用開發平臺「Trigger.IO」的一部分。 Trigger.IO功能容許更新你移動應用的HTML部分。須要注意的是原生代碼不能以這種方式更新。可是,若是你應用程序的流程控制使用的 JavaScript和加強的本機代碼,那麼應用程序流程做發生變化的時候同步更新是可能的。

動態更新內容對開發和測試的好處

能動態更新一個移動應用,也能夠加速開發進程。 iOS和Android的原生開發過程通常須要在桌面計算機上對應用程序代碼進行編譯,而後轉移到測試設備中去。一些發開發者項目,如Icenium、 LiveSync或Ion則支持編譯應用的更新到雲端,而後只需下載已經在設備上運行的應用程序,這就完成了更新。

當代碼進行了更改,並保存到服務器上,只須要一個簡單的三個手指頭的手勢動做就能夠更新iOS設備上的應用程序。 Icenium也有相似的功能並可用於Android。 Adobe公司的PhoneGap經過一個被稱爲Hydration的特性讓更新變得可能。在這種狀況下,開發人員能夠經過他們的IDE整合到各類設備 上,而無需物理鏈接到這些移動設備,就能夠推送新的代碼。

當Hydration功能啓用的時候,每次啓動應用程序時,應用程序將檢查PhoneGap構建服務器去看是否有新版本可用。若是有,用戶將被提示升級到新的版本。這可使你的應用程序的質量獲得保證,以及更容易使用。你將沒必要擔憂簽署代碼更新、配置和測試等問題。

注意事項

雖然動態更新應用的功能很強,但記住並確保凡是來源於網絡的內容必須不能隨意調用設備的原生代碼。若是不這樣作,可能會致使你的用戶數據暴露給第三 方,所以違反與蘋果或Google的協議。此外,動態更新內容不該該改變應用原來的功能。不然會可能被蘋果或者Google下架應用。

溝通兩個世界的橋樑

iOS和Android都提供了能在原生應用中使用的Web view控件,接下來的例子中將講解如何在Web view中使用Javascript調用原生代碼以及原生代碼如何調用在Web view中的Javascript。

首先你必須使用PhoneGap(Apache Cordova),可使用PhoneGap 1.4 for iOS 和PhoneGap 1.9 for Android。本文並不從PhoneGap的基礎開始講解,所以讀者可能須要到PhoneGap官網瞭解一些相關知識。對於iOs能夠將 CordovaWebView 放到原生的viewController控件中,而Android中也可使用。這容許你的web代碼能訪問整個PhoneGap API而且訪問任何PhoneGap插件。固然能夠根據PhoneGap plugin API 去將應用的原生代碼暴露給CordovaWebView。

假設你不須要使用PhoneGap的所有功能而只是須要在原生和HTML5兩個世界中使用它們部分的功能,則也能夠本身去編寫代碼是實現這個橋樑。本文以前提到的三個應用都是使用「私有API」的方式去實現編碼的。

研究iOS中的UIWebView

在iOS中的UIWebView和相關的UIWebViewDelegate提供了相關的機制,並容許原生代碼和Javascript之間互相調用,下面準備了一個簡單的例子進行講解:

注意屏幕中的上半部分,淺灰色部分使用的是UIWebView進行渲染,而下半部分白色的使用的是原生代碼聲稱的。這個應用只是從Web view中傳遞JSON對象到原生代碼中,反之亦然,代碼和Android版本的能夠在 這個地址下載:
https://github.com/ptraeg/html5-in-mobile-apps

從Javascript中調用原生代碼

從Javascript中去調用原生代碼很簡單,只是在方法中帶有command和以JSON格式要傳遞的參數,代碼以下:

  1.  var nativeBridge = { 
  2.    invoke: function (commandName, args) { 
  3.       console.log(commandName + ": " + JSON.stringify(args, null, 2)); 
  4.       window.location = 'js-call:' + commandName + ':' + 
  5.                       encodeURIComponent(JSON.stringify(args)); 
  6.   } 
  7. }; 


上面的代碼嘗試去將瀏覽器導航到一個新的URL,但並非用傳統的http://或file://的URL。這裏使用的是自定義協議js-call:,而在Objective-C方面,代碼以下:

  1. (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType (UIWebViewNavigationType)navigationType 

在這個調用的委託中,能夠查詢傳入的URL是不是來自Javascript的調用:

  1. NSString *requestURLString = [[request URL] absoluteString]; 
  2. f ([requestURLString hasPrefix:@"js-call:"]) { 

接下來是判斷command的名字和JSON參數字符串:

  1.  NSArray *components = [requestURLString componentsSeparatedByString:@":"]; 
  2. NSString *commandName = (NSString*)[components objectAtIndex:1]; 
  3. NSString *argsAsString = [ (NSString*)[components objectAtIndex:2] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding ]; 

 既然咱們將參數做爲字符串,則能夠將其轉換爲NSDictionary對象:

  1.  NSError *error = nil; 
  2. NSData *argsData = [argsAsString dataUsingEncoding:NSUTF8StringEncoding]; 
  3. NSDictionary *args = (NSDictionary*)[NSJSONSerialization JSONObjectWithData:argsData options:kNilOptions error:&error]; 

最後,是時候調用正確的原生方法以處理Javascript調用的參數而且刷新原生UI:

  1. if ([commandName isEqualToString:@"updateNames"]) { 
  2.    [self updateNativeNameValuesWithFirstName:[args objectForKey:@"fname"] 
  3.                                     LastName:[args objectForKey:@"lname"]]; 

記得在經過js-call調用方法的時候,必須從委託中返回boolean值爲no,以防止Web view嘗試訪問URL。

iOS原生代碼中訪問Javascript

這種方法比較簡單,由於咱們沒有使用委託方法。咱們能夠簡單經過傳入要調用的JavaScript函數名和須要的參數做爲字符串,傳給UIWebView的stringByEvaluatingJavaScriptFromString方法就能夠了
在這裏,咱們從本地視圖中得到值而且經過NSDictionary將其變成一個JSON對象。

  1.  NSDictionary *namesDict = @{@"fname": self.fNameTextField.text,  
  2.                             @"lname": self.lNameTextField.text}; 
  3. NSError *error; 
  4. NSData *namesData = [NSJSONSerialization dataWithJSONObject:namesDict  
  5.                                                     options:0  
  6.                                                       error:&error]; 
  7. NSString *namesJSON = [ [NSString alloc] initWithData:namesData 
  8.                                              encoding:NSUTF8StringEncoding] ; 

既然已經將JSON做爲字符串傳遞,只須要格式化命令並傳遞到Web view中就能夠了。

  1. NSString *jsCommand = [NSString stringWithFormat:@"setNames(%@)", namesJSON]; 
  2. [self.webView stringByEvaluatingJavaScriptFromString:jsCommand]; 

在Android中使用Webview

一樣,下圖是使用Android的Webview實現上面iOS例子的效果:

一樣,上部分灰色的是由Web View生成的,下半部分的是原生代碼生成。

從Javascript代碼中調用原生代碼

Android Web VIEW有個好處就是能將所有的Java類暴露給Javascript,這就不用象iOS那麼麻煩了。首先只須要對Android Web view進行註冊設置就能夠了,代碼以下:

  1. @Override 
  2. public void onStart() { 
  3.    super.onStart(); 
  4.    WebSettings webSettings = webview.getSettings(); 
  5.    webSettings.setJavaScriptEnabled(true); 
  6.    webview.loadUrl("file:///android_asset/webviewContent.html"); 
  7.    webview.addJavaScriptInterface(new WebViewInterface(this), "Android"); 

注意上面最後一行代碼,註冊了一個特殊的類名爲WebViewInterface,它能夠經過Javacript去訪問,此外,該類還經過字符串「Android」指出其命名空間,還能夠測試是否註冊了Android命名空間,代碼爲:

  1. if (window.Android) { 
  2.    Android.updateNames(JSON.stringify(nameData)); 

而在Java服務端,WebViewInterface是一個標準的Java類,若是使用的是Android 4.2 SDK,則必須使用@JavaScriptInterface去註解要暴露給Javascript的方法。

  1. public class WebViewInterface { 
  2.    Context mContext; 
  3.  
  4.    /** Instantiate the interface and set the context */ 
  5.    WebViewInterface(Context c) { 
  6.        mContext = c; 
  7.    } 
  8.  
  9.    @JavaScriptInterface 
  10.    public void updateNames(String namesJsonString) { 
  11.       Log.d(getPackageName(), "Sent from webview: " + namesJsonString); 
  12.       try { 
  13.          JSONObject namesJson = new JSONObject(namesJsonString); 
  14.          final String firstName = namesJson.getString("fname"); 
  15.          final String lastName = namesJson.getString("lname"); 
  16.  
  17.          // When invoked from JavaScript, this is executed on a thread  
  18.          // other than the UI thread. 
  19.          // Because we want to update the native UI controls, we must  
  20.          // create a runnable for the main UI thread. 
  21.          runOnUiThread(new Runnable() { 
  22.             public void run() { 
  23.                fnameEditText.setText(firstName); 
  24.                lnameEditText.setText(lastName); 
  25.              } 
  26.          }); 
  27.       } catch (JSONException e) { 
  28.          Log.e(getPackageName(),  
  29.             "Failed to create JSON object from Web view data"); 
  30.       } 
  31.    } 

注意當Javascript調用這個類中的方法的時候,它們並非在主UI 線程中執行的,若是咱們的目的是更新UI部分,則必須建立新的runnable線程而且傳遞給主UI線程執行:

  1. runOnUiThread(new Runnable() { 
  2.    public void run() { 
  3.       fnameEditText.setText(firstName); 
  4.       lnameEditText.setText(lastName); 
  5.    } 
  6. }); 

而在原生代碼中調用Javascript則比較簡單。將數據用JSON的形式存放到字符串中而後就能夠給Javascript調用,注意要使用的是Javascript:協議:

  1. public void sendNamesToWebView() { 
  2.    JSONObject namesJson = new JSONObject(); 
  3.    try { 
  4.       namesJson.put("fname", fnameEditText.getText().toString()); 
  5.       namesJson.put("lname", lnameEditText.getText().toString()); 
  6.       webview.loadUrl( "JavaScript:setNames(" + namesJson.toString() + ")" ); 
  7.    } catch (JSONException e) { 
  8.       Log.e(getPackageName(),  
  9.         "Failed to create JSON object for Web view"); 
  10.    } 

WebView的缺點分析

和軟件開發的大多數狀況同樣,咱們必須審視WebView的一些缺點,特別是在製做混合移動應用時要注意:

•     不是全部的Web view都渲染成一樣的效果 在過去的兩年中,Web view的功能有明顯提升。然而,你的用戶羣可能不會運行Android或iOS的最新和最優秀的 版本。 Android平臺在Web view方面已廣泛落後於iOS。Google彷佛在從新努力來改善這種狀況,但那些停留在舊版本的Android(尤爲是Ice Cream Sandwich 4.x版本前)的用戶可能會發現Web view的性能尤爲是複雜的表格,其效果和在iOS上呈現的並不同。開發人員須要在Android 2.2,2.3和4.x上測試HTML5代碼以確保內容呈現的一致。
•    性能問題
Web view的性能一般是比不上原生代碼。然而,在許多狀況下,你可能會發現性能是徹底能夠接受的,特別是在當前的硬件飛速發展的狀況下。請記住,原 生代碼和代碼Web view部分調用的銜接性能會有必定的影響。但通常來講,不會要求在毫秒級完成調用,對於大多數應用,這是否是一個問題。
•    滾動條問題

用戶可能沒法區分Web view的滾動條滾動方式和應用程序原生代碼滾動方式的不一樣。這其中的一些問題能夠經過在新版本的WebKit中使用CSS3的 -webkit-overflow-scrolling:touch屬性去解決。你可能也想看看解決方案,如FTScroller 和 iScroll。
•    複雜性

正如前面提到的,若是將內容從Web服務器中下載到移動設備,必須確保擁有必要的基礎設施來支持。由於如今應用程序既有原生代碼,也有Web服務器和潛在 的後端服務參與,這會存在更多的故障點。你必須針對早期部署版本的應用程序作好兼容性測試,以確保當有應用更新的時候,現有版本的應用程序仍然運行。
•    遵照開發者協議

開發者的你必須熟悉每一類移動應用商店對web方面內容的限制。正如本文所提到的,特別是在iOS平臺上要額外注意,不然會不容許發佈甚至下架。
•    安全性

如前所述對於全部來源於web的內容都應該持有懷疑的態度,尤爲是若是將和你的本機原生代碼交互的。確保你只暴露你須要使用的原生代碼部分。若是不這樣作,可能會致使一些Web的惡意代碼能調用本機代碼。必須確保將Web的安全實踐考慮進來以確保Web代碼的安全調用。

鑑於一個混合型移動應用所帶來的靈活性,若是你發現Web view的性能在交互方面不夠優秀,則能夠在應用中編寫原生代碼。固然,編寫更多的本機代碼,則在平臺遷移的時候須要更多的從新改寫,這是一種利弊權衡。 總的來講,混合型移動應用的解決方案能讓你最大限度掌控應用的性能和靈活性。

小結
移動應用開發中使用HTML5目前是個有點爭議性的話題。然而,重要的是要做爲開發者,必須瞭解每一種解決方案的優缺點,瞭解什麼樣的策略是適合你的應用 程序,這些都是由需求決定的。儘管移動HTML5的解決方案在最近幾個月已經有一些負面的宣傳,但本文仍是講解了使用HTML5的好處。
不管採用什麼方法開發移動應用,必須保持性能的高效和安全性,而且儘早測試,採用多種設備測試。在這篇文章中,咱們已經看到,HTML5帶來了許多好處,特別是採用混合HTML 5和原生代碼開發,則給開發者更高的自由度選擇。


http://mobile.51cto.com/hot-415856_all.htm

相關文章
相關標籤/搜索