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+")"); }) });
