webview與JS的互動
一:hybird app, web app 和 native app 的區別
Web App | Hybird App | 混合Native App | |
開發成本 | 低 | 中 | 高 |
維護更新 | 簡單 | 簡單 | 複雜 |
體驗 | 差 | 優 | 優 |
跨平臺 | 優 | 優 | 差 |
Native App是一種基於智慧手機本地作業系統如IOS,Android等並運用原生程式編寫執行的第三方運用程式,也叫本地App。
Web App 是針對Iphone,Android優化後的web站點,前端使用的技術是:html5,css,javascript等,伺服器端技術是java,php,asp等。需要注意的是web app開發還是比較有限的。因為Web APP開發不能整合裝置的核心功能,比如發文字資訊,也不能充分使用APP Store進行更新。但是Web APP開發也有其優勢所在。
首先它解決了iphone APP的可擴充套件性問題,因為它是可以跨平臺使用的。比如你開發了一款Web App,那麼它既可以在手機iphone上使用,也可以再平板ipad上使用,而不像iphone APP那樣只針對某個平臺。
其次web APP也繞開了APP store嚴格的提交和更新審查規則。眾所周知,隨著APP store中的APP逐漸增多,APP store也推出了一系列的提交和審查規則,可謂相當之嚴格。而web APP則繞開了這些提交和更新審查規則,從而使得web APP的升級和維護變得更容易。因為它是一個獨立的站點,而不是依附於app store上的,不管是升級還是維護在客戶端進行即可,無需提交稽核。
Hybird App通常分為三種型別:多view混合型,單View混合型,web主體型。
1. 多view混合型:
即Native View和web View獨立展現,交替出現。目前常見的Hybird App是Native View與web View交替出現,這種應用混合邏輯相對簡單,即在需要的時候,將webView當成一個獨立的view(Activity)執行起來,在webview內完成相關的展示操作。這種移動運用主體通常是 Native App, web技術只起到補充作用。開發難度和Native App相當。
2. 單view混合型:
即在同一個View內,同時包括Native View和Web View。互相之間是覆蓋(層疊)的關係。這種Hybrid App的開發成本較高,開發難度較大,但是體驗較好。如百度搜尋為代表的單View混合型移動應用,既可以實現充分的靈活性,又能實現較好的使用者體驗。
3. Web主體型:
即移動應用的主體是Web View,主要以網頁語言編寫,穿插Native功能的Hybrid App開發型別。這種型別開發的移動應用體驗相對而言存在缺陷,但整體開發難度大幅降低,並且基本可以實現跨平臺。Web主體型的移動應用使用者體驗的好壞,主要取決於底層中介軟體的互動與跨平臺的能力。國外的appMobi、PhoneGap,國內的AppCan和Rexsee都屬於Web主體型移動應用中介軟體。其中Rexsee不支援跨平臺開發。appMobi和PhoneGap除基礎的底層能力更多是通過外掛(Plugins)擴充套件的機制實現Hybrid。而AppCan除了外掛機制,還提供了大量的單View混合型的介面來完善和彌補Web主體型Hybrid App體驗差的問題,接近Native App的體驗。
以上的知識點是從這邊參考的: http://www.gtuanb.com/a/yd/2013/1231/127.html
二:Android webview與js的互動方式
1. 關於webview。
我們知道目前android市場上的一些應用採用的開發方式分為三種:Native App,web App,Hybird App 。下面介紹Hybird App中實現的主要技術native元件與JS的資料互動的理解。
Android API中提供了webview元件來實現對html渲染,所謂的HybridApp開發方式即是彙集了HTML5、CSS3、jS的相關開發技術,以及資料交換格式json/XML。 下面是Android_webview 與 JS互動的步驟如下:
1. 新建一個webview的佈局webview.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <WebView android:id="@+id/webview" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
如果應用中需要用到多個webview的介面,那隻要寫一個佈局就夠了。
2. 在activity中初始化webview
1. 初始化佈局
setContentView(R.layout.exam); webView = (WebView) findViewById(R.id.webview); webView.setScrollBarStyle(0);//滾動條風格,為0指滾動條不佔用空間,直接覆蓋在網頁上
2. 新增設定,使js程式碼能執行
WebSettings setting = webView.getSettings(); setting.setJavaScriptEnabled(true); setting.setJavaScriptCanOpenWindowsAutomatically(true); setting.setAllowFileAccess(true);// 設定允許訪問檔案資料 setting.setSupportZoom(true); setting.setBuiltInZoomControls(true); setting.setJavaScriptCanOpenWindowsAutomatically(true); setting.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); setting.setDomStorageEnabled(true); setting.setDatabaseEnabled(true); setting.setDefaultTextEncodingName("GBK");//設定字元編碼 webView.addJavascriptInterface(new AndroidForJs(this), "JavaScriptInterface");
設定完這些後會發現在除錯js程式碼時還是不能彈出alert對話方塊調式程式碼,上網查了之後發現是要新增如下程式碼:
webView.setWebChromeClient(new WebChromeClient());
3. 裝載html5頁面
本地介面:webView.loadUrl("file:///android_asset/test.html");,其中test.html頁面是放在assets資料夾下。
線上介面:webView.loadUrl("http://www.baidu.com");
3. 具體的互動如下:
(1)js呼叫native程式碼
上面的程式碼中 webView.addJavascriptInterface(new AndroidForJs(this), "JavaScriptInterface");
其中AndroidForJs就是專門用來放js呼叫native的程式碼的,"JavaScriptInterface"就是js呼叫AndroidForJs時的名稱,先寫一個AndroidForJs.java程式碼如下:
public class AndroidForJs { private Context mContext; private String cmdCode, resultKey; private long visitTime; public AndroidForJs(Context context) { this.mContext = context; } // 以json實現webview與js之間的資料互動 public String jsontohtml(String abc) { JSONObject map; JSONArray array = new JSONArray(); try { map = new JSONObject(); map.put("name", abc); map.put("age", 25); map.put("address", abc); array.put(map); map = new JSONObject(); map.put("name", "jacky"); map.put("age", 22); map.put("address", "中國北京"); array.put(map); map = new JSONObject(); map.put("name", "vans"); map.put("age", 26); map.put("address", "中國深圳"); map.put("phone", "13888888888"); array.put(map); } catch (JSONException e) { e.printStackTrace(); } return array.toString(); } }
上面的都是android(java)方法的配置項,前端不需要做任何事情。下面如下方法是前端呼叫native方法。
然後在js中可以用如下方式呼叫native方法
var result = JavaScriptInterface.jsontohtml("uyiyu");
其中"uyiyu"就是js傳給native的引數,需要的話就把引數傳過去,不需要的話就不傳。看具體的需求。其中jsontohtml是java中的一個方法。也可以改成其他的方法名。
result是native方法返回的值.一般是返回前端json資料,但是使用這種方式有問題,前端拿不到返回的資料,我們可以接著往下看。
注意:專案中遇到的問題是如果呼叫的native方法如果是一個向伺服器發起請求的方法,如下:
public String jsontohtml () { String result = null; cmdCode = "123"; visitTime = System.currentTimeMillis() / 1000; resultKey = Utils.getResultKey(cmdCode, visitTime, mContext); String key = Utils.getKey(cmdCode, visitTime, mContext); final String url = Utils.getMainUrl(key, cmdCode, visitTime, AllServerPort.URL_GET_EQUIPMENT, mContext); LogUtil.d(url); new HttpGetData(mContext, new CallBack() { @Override public void handlerData(String result) { // TODO Auto-generated method // stub LogUtil.d("-----RESPONSE------" + result); Map<String, String> backMsg = Utils.parseResponseResult( mContext, result, cmdCode, visitTime, resultKey); if (backMsg.get(Constant.BACK_FLAG).equals("1")) { String body = backMsg.get(Constant.BACK_BODY); JSONObject jsonBody; JSONArray jsonArray = null; try { jsonBody = new JSONObject(body); jsonArray = jsonBody.getJSONArray("dispatchKitList"); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } result = jsonArray.toString(); LogUtil.d("------------success--------------" + jsonArray.toString()); } } }, url).start(); return result; }
這時如果還用var result = JavaScriptInterface.jsontohtml ();這個方法會發現獲取的result是null的,這是因為java中向伺服器請求後到伺服器返回結果進入回撥函式handlerData是需要一段時間的,而js沒有等待這段時間就從java獲取了返回值,這時result還沒被賦值,就為空了,解決的方法只能是native從伺服器端獲取到資料後再呼叫js的方法把結果傳給js。所以上述方法改為
public String jsontohtml () { String result = null; cmdCode = "123"; visitTime = System.currentTimeMillis() / 1000; resultKey = Utils.getResultKey(cmdCode, visitTime, mContext); String key = Utils.getKey(cmdCode, visitTime, mContext); final String url = Utils.getMainUrl(key, cmdCode, visitTime, AllServerPort.URL_GET_EQUIPMENT, mContext); LogUtil.d(url); new HttpGetData(mContext, new CallBack() { @Override public void handlerData(String result) { // TODO Auto-generated method // stub LogUtil.d("-----RESPONSE------" + result); Map<String, String> backMsg = Utils.parseResponseResult( mContext, result, cmdCode, visitTime, resultKey); if (backMsg.get(Constant.BACK_FLAG).equals("1")) { String body = backMsg.get(Constant.BACK_BODY); JSONObject jsonBody; JSONArray jsonArray = null; try { jsonBody = new JSONObject(body); jsonArray = jsonBody.getJSONArray("dispatchKitList"); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } Message message = MyEquipmentActivity.handler.obtainMessage(); message.what = Constant.HANDLER_SHOW_EQUIPMENT; message.obj = jsonArray.toString(); MyEquipmentActivity.handler.sendMessage(message); LogUtil.d("------------success--------------" + jsonArray.toString()); } } }, url).start(); return result; }
Handler中呼叫js方法
public static Handler handler = new Handler() { @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub super.handleMessage(msg); String handlerMsg = ""; if (msg.obj != null) { handlerMsg = msg.obj.toString(); } switch (msg.what) { case Constant.HANDLER_SHOW_EQUIPMENT: webView.loadUrl("javascript:getEquipmentSuccess('" + handlerMsg + "')"); break; default: break; } } };
如上程式碼:webView.loadUrl("javascript:jsontohtmlSuccess('" + handlerMsg + "')");
js中呼叫native方法拿JSON資料最終方案:
前端只需要如下呼叫即可:
function jsontohtmlSuccess (json) { var data = eval("("+json+")");//解析json字串 // data 就是我們從開發那邊拿回來的json資料了。 }
2) native呼叫js方法
常見的方法是
不帶引數:webView.loadUrl("javascript:submit()");
帶引數:webView.loadUrl("javascript:getListSuccess('" + handlerMsg + "')");
目前程式中使用的方法就是這個,但是因為用這個方法無法獲取js return的引數,所以需要來回互相呼叫才能完成一次互動,比較麻煩。
3) 即上面可知:開發有2種(帶引數和不帶引數)方法呼叫前端程式碼,如下:
不帶引數:webView.loadUrl("javascript:submit()");
帶引數:webView.loadUrl("javascript:getListSuccess('" + handlerMsg + "')");
其中submit是我們javascript中的一個方法名稱。如下可以這樣寫程式碼:
function submit(){ var result = {“json”:””}; // json資料 JavaScriptInterface.submitResult(result); }
Json資料使用result變數儲存起來,之後使用JavaScriptInterface.submitResult(result)方法呼叫即可。submitResult是java方法,不用管!開發通過上面的程式碼就可以拿到我們前端的返回資料。
綜合所述:
1. js中呼叫native方法拿JSON資料最終方案:
前端只需要如下呼叫即可:
function jsontohtmlSuccess(json) { var data = eval("("+json+")");//解析json字串 // data 就是我們從開發那邊拿回來的json資料了。 }
伺服器端需要有這個方法:
webView.loadUrl("javascript:jsontohtmlSuccess('" + handlerMsg + "')");
2. JS中的資料返回給native端。如下程式碼:
function submit(){ var result = {“json”:””}; JavaScriptInterface.submitResult(result); }
伺服器端 需要有這個呼叫方法:
webView.loadUrl("javascript:submit()");
比如現在html5頁面有一個按鈕btn,點選按鈕btn後,需要把資料傳遞給native端;程式碼如下:
<div id=” bookidA”> bookidA </div> Var bookidA = document.getElementById(“bookidA”); bookidA.addEventListener(‘click’,function(e){ e.preventDefault(); submit(); }); function submit(){ var result = {“json”:””}; JavaScriptInterface.submitResult(result); }
三: JS與iOS Native Code互調方法
為大家介紹一個優秀的國人開發開源小專案:WebViewJavascriptBridge。它優雅地實現了在使用UIWebView時JS與ios 的Objective-C nativecode之間的互調,支援訊息傳送、接收、訊息處理器的註冊與呼叫以及設定訊息處理的回撥。它是連線UIWebView和Javascript的bridge。
如下JS程式碼實現connectWebViewJavascriptBridge
// 連線html function connectWebViewJavascriptBridge(callback) { if (window.WebViewJavascriptBridge) { callback(WebViewJavascriptBridge) } else { document.addEventListener('WebViewJavascriptBridgeReady', function() { callback(WebViewJavascriptBridge) }, false) } } connectWebViewJavascriptBridge (function(bridge) { // init方法是把資料傳給開發 data是前端需要傳遞的資料 // 再呼叫responseCallback(data) bridge.init(function(message, responseCallback) { var data = {“json”:””}; responseCallback(data); }); // registerHandler 這個方法是從ios拿到資料 給前端。其中testJavascriptHandler要與開發那邊名字對應上 bridge.registerHandler('testJavascriptHandler', function(data, responseCallback) { var data = eval("("+data+")"); }) });