添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
俊逸的电脑桌  ·  Vue ...·  1 年前    · 
不羁的苦瓜  ·  【mapbox】常用功能 ...·  1 年前    · 
精彩文章免费看

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

上次已经初步完成了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更新,以及方法调用的各种回调的问题就不在这里细述了,有兴趣的可以一起讨论。文章是原创的,如有雷同,视你盗版。