美文网首页
Android与H5交互框架实践(下)

Android与H5交互框架实践(下)

作者: Nicole被占用 | 来源:发表于2019-05-14 09:21 被阅读0次

    上次已经初步完成了Js调用Android的过程,但是是Android方法如果有返回参数,比如支付成功啊,获取用户token给到H5怎么处理?还是那个协议

    nicole://util:callbackId01/scan/params'sJson(参数的json)
    

    之前一篇文章有写到这个callbackId01。这个callbackId01其实只是做一个标记用的,js端为什么要传递这个过来?Android方法的完成怎么样才能让Js端知道完成了?
    不难想象,肯定是Android端方法执行完成,主动告知Js端,那么其实就是主动调用Js端的方法,并且传递对应的方法参数过去告知完成。而又因为这个方法有很多,不可能每个方法都去写对应一个对应的回调方法,所以需要进行封装。Js本地有一个类似Map的集合,key是方法的标记,value是方法。Js本地在调用Android的时候,如果是需要回调的,会把回调的那个标记也就是callbackId01传递给Android,Android在执行方法后,调用loadUrl(),或者evaluateJavascript(),执行相关方法的通用方法,Js端内部在通过map找到需要执行的方法,这样就完成了回调。有点绕,下面贴上代码,Js部分代码忽略。

    //这个代码之前有贴过,再贴一遍。
    @ModuleName("util")
    public class JsUtil implements JsModuleApi {
        /**
         * 二维码扫描
         */
        public void scanQRCode(WebView webView, String json, JavaCallback methodCallback, JsViewInterface jsViewInterface) {
            //伪代码
            QRCodeScanActivity.startQRCodeScanner(webView.getContext(), new OnQRScanListenerImpl() {
                @Override
                public void onScanQRCodeSuccess(String result) {
                    HashMap<String, Object> params = new HashMap<>(2);
                    params.put("resultData", result);
                    methodCallback.onSuccess(webView, params);
                }
            });
        }
    }
    

    这里着重看下JavaCallback 这个类,所传递的数据也是统一格式code,message,data的形式。

    public final class JavaCallback {
    
        private String responseId;
    
        public JavaCallback(String responseId) {
            this.responseId = responseId;
        }
    
        public void onSuccess(WebView webView) {
            onSuccess(webView, "");
        }
    
        public void onSuccess(WebView webView, Map<String, Object> paramsMap) {
            onCallback(webView, paramsMap, true, new ErrorMessage("成功"));
        }
    
        public void onSuccess(WebView webView, Object obj) {
            onCallback(webView, obj, true, new ErrorMessage("成功"));
        }
    
        public void onFail(WebView webView, ErrorMessage errorMessage) {
            onCallback(webView, "", false, errorMessage);
        }
    
        public void onFail(WebView webView, Map<String, Object> paramsMap, ErrorMessage errorMessage) {
            onCallback(webView, paramsMap, false, errorMessage);
        }
    
        private void onCallback(WebView webView, Map<String, Object> paramsMap, boolean isSuccess, ErrorMessage errorMessage) {
            JsMessage jsMessage = new JsMessage();
            JsMessage.JsData jsData = new JsMessage.JsData();
            jsMessage.setResponseId(responseId);
            if (paramsMap != null) {
                jsData.setResult(paramsMap);
            }
            jsData.setMessage(errorMessage.getErrorMessage());
            if (isSuccess) {
                jsData.setCode(1);
            } else {
                jsData.setCode(0);
            }
            jsMessage.setResponseData(jsData);
            String params = JsonUtil.objectToJson(jsMessage);
            String formatJsCode = String.format(JsConstants.JS_CODE, params);
            CLog.debug(params);
            webView.evaluateJavascript(formatJsCode, null);
        }
    
        private void onCallback(WebView webView, Object obj, boolean isSuccess, ErrorMessage errorMessage) {
            JsMessage jsMessage = new JsMessage();
            JsMessage.JsData jsData = new JsMessage.JsData();
            jsMessage.setResponseId(responseId);
            if (obj!=null) {
                jsData.setResult(obj);
            }
            jsData.setMessage(errorMessage.getErrorMessage());
            if (isSuccess) {
                jsData.setCode(1);
            } else {
                jsData.setCode(0);
            }
            jsMessage.setResponseData(jsData);
            String params = JsonUtil.objectToJson(jsMessage);
            String formatJsCode = String.format(JsConstants.JS_CODE, params);
            CLog.d(params);
            webView.evaluateJavascript(formatJsCode, null);
        }
    
        public String getResponseId() {
            return responseId;
        }
    }
    

    截止到现在这个CallbackId还没用上,其实在上一篇,反射执行模块方法的时候已经把id传过来了,看下之前部分模块的代码。

    //JsBridgeEngine类里面,callBackId是解析传递过来的uri里面的
    method.invoke(jsModuleApi, webView, decode, new JavaCallback(callBackId), jsViewInterface);
    
    

    同样的Android调用Js端的方法,也可以通过传递Android本地的回调Id给Js,然后Js执行相关代码后再主动调用Android的方法进行回调。这样JsBridge的桥梁就搭建起来了。

    H5的WebView的响应速度是不及原生的,尤其是页面显示的情况。通常如果网速比较慢,如果不做任何处理,基本就是一片空白。怎么去优化?html里面很多的Css,js,图片资源,等这些资源全部得到结果返回后原生的webview才会展示出界面。这就导致万一核心的资源没加载完成,界面就一直无法正确加载。那部分资源本地化就是比较好的方式。怎么让资源本地化?还是两个字“拦截”!为了区分方法调用和资源,我们用了WebChromeClient的onJsPrompt()拦截url调用方法,WebViewClient的shouldInterceptRequest拦截url加载资源。

    public WebResourceResponse shouldInterceptRequest(WebView webView, WebResourceRequest request) {
    

    上面的这个方法会在比如Js一些资源是用src的方式引入url资源的时候触发。
    如果是本地化的情况,比如某个js文件的引入

    <script type="text/javascript" src="https://xxxxxx/xxxx.js></script>
    

    这个时候其实可以把src换成

    <script type="text/javascript" src="nicole://xxxxxx/xxxx.js></script>
    

    这样会去触发WebViewClient的shouldInterceptRequest方法,第二个参数Request指的是网页发出的请求。截止目前,可以把我们Android看成一个小型服务器,网页之前要去远程服务器获取资源文件,现在改为从Android端获取。那下面就简单了,直接拦截其发出的url找到对应资源,通过文件流的形式返回资源给H5。下面还是代码。

            @Nullable
            @Override
            public WebResourceResponse shouldInterceptRequest(WebView webView, WebResourceRequest request) {
                Uri uri = request.getUrl();
                HashMap<String, String> map = new HashMap<>(2);
                String scheme = uri.getScheme();
                if (TextUtils.equals(scheme, JsConstants.SCHEMA)) {
                    String authority = uri.getAuthority();
                    WebResourceResponse response = null;
                    if (TextUtils.equals(authority, JsConstants.RESOURCE_LOCAL_ID)) {
                        try {
                            //目前就是图片
                            List<String> pathSegments = uri.getPathSegments();
                            String localId = pathSegments.get(0);
                            InputStream is = new FileInputStream(new File(LocalResSingleton.get().getLocalResource(localId)));
                            response = new WebResourceResponse(JsConstants.MIME_PNG, JsConstants.MIME_UTF_8, is);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        return response;
                    } else if (TextUtils.equals(authority, JsConstants.JS_ASSET)) {
                        String url = uri.toString();
                        CLog.debug("传过来的资源:" + url);
                        String assetFile = url.replace(JsConstants.JS_ASSET_ROOT_PATH, FileUtil.getWebDataPath());
                        // LocalResSingleton.get().getLocalAssetFile(url);
                        File file = new File(assetFile);
                        if (file == null || !file.exists()) {
                            return new WebResourceResponse("", JsConstants.MIME_UTF_8, null);
                        }
                        try {
                            InputStream is = new FileInputStream(file);
                            if (url.endsWith(JsConstants.SUFFIX_SVG)) {
                                response = new WebResourceResponse(JsConstants.MIME_SVG, JsConstants.MIME_UTF_8, is);
                            } else if (url.endsWith(JsConstants.SUFFIX_JS)) {
                                response = new WebResourceResponse(JsConstants.MIME_JS, JsConstants.MIME_UTF_8, is);
                            } else if (url.endsWith(JsConstants.SUFFIX_CSS)) {
                                response = new WebResourceResponse(JsConstants.MIME_CSS, JsConstants.MIME_UTF_8, is);
                            } else if (url.endsWith(JsConstants.SUFFIX_WOFF)) {
                                response = new WebResourceResponse(JsConstants.MIME_WOFF, JsConstants.MIME_UTF_8, is);
                                map.put(JsConstants.MIME_HEADER, JsConstants.MIME_DOT);
                                response.setResponseHeaders(map);
                            } else if (url.endsWith(JsConstants.SUFFIX_WOFF2)) {
                                response = new WebResourceResponse(JsConstants.MIME_WOFF2, JsConstants.MIME_UTF_8, is);
                                map.put(JsConstants.MIME_HEADER, JsConstants.MIME_DOT);
                                response.setResponseHeaders(map);
                            } else if (url.endsWith(JsConstants.SUFFIX_TTF)) {
                                response = new WebResourceResponse(JsConstants.MIME_TTF, JsConstants.MIME_UTF_8, is);
                                map.put(JsConstants.MIME_HEADER, JsConstants.MIME_DOT);
                                response.setResponseHeaders(map);
                            } else if (url.endsWith(JsConstants.SUFFIX_OTF)) {
                                response = new WebResourceResponse(JsConstants.MIME_OTF, JsConstants.MIME_UTF_8, is);
                                map.put(JsConstants.MIME_HEADER, JsConstants.MIME_DOT);
                                response.setResponseHeaders(map);
                            } else if (url.endsWith(JsConstants.SUFFIX_EOT)) {
                                response = new WebResourceResponse(JsConstants.MIME_EOT, JsConstants.MIME_UTF_8, is);
                                map.put(JsConstants.MIME_HEADER, JsConstants.MIME_DOT);
                                response.setResponseHeaders(map);
                            } else {
                                response = new WebResourceResponse("", JsConstants.MIME_UTF_8, is);
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        return response;
                    } else {
                        return super.shouldInterceptRequest(webView, request);
                    }
                } else {
                    return super.shouldInterceptRequest(webView, request);
                }
            }
    

    上面的代码再解释下,如果拦截到了,就把response返回。如果不需要拦截,就让系统自己去处理。另外每种类型的文件都要有一个mimeType。从WebResourceResponse的构造方法可以看出来。

    /**
         * Constructs a resource response with the given MIME type, encoding, and
         * input stream. Callers must implement
         * {@link InputStream#read(byte[]) InputStream.read(byte[])} for the input
         * stream.
         *
         * @param mimeType the resource response's MIME type, for example text/html
         * @param encoding the resource response's encoding
         * @param data the input stream that provides the resource response's data. Must not be a
         *             StringBufferInputStream.
         */
        public WebResourceResponse(String mimeType, String encoding,
                InputStream data) {
            mMimeType = mimeType;
            mEncoding = encoding;
            setData(data);
        }
    

    MimeType我给大家整理下,这里自己看下就好。

        //js mime type
        public static final String MIME_JS = "application/javascript";
        public static final String MIME_CSS = "text/css";
        public static final String MIME_PNG = "image/png";
        public static final String MIME_SVG = "image/svg+xml";
        public static final String MIME_WOFF = "application/x-font-woff";
        public static final String MIME_WOFF2 = "application/x-font-woff2";
        public static final String MIME_TTF = "application/x-font-truetype";
        public static final String MIME_OTF = "application/x-font-opentype";
        public static final String MIME_EOT = "application/vnd.ms-fontobject";
        public static final String MIME_HEADER = "Access-Control-Allow-Origin";
        public static final String MIME_DOT = "*";
        public static final String MIME_UTF_8 = "UTF-8";
    

    关于资源还有几个问题说下
    1.你的当前网页的网址如果是https的但是资源是http的话,这样是不允许的。
    报错一般如下Mixed Content: The page at ’ was loaded over HTTPS, but requested an insecure resource ‘http://xxxxxx. This request has been blocked; the content must be served over HTTPS.
    解决方式:

    WebSettings settings = this.getSettings();
    settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
    

    官方不太建议这种方式,会引发安全问题,所以尽量让H5那边统一,而且IOS端好像是没有混用的,我们项目最后是统一了。
    2.跨域问题。报错如下: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://xxxxxxxx.ex.com is therefore not allowed access. 问题在网上搜了很多都没怎么搜到,都是说调用下面的两个api就好了。如果是android16以下的要做一个反射执行。

    settings.setAllowFileAccessFromFileURLs(true);
    settings.setAllowUniversalAccessFromFileURLs(true);
    

    我试了很多遍都没办法成功。最后想了想,既然本地是个服务器,那只能在response上面做文章了,我看了下response对象里面还真有一个api,关于header的。那么久比较简单了。

    map.put("Access-Control-Allow-Origin","*")
    response.setResponseHeaders(map);
    

    暂时先写到这里吧,连同上篇文章,只是提供了一些思路,和遇到的几个问题分享,具体的项目工程还涉及到h5的版本控制,本地api更新,以及方法调用的各种回调的问题就不在这里细述了,有兴趣的可以一起讨论。文章是原创的,如有雷同,视你盗版。

    相关文章

      网友评论

          本文标题:Android与H5交互框架实践(下)

          本文链接:https://www.haomeiwen.com/subject/gyjdaqtx.html