Android H5容器整理

本文详细介绍了在Android中实现H5容器的一系列关键技术和实践,包括如何设计JSBridge、实现URL拦截与重定向、资源拦截与离线、获取JS日志、接口预取、Cookie同步、注入JSBridge、唤起其他APP、下载apk、离线包设计、自定义错误页面、页面加载超时、性能统计、任务栈多开、MessageChannel通信、WebView安全问题、V8引擎使用以及图片和网络请求的缓存策略。内容深入且全面,是Android H5集成开发的宝贵参考资料。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.如何实现和设计一套JSBridge?

前端JS调用native的方式有很多种,或者说android有很多种方式可以拦截或者获取到JS的行为。如下使用onConsoleMessage的方式,来设计一个简单的JSBridge:

  • 前端代码片段
(function () {
   
  var callbackArr = {
   };
  window.XJSBridge = {
   
    //JS调动native
    callNative: function (func, param, callback) {
   
      //生成调用的序列号Id
      var seqId = "__bridge_id_" + Math.random() + "" + new Date().getTime();
      //保存回调
      callbackArr[seqId] = callback;
      //生成约定的JSBridge消息
      var msgObj = {
   
        func: func,
        param: param,
        msgType: "callNative",
        seqId: seqId,
      };
      //打印约定的日志
      console.log("____xbridge____:" + JSON.stringify(msgObj));
    },
    //native调用JS的方法
    nativeInvokeJS: function (res) {
   
      //解析native的消息
      res = JSON.parse(res);
      //判断是否是callback消息
      if (res && res.msgType === "jsCallback") {
   
        //获取之前保持的回调方法
        var func = callbackArr[res.seqId];
        //delete callbackArr[res.seqId];
        //调用JS的回调
        if ("function" === typeof func) {
   
          setTimeout(function () {
   
            //调用js的回调
            func(res.param);
          }, 1);
        }
      }
      return true;
    },
  };
  document.dispatchEvent(new Event("xbridge inject success..."), null);
  console.log("xbridge inject success...");
})();

如上代码就是在window对象上挂载一个XJSBridge对象,可以通过callNative函数来调用android的native方法,其原理就是JS调用console.log打印一行约定的日志(Bridge协议),android端通过WebChromeClient的onConsoleMessage方法,获取到打印的日志,然后针对协议解析出对应的协议。

  • 其中WebChromeClient的代码如下

public class XWebChromeClient extends WebChromeClient {
   

    private WebViewPage mWebViewPage;

    public XWebChromeClient setWebViewPage(WebViewPage mWebViewPage) {
   
        this.mWebViewPage = mWebViewPage;
        return this;
    }

    @Override
    public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
   

        if (ConsoleMessage.MessageLevel.LOG.equals(consoleMessage.messageLevel())) {
   
            XLog.d("onConsoleMessage:" + consoleMessage.message()
                    + ",line = " + consoleMessage.lineNumber()
                    + ",sourceId = " + consoleMessage.sourceId()
                    + ",messageLevel = " + consoleMessage.messageLevel());
            //log级别的日志
            if (mWebViewPage.handleMsgFromJS(consoleMessage.message())) {
   
                return true;
            }
        } else if (ConsoleMessage.MessageLevel.ERROR.equals(consoleMessage.messageLevel())) {
   
            //ERROR级别的日志
            XLog.e("onConsoleMessage:" + consoleMessage.message()
                    + ",line = " + consoleMessage.lineNumber()
                    + ",sourceId = " + consoleMessage.sourceId()
                    + ",messageLevel = " + consoleMessage.messageLevel());
        } else {
   
            XLog.w("onConsoleMessage:" + consoleMessage.message()
                    + ",line = " + consoleMessage.lineNumber()
                    + ",sourceId = " + consoleMessage.sourceId()
                    + ",messageLevel = " + consoleMessage.messageLevel());
        }
        return super.onConsoleMessage(consoleMessage);
    }
}

这里的mWebViewPage会调用到XJSBridgeImpl的handleLogFromJS方法,在这里针对JSBridge进行解析,比如:


public class XJSBridgeImpl {
   

    private WebView mWebView;

    public XJSBridgeImpl(WebView view) {
   
        mWebView = view;
    }

    private static String XJSBRIDGE_HEADER = "____xbridge____:";

    public boolean handleLogFromJS(String log) {
   
        if (log != null && log.startsWith(XJSBRIDGE_HEADER)) {
   
            String msg = log.substring(XJSBRIDGE_HEADER.length());
            XLog.d("msg:" + msg);
            return handleMsgFromJS(msg);
        }
        return false;
    }

    private boolean dispatch(String func, final String seqId, String param) {
   

        if (func.equals("nativeMethod")) {
   
            //模拟js bridge的native实现
            Toast.makeText(mWebView.getContext(), "Hello XJSBridge!I am in native.", Toast.LENGTH_SHORT).show();
            new Thread(new Runnable() {
   
                @Override
                public void run() {
   
                    XLog.d("hello, I am native method...");
                    try {
   
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
   
                        e.printStackTrace();
                    }
                    JSONObject mockRes = new JSONObject();
                    try {
   
                        mockRes.put("bridge", XJSBridgeImpl.class.getName());
                        mockRes.put("data", "I am from native");
                    } catch (JSONException e) {
   
                        e.printStackTrace();
                    }
                    invokeJS(seqId, mockRes);
                }
            }).start();
        }
        return false;
    }


    private void invokeJS(String seqId, JSONObject res) {
   
        final JSONObject jsonObject = new JSONObject();
        try {
   
            jsonObject.put("seqId", seqId);
            jsonObject.put("msgType", "jsCallback");
            jsonObject.put("param", res);
        } catch (JSONException e) {
   
            XLog.e(e);
        }
        mWebView.post(new Runnable() {
   
            @Override
            public void run() {
   
                //需要在主线程调用
                mWebView.evaluateJavascript(String.format("javascript: window.XJSBridge.nativeInvokeJS('%s')", jsonObject.toString()), new ValueCallback<String>() {
   
                    @Override
                    public void onReceiveValue(String s) {
   
                        XLog.d("onReceiveValue:s = " + s);
                    }
                });
            }
        });
    }


    private boolean handleMsgFromJS(String message) {
   
        try {
   
            JSONObject jsonObject = new JSONObject(message);
            String func = jsonObject.getString("func");
            String seqId = jsonObject.getString("seqId");
            String param = jsonObject.getString("param");
            dispatch(func, seqId, param);
            return true;
        } catch (JSONException e) {
   
            XLog.e(e);
        }
        return false;
    }

    public void changeH5Background() {
   
        mWebView.evaluateJavascript(String.format("javascript: changeColor('%s')", "#f00"), new ValueCallback<String>() {
   
            @Override
            public void onReceiveValue(String s) {
   
                XLog.d("changeH5Background:onReceiveValue:s = " + s);
            }
        });
    }

    public void addObjectForJS(){
   
        mWebView.addJavascriptInterface(new NativeLog(),"nativeLog");
    }
}
  • native如何回调给前端?

android通过mWebView.evaluateJavascript或者mWebView.loadUrl的方式调用JS,在android 4.4以上推荐使用evaluateJavascript,loadUrl会导致页面刷新,键盘收回等问题。

  • native如何向前端注入对象?

通过addJavascriptInterface添加对象,比如:


mWebView.addJavascriptInterface(new NativeLog(),"nativeLog");

其中NativeLog的实现如下:


public class NativeLog {
   

    @JavascriptInterface
    public void print(String log) {
   
        XLog.d("nativeLog:" + log);
    }
}

这里面的方法print,需要添加注解@JavascriptInterface,JS才能访问到。

添加之后,前端可以通过如下方式调用:


document.getElementById('a2').addEventListener('click', function () {
   
    console.log('clicked....');
    if (nativeLog) {
   
        nativeLog.print('content from js');
    }
});

2.如果实现url的拦截和重定向?

WebView的WebViewClient中,可以通过重写shouldOverrideUrlLoading实现,如果需要拦截,可以return true;比如:


public class XWebViewClient extends WebViewClient {
   

    public XWebViewClient() {
   
        super();
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
   
        XLog.d("shouldOverrideUrlLoading2...");
        if (shouldOverrideUrlLoadingInternal(view, request.getUrl().toString())) {
   
            return true;
        }
        return super.shouldOverrideUrlLoading(view, request);
    }

    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
   
        XLog.d("shouldOverrideUrlLoading1...");
        if (shouldOverrideUrlLoadingInternal(view, url)) {
   
            return true;
        }
        return super.shouldOverrideUrlLoading(view, url);
    }

    private boolean shouldOverrideUrlLoadingInternal(WebView view, String url) {
   
        if (url != null && url.startsWith("https://www.baidu.com")) {
   
            view.loadUrl("file:///android_asset/xbridge_demo.html");
            return true;
        }
        if (url != null && url.startsWith("xscheme://native_page")) {
   
            //根据前端的href进行scheme拦截,跳转到native的页面
            view.getContext().startActivity(new Intent(view.getContext(), TestActivity.class));
            return true;
        }
        if(url != null && url.startsWith("")){
   

        }</
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值