webview與JS的互動

龍恩0707發表於2015-06-03

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

相關文章