zoukankan      html  css  js  c++  java
  • Android中Native和H5交互

    1.概述

        时至今日,H5的跨平台性越发凸显优势,一套代码适配android、ios,既能减少开发成本,又便于更新与维护.但是native的性能体验也确实更佳,尤其体现在复杂界面和频繁变化的界面上.事实上,移动平台native+h5的开发模式不是什么新鲜事了,各种框架层出不穷,主要目的就是为了使native与h5交互更加便捷高效,而在Android中必然需要WebView作为载体来展示H5内容和进行交互.

    2.交互方式

    • 传统的JSInterface:使用Android原生的javascriptInterface来进行js和java的通信.

      Native Invoke Js                                                                                                                                                                                                                

      WebSettings webSettings = webView.getSettings();
      //打通js通道 WebSettings webSettings = webView.getSettings();
      webSettings.setJavaScriptEnabled(true);
      webView.addJavascriptInterface(new JsInterface(), "android");
      //然后通过WebView的addJavascriptInterface方法去注入一个自定义的interface。
      public class JsInterface {
      @JavascriptInterface
      public void showToast(String toast) {
      Toast.makeText(MainActivity.this, toast, Toast.LENGTH_SHORT).show(); log("show toast success");
      }
      public void log(final String msg){
      webView.post(new Runnable() {
      @Override
      public void run() {
      webView.loadUrl("javascript: log(" + "'" + msg + "'" + ")");
      }
      });
      }
      }
      webView.addJavascriptInterface(new JsInterface(), "android");
      webView.loadUrl("file:///android_asset/interact.html");                                      

      @JavascriptInterface 是为了修复4.2版本之前的addjavascriptInterface接口引起的漏洞,这个漏洞曾导致恶意网页通过Js方法遍历刚刚通过addjavascriptInterface注入进来的类的所有方法从中获取到getClass方法,然后通过反射获取到Runtime对象,进而调用Runtime对象的exec方法执行一些操作.于是,交互如果按照这种方式的话,就得区分4.2之前与之后.         Js Invoke Native

      function showToast(toast){
      javascript:android.showToast(toast); //这里的android是上面java代码命名的
      }
      function log(msg){ console.log(msg); }

                                                                                                                                                                                                              

    • JSBridge的方式:其实就是通过重写WebView中WebChromeClient类的onJsPrompt()方法来进行js和java的通信                                                                

      对WebView熟悉的话,肯定知道Js中对应的window.alert()window.confirm()window.prompt()这三个方法的调用在WebChromeClient中都有对应的回调方法,分别为:
      onJsAlert()onJsConfirm()onJsPrompt(),对于它们传入的message,都可以在相应的回调方法中接收到,所以,对于Js调Native方法, 可以借助这个信道,和前端协定好一段特定规则的message,这个规则中应至少包含这些信息:

      所调用Native方法所在类的类名
      所调用Native的方法名
      Js调用Native方法所传入的参数

      基于这些信息,定义一个自己的协议。比如规定sheme,path等等。比如:

    scheme://host:port/path?query对应的协定prompt传入message的格式为:jsbridge://class:port/method?params

      这样,前端和app端协商好后,前端需要通过Js调用Native方法来获取一些信息或功能,就只需要按照协议的格式把需要调用的类名、方法名、参数放入对应得位置即可,而 会在onJsPrompt方法中接收到,所以根据与前端协定好的协议来进行解析, 可以用一个Uri来包装这段协议,然后通过Uri:getHost、getPath、getQuery方法获取对应的类名,方法名,参数数据,最后通过反射来调用指定类中指定的方法.(port的作用是为了标识Js中的回调function,当Js调用Native方法时,会得到本次调用的port号)

    自动生成port和绑定function回调的Js代码如下:

     
    generatePort: function () {
        return Math.floor(Math.random() * (1 << 50)) + '' + increase++;
    },
    //调用Native方法
    callMethod: function (clazz, method, param, callback) {
        var port = PrivateMethod.generatePort();
        if (typeof callback !== 'function') {
            callback = null;
        }
        //绑定对应port的function回调函数
        PrivateMethod.registerCallback(port, callback);
        PrivateMethod.callNativeMethod(clazz, port, method, param);
    },
    onComplete: function (port, result) {
        //把Native返回的Json字符串转为JSONObject
        var resultJson = PrivateMethod.str2Json(result);
        //获取对应port的function回调函数
        var callback = PrivateMethod.getCallback(port).callback;
        PrivateMethod.unRegisterCallback(port);
        if (callback) {
            //执行回调
            callback && callback(resultJson);
        }
    }

    数据的返回格式为Json字符串,基本格式为:

     
    resultData = {
        status: {
            code: 0,//0:成功,1:失败
            msg: '请求超时'//失败时候的提示,成功可为空
        },
        data: {}//数据,无数据可以为空
    };

    所以符合Js调用的Native方法格式为:

     
    public static void ***(WebView webView, JSONObject data, JsCallback callback) {
    	//get some info ...
    	JsCallback.invokeJsCallback(callback, true, result, null);
    }
    

    判断Js调用的方法是否符合该格式的代码为,符合则存入一个Map中供Js调用:

     
    private void putMethod(Class<?> clazz) {
        if (clazz == null)
            return;
        ArrayMap<String, Method> arrayMap = new ArrayMap<>();
        Method method;
        Method[] methods = clazz.getDeclaredMethods();
        int length = methods.length;
        for (int i = 0; i < length; i++) {
            method = methods[i];
            int methodModifiers = method.getModifiers();
            if ((methodModifiers & Modifier.PUBLIC) != 0 && (methodModifiers & Modifier.STATIC) != 0 && method.getReturnType() == void.class) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes != null && parameterTypes.length == 3) {
                    if (WebView.class == parameterTypes[0] && JSONObject.class == parameterTypes[1] && JsCallback.class == parameterTypes[2]) {
                        arrayMap.put(method.getName(), method);
                    }
                }
            }
        }
        mArrayMap.put(clazz.getSimpleName(), arrayMap);
    }
    

    对于有返回值的方法,并不需要设置它的返回值,因为方法的结果最后是通过JsCallback.invokeJsCallback来进行对Js层的回调.

    看图:

    • UrlRouter:UrlRouter是一个通过url来让前端唤起native页面的框架。不过,如果协议定义的合理,它可以让前端,Android和iOS三端有一个高度的统一,十分方便

    通过WebViewClient类的shouldOverrideUrlLoading方法去拦截前端写的url,发现如果是符合定义的UrlRouter协议的话,就跳转到相应的页面。

    eg.前端一个按钮<input type="button" value="login" onclick="javascript:location.href='http://www.baidu.com/'">

    java代码中这样:

     
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        parserURL(url); //解析url,如果符合跳转native界面的url规则,则跳转native界面
        return super.shouldOverrideUrlLoading(view, url);
    }

    这种方式没怎么用过,也就不细说了.

    3.总结

    H5用Webview加载的方式,往往还涉及到WebView的各种安全性、兼容性的问题,再比如易OOM,Webview许多方法并不按正常时机加载;这些坑都是需要踩过填过之后才能积累经验的.上述3种方式,都有自己的适用场景,比如个别页面需要加载h5首推第一种,app中有大量的h5交互那么就直接用第二种吧,对于一些平台统一的特殊要求的话就用UrlRouter的方式了.

  • 相关阅读:
    函数指针和指针函数和回调函数以及函数指针数组
    C语言中的结构体,结构体数组
    linux中的shell脚本编程
    回车和换行在linux下和windows下
    内存的段式管理和页式管理,逻辑地址-虚拟地址-物理地址
    [CSAPP-II] 链接[符号解析和重定位] 静态链接 动态链接 动态链接接口
    c语言中函数调用的本质从汇编角度分析
    运算符优先级
    Redis实战经验及使用场景
    RESTful API 设计最佳实践【转】
  • 原文地址:https://www.cnblogs.com/fuyaozhishang/p/6582616.html
Copyright © 2011-2022 走看看