1. 程式人生 > >移動端混合開發(1):和H5的javascript互動

移動端混合開發(1):和H5的javascript互動

最近公司專案開發中涉及到了大量的混合開發,這裡開一個系列,把開發中的經驗和遇到的問題和大家分享下

講到移動端的混合開發,繞不開的一個話題就是原生和Js的互動,關於iOS、Android怎麼和js互動,網上的資料很多,這裡先簡單介紹幾個方法。

js部分

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>

    <script>
            function
iOSShowDialog() {
alert("iOS btn"); //showDialog 為iOS內定義好的方法 var params = {"title":"js 呼叫 iOS", "type":0, "callBack":"nativeCallBack"}; var paramsStr = JSON.stringify(params); alert("iOS params:" + paramsStr); window.webkit.messageHandlers.showDialog.postMessage(paramsStr); } function
androidShowDialog() {
alert("Android btn"); var params = {"title":"js 呼叫 Android", "type":1, "callBack":"nativeCallBack"}; var paramsStr = JSON.stringify(params); alert("iOS params:" + paramsStr); androidProxy.showDialog(paramsStr); } function
nativeCallBack(paramsStr) {
// var str:String = paramsStr; var params = JSON.parse(paramsStr); document.getElementById("info").innerHTML = params.message; }
</script> <body> <body> <div> <button id="iOSBtn" onclick="iOSShowDialog()">click me call iOS show dialog</button> </div> <div> <button id="AndroidBtn" onclick="androidShowDialog()">click me call Android show dialog</button> </div> <div> <textarea id="info">...</textarea> </div> </body> </body> </html>

簡單說一下,js裡一共定義了三個方法,前兩個iOSShowDialog,androidShowDialog看名字應該就能猜出來,是分別用來呼叫iOS和Android原生的方法的,第三個方法nativeCallBack是用來給原生回撥的。
這裡稍微多說一點,雖然js呼叫原生的方法是可以有返回值的,但是在設計這個構架時,還是建議使用非同步回撥的方式。因為在js和原生的通訊場景中,有很多是非同步流程,最典型的就是js向原生要一個數據用來在頁面中展示,但是這個資料原生是需要通過伺服器請求返回的。這個場景裡,很明顯函式返回值是沒有辦法滿足需求的。
頁面刷出來是這個樣子:
這裡寫圖片描述
js準備完畢

iOS部分

override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.

        // 建立配置
        let config = WKWebViewConfiguration()
        // 建立UserContentController(提供JavaScript向webView傳送訊息的方法)
        let userContent = WKUserContentController()
        // 新增訊息處理,注意:self指代的物件需要遵守WKScriptMessageHandler協議,結束時需要移除
        userContent.add(self, name: "showDialog");
        // 將UserConttentController設定到配置檔案
        config.userContentController = userContent;

        webView = WKWebView(frame: CGRect(x:0, y:0, width:self.view.bounds.width, height:300), configuration: config);

//        let url = Bundle.main.url(forResource: "demo", withExtension: "html");

        let url = URL(string: "http://你的域名/demo.html");

        webView?.navigationDelegate = self;

        webView?.uiDelegate = self;

//        webView?.loadFileURL(url!, allowingReadAccessTo: Bundle.main.bundleURL);

        webView?.load(URLRequest(url: url!));

        self.view.addSubview(webView!);
    }


func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if(message.name == "showDialog")
        {
            let paramsStr = message.body as! String;

            let paramsData = paramsStr.data(using: .utf8);

            let params = try! JSONSerialization.jsonObject(with: paramsData!, options: .allowFragments) as! Dictionary<String, Any>;

            let title = params["title"] as! String;

            let type = params["type"] as! Int;

            let callBack = params["callBack"] as! String;

            let alert = UIAlertController(title: title, message: "type is "+type.description, preferredStyle: .alert);
            alert.addAction(UIAlertAction(title: "確定", style: .default, handler: nil))
            self.present(alert, animated: true, completion: nil);


            let callBackParams = ["message":"iOS的回撥來了"];

            let callBackParamsStrData = try! JSONSerialization.data(withJSONObject: callBackParams, options: []);

            let callBackParamsStr = String(data: callBackParamsStrData, encoding: .utf8);

            let callBackFunc = callBack+"("+"'\(callBackParamsStr!)'"+")";

            webView?.evaluateJavaScript(callBackFunc, completionHandler: { (item, error) in
                print(item, error);
            })
        }
    }

這裡只上兩個核心的方法,iOS我沒有用UIWebView+JavascriptCore,而是直接用了WKWebView,畢竟WKWebView效率和記憶體消耗好很多。但是WKWebView貌似不支援JavascriptCore,它有自己的js互動方式,並且也很簡單:

// 建立配置
        let config = WKWebViewConfiguration()
        // 建立UserContentController(提供JavaScript向webView傳送訊息的方法)
        let userContent = WKUserContentController()
        // 新增訊息處理,注意:self指代的物件需要遵守WKScriptMessageHandler協議,結束時需要移除
        userContent.add(self, name: "showDialog");
        // 將UserConttentController設定到配置檔案
        config.userContentController = userContent;

這裡注意一下userContent.add(self, name: "showDialog");這裡的是向H5注入方法的過程,因此showDialog必須和js裡呼叫的方法名一致。並且,第一個引數填入的物件必須遵守WKScriptMessageHandler介面。
再來看響應,

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) 

剛才說了,userContent傳入的物件必須遵守WKScriptMessageHandler介面,上面的函式就是接口裡必須實現的方法。

if(message.name == "showDialog")

message物件的name屬性是方法名

let paramsStr = message.body as! String;

message 物件的body是方法的引數
這裡寫圖片描述
再來看回調,我在js傳給iOS的json裡添加了一個欄位callBack,因此,回撥只需要將callBack欄位裡的方法名取出,拼裝成一個函式呼叫語句,通過WKWebView呼叫js的方法,調一下就可以了。

let callBackParams = ["message":"iOS的回撥來了"];

            let callBackParamsStrData = try! JSONSerialization.data(withJSONObject: callBackParams, options: []);

            let callBackParamsStr = String(data: callBackParamsStrData, encoding: .utf8);

            let callBackFunc = callBack+"("+"'\(callBackParamsStr!)'"+")";

            webView?.evaluateJavaScript(callBackFunc, completionHandler: { (item, error) in
                print(item, error);
            })

注意這句,let callBackFunc = callBack+"("+"'\(callBackParamsStr!)'"+")";
剛才說了,拼裝成一句函式呼叫語句。剛開始,我拼出來的是這樣的:"nativeCallBack(["message":"iOS的回撥來了"])"
乍一看,沒毛病,但始終報錯,後來才意識到,我在js的回撥方法裡定義的引數是一個String,也就是說,我呼叫的語句引數必須有引號包圍。所以,正確的應該是這樣的:"nativeCallBack( ' ["message":"iOS的回撥來了"] ' )"
這裡寫圖片描述
嗯,沒毛病!

Android部分

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mWebView = (WebView) findViewById(R.id.webview);
        WebSettings webSettings = mWebView.getSettings();

        // 設定與Js互動的許可權
        webSettings.setJavaScriptEnabled(true);

        // 通過addJavascriptInterface()將Java物件對映到JS物件
        //引數1:Javascript物件名
        //引數2:Java物件名


        // 載入JS程式碼
        // 格式規定為:file:///android_asset/檔名.html

        mWebView.post(new Runnable() {
            @Override
            public void run() {
//                mWebView.loadUrl("file:///android_asset/demo.html");
                mWebView.loadUrl("http://你的域名/demo.html");

                mWebView.addJavascriptInterface(new AndroidtoJs(mWebView), "androidProxy");//AndroidtoJS類物件對映到js的test物件
            }
        });

    }

@JavascriptInterface
    public void showDialog(String dataStr)
    {
        JSONObject jsonObject = null;
        try {
            jsonObject = new JSONObject(dataStr);

            String title = jsonObject.getString("title");

            int type = jsonObject.getInt("type");

            Toast.makeText(AppApplication.getInstance(), title + "type:" + type, Toast.LENGTH_LONG).show();

            final String callBack = jsonObject.getString("callBack");

            Map<String,String> callBackParams = new HashMap<>();
            callBackParams.put("message", "這是從Android原生返回的資料");

            JSONObject paramsJsonObjct = new JSONObject(callBackParams);
            final String paramsJsonObjctStr = paramsJsonObjct.toString();

            Log.d("AndroidtoJs",paramsJsonObjctStr);

            wView.post(new Runnable() {
                @Override
                public void run() {
                    wView.evaluateJavascript("javascript:" + callBack + "('" + paramsJsonObjctStr + "')", new ValueCallback<String>() {
                        @Override
                        public void onReceiveValue(String value) {

                        }
                    });
                }
            });

        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

Android相對簡單點,直接用WebView就行,方式其實和iOS類似,向H5注入一個物件,物件中定義的方法就可以背js呼叫。mWebView.addJavascriptInterface(new AndroidtoJs(mWebView), "androidProxy");//AndroidtoJS類物件對映到js的test物件
注意,androidProxy就是你在js裡呼叫的物件名,AndroidtoJs是在Android專案裡定義的物件,裡面封裝了需要暴露給js的方法public void showDialog(String dataStr)。這個方法前一定要加上@JavascriptInterface
這裡寫圖片描述
回撥也很簡單,

wView.post(new Runnable() {
                @Override
                public void run() {
                    wView.evaluateJavascript("javascript:" + callBack + "('" + paramsJsonObjctStr + "')", new ValueCallback<String>() {
                        @Override
                        public void onReceiveValue(String value) {

                        }
                    });

這裡有一點注意的地方,Android規定呼叫webUiew物件的方法必須在同一個執行緒,不然會報錯。因此,在呼叫webView.loadUrl和webView.evaluateJavascript時,都加上了webView.post(new Runnable() {}

尾聲

第一篇就寫這些,有什麼問題望大家多多指教。