Native JsBridge源码解析 深入理解JsBridge

来源:互联网 发布:库里杜兰特知乎 编辑:程序博客网 时间:2024/06/02 02:51

最近项目中使用了 HyBrid 框架,但是在使用过程中遇到了不少问题,因此花时间来研究了一下其中原理! 在平时开发过程中,不管是可复用性非常高,可以跨平台开发的 HyBrid ,还是半 Native 半 web 浅尝辄止的 HyBrid ,对 Android 而言,陌生的就是其中的通信——Android 与 Html 的互相通信。这里就不掉书包,直接阐明其中的使用方法。

  • 引入JsBridge库
//at your porject gradlerepositories {    // ...    maven { url "https://jitpack.io" }}//at you app gradledependencies {    compile 'com.github.lzyzsd:jsbridge:1.0.4'}
  • Android端收发消息

    1. 使用控件
    2. 向Html发送消息
    3. 接收Html发送的消息
//布局 <com.github.lzyzsd.jsbridge.BridgeWebView        android:id="@+id/webView"        android:layout_width="match_parent"        android:layout_height="match_parent" >     </com.github.lzyzsd.jsbridge.BridgeWebView>//控件webView = (BridgeWebView) findViewById(R.id.webView);/** * 发送消息给html * @param value 字符串 * @param data  数据 * @param function  回调方法 */webView.callHandler("value", "data", new CallBackFunction() {            @Override            public void onCallBack(String response) {            }        }); /** * 发送消息给html * @param data  字符串 */    webView.send("hello");/** * register handler,so that javascript can call it * 注册handler,以便javascript可以调用它 * @param handlerName * @param handler */webView.registerHandler("value", new BridgeHandler() {            @Override            public void handler(String data, CallBackFunction function) {            }        });
  • Html端收发消息
//注册 WebViewJavascriptBridgeReadyfunction connectWebViewJavascriptBridge(callback) {    if (window.WebViewJavascriptBridge) {        callback(WebViewJavascriptBridge)    } else {        document.addEventListener(            'WebViewJavascriptBridgeReady'            , function() {                callback(WebViewJavascriptBridge)            },            false        );    }}connectWebViewJavascriptBridge(function(bridge) {    bridge.init(function(message, responseCallback) {        console.log('JS got a message', message);        //默认返回值        var data = {            'Javascript Responds': '测试中文!'        };        console.log('JS responding with', data);        responseCallback(data);    });    /**     * 接收消息     * "functionInJs"   字符串标签     * data 收到数据     * responseCallback 回调接口     */     bridge.registerHandler("functionInJs", function(data, responseCallback) {          document.getElementById("show").innerHTML = ("data from Java: = " + data);          var responseData = "Javascript Says Right back aka!";          responseCallback(responseData);      });  })

通过库文件demo可以仔细看到这些方法,还是比较容易理解的。但是具体是怎么一个原理呢?跟踪代码看看,其实非常简单:

webView.send("hello");/** * 发送消息给html * @param value 字符串 * @param data  数据 * @param function  回调方法 */private void sendMessage(String value, String data, CallBackFunction function) {        webView.callHandler(value, data, function);    }

接下来看看BridgeWebView的源码:

@Override    public void send(String data) {        send(data, null);    }    @Override    public void send(String data, CallBackFunction responseCallback) {        doSend(null, data, responseCallback);    }/**     * call javascript registered handler     *     * @param handlerName     * @param data     * @param callBack     */    public void callHandler(String handlerName, String data, CallBackFunction callBack) {        doSend(handlerName, data, callBack);    }

接下来仔细研究一下doSend这个方法,源码如下:

private void doSend(String handlerName, String data, CallBackFunction responseCallback) {        Message m = new Message();        if (!TextUtils.isEmpty(data)) {            m.setData(data);        }        if (responseCallback != null) {            String callbackStr = String.format(BridgeUtil.CALLBACK_ID_FORMAT, ++uniqueId + (BridgeUtil.UNDERLINE_STR + SystemClock.currentThreadTimeMillis()));            responseCallbacks.put(callbackStr, responseCallback);            m.setCallbackId(callbackStr);        }        if (!TextUtils.isEmpty(handlerName)) {            m.setHandlerName(handlerName);        }        queueMessage(m);    }

上面的代码其实就是封装了一个 Message,然后将 Message 添加到添加到队列里,这个结构类似于 Handler,接下来看看队列消息里面怎么将消息发送到 Js?

private void queueMessage(Message m) {        if (startupMessage != null) {            startupMessage.add(m);        } else {            dispatchMessage(m);        }    }    void dispatchMessage(Message m) {        String messageJson = m.toJson();        //escape special characters for json string        messageJson = messageJson.replaceAll("(\\\\)([^utrn])", "\\\\\\\\$1$2");        messageJson = messageJson.replaceAll("(?<=[^\\\\])(\")", "\\\\\"");        String javascriptCommand = String.format(BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, messageJson);        if (Thread.currentThread() == Looper.getMainLooper().getThread()) {            LUtil.e(javascriptCommand);            this.loadUrl(javascriptCommand);        }    }

startupMessage 其实就是一个消息队列,这个对象的初始值肯定不为 null,但是这里的 startupMessage 确实是被置空了,至于在什么地方置空的,稍候再研究,我们可以看到在分发消息的时候,直接将 Message 转成了 Json 字符串,然后对字符串经过一系列处理便加载了这个方法,通过日志,我们可以看到这时候输出的内容如下:

Android 发送消息给HTML

问题是我们注册的时候,并没有这个方法,那么这个方法在什么地方被声明的呢?通过查看 BridgeWebView 的相关方法,最后发现在 BridgeWebViewClient 的 onPageFinished 方法里,这里将 assets 文件夹下的 WebViewJavascriptBridge.js 读取出来发送到了 html,而 WebViewJavascriptBridge.js 内容是什么呢?看代码:

//发送消息内容:view.loadUrl("javascript:" + jsContent);//消息内容如下//notation: js file can only use this kind of comments//since comments will cause error when use in webview.loadurl,//comments will be remove by java use regexp(function() {    if (window.WebViewJavascriptBridge) {        return;    }    var messagingIframe;    var sendMessageQueue = [];    var receiveMessageQueue = [];    var messageHandlers = {};    var CUSTOM_PROTOCOL_SCHEME = 'yy';    var QUEUE_HAS_MESSAGE = '__QUEUE_MESSAGE__/';    var responseCallbacks = {};    var uniqueId = 1;    function _createQueueReadyIframe(doc) {        messagingIframe = doc.createElement('iframe');        messagingIframe.style.display = 'none';        doc.documentElement.appendChild(messagingIframe);    }    //set default messageHandler    function init(messageHandler) {        if (WebViewJavascriptBridge._messageHandler) {            throw new Error('WebViewJavascriptBridge.init called twice');        }        WebViewJavascriptBridge._messageHandler = messageHandler;        var receivedMessages = receiveMessageQueue;        receiveMessageQueue = null;        for (var i = 0; i < receivedMessages.length; i++) {            _dispatchMessageFromNative(receivedMessages[i]);        }    }    function send(data, responseCallback) {        _doSend({            data: data        }, responseCallback);    }    function registerHandler(handlerName, handler) {        messageHandlers[handlerName] = handler;    }    function callHandler(handlerName, data, responseCallback) {        _doSend({            handlerName: handlerName,            data: data        }, responseCallback);    }    //sendMessage add message, 触发native处理 sendMessage    function _doSend(message, responseCallback) {        if (responseCallback) {            var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();            responseCallbacks[callbackId] = responseCallback;            message.callbackId = callbackId;        }        sendMessageQueue.push(message);        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;    }    // 提供给native调用,该函数作用:获取sendMessageQueue返回给native,由于android不能直接获取返回的内容,所以使用url shouldOverrideUrlLoading 的方式返回内容    function _fetchQueue() {        var messageQueueString = JSON.stringify(sendMessageQueue);        sendMessageQueue = [];        //android can't read directly the return data, so we can reload iframe src to communicate with java        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);    }    //提供给native使用,    function _dispatchMessageFromNative(messageJSON) {        setTimeout(function() {            var message = JSON.parse(messageJSON);            var responseCallback;            //java call finished, now need to call js callback function            if (message.responseId) {                responseCallback = responseCallbacks[message.responseId];                if (!responseCallback) {                    return;                }                responseCallback(message.responseData);                delete responseCallbacks[message.responseId];            } else {                //直接发送                if (message.callbackId) {                    var callbackResponseId = message.callbackId;                    responseCallback = function(responseData) {                        _doSend({                            responseId: callbackResponseId,                            responseData: responseData                        });                    };                }                var handler = WebViewJavascriptBridge._messageHandler;                if (message.handlerName) {                    handler = messageHandlers[message.handlerName];                }                //查找指定handler                try {                    handler(message.data, responseCallback);                } catch (exception) {                    if (typeof console != 'undefined') {                        console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception);                    }                }            }        });    }    //提供给native调用,receiveMessageQueue 在会在页面加载完后赋值为null,所以    function _handleMessageFromNative(messageJSON) {        console.log(messageJSON);        if (receiveMessageQueue && receiveMessageQueue.length > 0) {            receiveMessageQueue.push(messageJSON);        } else {            _dispatchMessageFromNative(messageJSON);        }    }    var WebViewJavascriptBridge = window.WebViewJavascriptBridge = {        init: init,        send: send,        registerHandler: registerHandler,        callHandler: callHandler,        _fetchQueue: _fetchQueue,        _handleMessageFromNative: _handleMessageFromNative    };    var doc = document;    _createQueueReadyIframe(doc);    var readyEvent = doc.createEvent('Events');    readyEvent.initEvent('WebViewJavascriptBridgeReady');    readyEvent.bridge = WebViewJavascriptBridge;    doc.dispatchEvent(readyEvent);})();

前面讲的 startupMessage 置空,就是在上面的 onPageFinished 中被置空的。

//置空startupMessageif (webView.getStartupMessage() != null) {    for (Message m : webView.getStartupMessage()) {        webView.dispatchMessage(m);    }    webView.setStartupMessage(null);}

接下来分析,BridgeWebView 端发送的消息是怎么在 JS 被执行的呢?先是通过 _handleMessageFromNative 方法调用 _dispatchMessageFromNative 方法,最后执行如下代码:

try {   handler(message.data, responseCallback);} catch (exception) {    if (typeof console != 'undefined') {        console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception);    }}

handler(message.data, responseCallback) 方法具体实现是在下面实现的( js 不大熟悉,整理代码发现逻辑大致如下):

/**             * 接收消息             * "functionInJs"   字符串标签             * data 收到数据             * responseCallback 回调接口             */            bridge.registerHandler("functionInJs", function(data, responseCallback) {                document.getElementById("show").innerHTML = ("data from Java: = " + data);                var responseData = "Javascript Says Right back aka!";                responseCallback(responseData);            });

接着我们看看 BridgeWebView 端发送消息的回调接口,是怎么实现的?通过代码我们发现 _dispatchMessageFromNative 方法下回调方法 _doSend 方法:

 //sendMessage add message, 触发native处理 sendMessage    function _doSend(message, responseCallback) {        if (responseCallback) {            var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();            responseCallbacks[callbackId] = responseCallback;            message.callbackId = callbackId;        }        sendMessageQueue.push(message);        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;    }

_doSend 方法请求 url:yy://QUEUE_MESSAGE/
当 BridgeWebView 的 shouldOverrideUrlLoading 方法收到 messagingIframe 请求,继续检查这个方法:

 @Override public boolean shouldOverrideUrlLoading(WebView view, String url) {      try {          url = URLDecoder.decode(url, "UTF-8");          LUtil.e("shouldOverrideUrlLoading:"+url);      } catch (UnsupportedEncodingException e) {          e.printStackTrace();      }      if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) {           webView.handlerReturnData(url);          return true;      } else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { //          webView.flushMessageQueue();          return true;      } else {          return super.shouldOverrideUrlLoading(view, url);      }  }

这次我们查看 BridgeWebView 的 flushMessageQueue 方法,直接看代码:

        /**     * 刷新消息队列     */    void flushMessageQueue() {        if (Thread.currentThread() == Looper.getMainLooper().getThread()) {            loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new CallBackFunction() {                ***            });        }    }public void loadUrl(String jsUrl, CallBackFunction returnCallback) {        this.loadUrl(jsUrl);        responseCallbacks.put(BridgeUtil.parseFunctionName(jsUrl), returnCallback);    }

其实这里很清楚了,就是当 JS 需要返回数据的时候,先将需要返回的数据保存下来,然后发送请求到 BridgeWebView ,告诉 BridgeWebView 我要发送回调数据了,然后 BridgeWebView 将请求和接口以键值对的形式保存下来,并请求 js 的方法—— javascript:WebViewJavascriptBridge._fetchQueue();JS 收到消息后就发送请求,然后 shouldOverrideUrlLoading 收到请求判断消息头以后调用 BridgeWebView 的方法,代码如下:

//返回消息头 yy://return/_fetchQueue/+返回数据void handlerReturnData(String url) {        String functionName = BridgeUtil.getFunctionFromReturnUrl(url);        CallBackFunction f = responseCallbacks.get(functionName);        String data = BridgeUtil.getDataFromReturnUrl(url);        if (f != null) {            f.onCallBack(data);            responseCallbacks.remove(functionName);            return;        }    }

最后将这个过程整理如下:

  • 安卓发送消息给 Js 过程
  1. 当 WebView 发送数据给 Js 时: WebView 请求 js 方法—— javascript:WebViewJavascriptBridge._handleMessageFromNative( gson 字符串);
  2. js 执行相关方法: _handleMessageFromNative——_dispatchMessageFromNative——handler(message.data, responseCallback);,
  • Js 通过接口返回数据
  1. js 执行 _doSend 方法;
  2. BridgeWebView 拦截请求并判断消息头,如果消息头标签是—— yy://,则将 _fetchQueue 和接口再次以键值对的形式保存下来,并通过 BridgeWebView 请求 js 的 _fetchQueue() 方法;
  3. js 的 _fetchQueue() 方法将值发送给 BridgeWebView;
  4. BridgeWebView 再次判断请求的消息头,如果消息头标签是—— yy://return/ ,则用 _fetchQueue 取出的接口对象;
  5. 在接口对象中,根据返回的接口对象 Id —— responseId 取出 BridgeWebView 存储的待处理数据的接口对象,并将请求里面包含的数据取出来,执行待处理数据的接口。

接下来 Js 发送消息给 BridgeWebView 的过程又如何呢?首先咱们得与 Js 约定一个 Handler,然后将这个 Handler 保存下来,代码如下:

Map<String, BridgeHandler> messageHandlers = new HashMap<String, BridgeHandler>();/** * register handler,so that javascript can call it *  * @param handlerName * @param handler */public void registerHandler(String handlerName, BridgeHandler handler) {        if (handler != null) {            messageHandlers.put(handlerName, handler);        }    }

然后咱们继续看 Js 是如何发送消息给 BridgeWebView,还是直接看源码:

 //call native method window.WebViewJavascriptBridge.callHandler(     'submitFromWeb'     , {'param': '中文测试'}     , function(responseData) {         document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData     } );

来来来,咱们理一下这个过程,首先 js 执行 callHandler 方法—— _doSend 方法,在这个方法 _doSend 方法中还是一样:将 Message 信息保存下来,并发送请求给 BridgeWebView,告诉他,我要给你发消息了,准备好!
然后过程就和上面类似了,过程如下:

  1. js 执行 _doSend 方法,保存消息并发送请求—— yy://QUEUE_MESSAGE/;
  2. BridgeWebView 拦截请求并判断消息头,如果消息头标签是—— yy://,则将 _fetchQueue 和接口以键值对的形式保存下来,并通过 BridgeWebView 请求 js 的 _fetchQueue() 方法;
  3. js 执行 _fetchQueue() 方法并发送请求——yy://return/_fetchQueue/+”消息”;
  4. BridgeWebView 在 shouldOverrideUrlLoading 方法拦截请求,如果消息头标签是—— yy://return/_fetchQueue/,则取出第2点保存的接口,并将请求包含的数据发送给接口;
  5. 在接口当中做两件事,第一件事是根据请求信息生成 Message 对象并保存下来(BridgeWebView的queueMessage方法),另一件事就是根据请求内容将 js 与 BridgeWebView 约定 BridgeHandler ,并执行 BridgeHandler 的 handler 方法;
  6. BridgeWebViewClient 在 onPageFinished 方法中分发消息(dispatchMessage方法)—— BridgeWebView 请求 js 的 _handleMessageFromNative 方法;
  7. Js 执行 _handleMessageFromNative 方法—— _dispatchMessageFromNative 方法,在方法中根据 responseId 找出接口,并执行该接口,完毕。

上述已经描述了 BridgeWebView 发送消息并回调、Js 发送消息并回调,整个过程我们已经很清楚了,那么这些个过程有没有需要优化的地方呢?比如 BridgeWebView 与 js 优化的地方有很多,那么能不能把接口改一下呢?咱们试一试!由于整个 JsBridge 代码文件不多,我们可以直接下载下来,这样方便我们修改,因此我们可以直接修改 BridgeHandler.java ,然后修改部分代码,说干就干:

public interface BridgeHandler {    void handler(String name, String data, CallBackFunction function);}//修改DefaultHandlerpublic class DefaultHandler implements BridgeHandler {    @Override    public void handler(String name,String data, CallBackFunction function) {        if(function != null){            function.onCallBack("DefaultHandler response data");        }    }}//通过Js信息查找HandlerJsBridgeHandler handler;                            if (!TextUtils.isEmpty(m.getHandlerName())) {                                handler = messageHandlers.get(m.getHandlerName());                            } else {                                handler = defaultHandler;                            }                            if (handler != null){                                handler.handler(m.getHandlerName(),m.getData(), responseFunction);                            }//修改注册方法@Override  public void handler(String name, String data, final CallBackFunction function) {      switch (name){          case "open"://打开后台维护界面              break;          case "token"://获取token              break;          case "clientId"://获取clientId              break;          case "out"://退出账号              break;      }  }

以上已经分析完毕。JsBridge 的原理大家都知道了,接下来需要做的就是WebView 的优化,因为我们知道原生的 WebView 加载页面的时候渲染比较慢,一些诸如腾讯 X5、VasSonic 等据说渲染效果比 WebView 好很多,那么兼容通信和渲染效果良好的的 JsBridge 框架,应该怎么搭建呢?好好想一想?

  • 腾讯 X5 WebView 接入

    X5 WebView 比较简单,就是一个 jar 包,然后将原本的 android.webkit 包下的内容转为 com.tencent.smtt 下的相关类,并实现相关权限(运行时权限需要自己处理),具体如下:

系统内核 SDK内核 android.webkit.ConsoleMessage com.tencent.smtt.export.external.interfaces.ConsoleMessage android.webkit.CacheManager com.tencent.smtt.sdk.CacheManager(deprecated) android.webkit.CookieManager com.tencent.smtt.sdk.CookieManager android.webkit.CookieSyncManager com.tencent.smtt.sdk.CookieSyncManager android.webkit.CustomViewCallback com.tencent.smtt.export.external.interfaces.IX5WebChromeClient.CustomViewCallback android.webkit.DownloadListener com.tencent.smtt.sdk.DownloadListener android.webkit.GeolocationPermissions com.tencent.smtt.export.external.interfaces.GeolocationPermissionsCallback android.webkit.HttpAuthHandler com.tencent.smtt.export.external.interfaces.HttpAuthHandler android.webkit.JsPromptResult com.tencent.smtt.export.external.interfaces.JsPromptResult android.webkit.JsResult com.tencent.smtt.export.external.interfaces.JsResult android.webkit.SslErrorHandler com.tencent.smtt.export.external.interfaces.SslErrorHandler android.webkit.ValueCallback com.tencent.smtt.sdk.ValueCallback android.webkit.WebBackForwardList com.tencent.smtt.sdk.WebBackForwardList android.webkit.WebChromeClient com.tencent.smtt.sdk.WebChromeClient android.webkit.WebHistoryItem com.tencent.smtt.sdk.WebHistoryItem android.webkit.WebIconDatabase com.tencent.smtt.sdk.WebIconDatabase android.webkit.WebResourceResponse com.tencent.smtt.export.external.interfaces.WebResourceResponse android.webkit.WebSettings com.tencent.smtt.sdk.WebSettings android.webkit.WebSettings.LayoutAlgorithm com.tencent.smtt.sdk.WebSettings.LayoutAlgorithm android.webkit.WebStorage com.tencent.smtt.sdk.WebStorage android.webkit.WebView com.tencent.smtt.sdk.WebView android.webkit.WebViewClient com.tencent.smtt.sdk.WebViewClient
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /><uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.READ_PHONE_STATE" />
  • VasSonic-Android 接入
    VasSonic接入比较简单,但是使用就比较复杂,接入如下:
//Add VasSonic gradle plugin as a dependency in your module's build.gradlecompile 'com.tencent.sonic:sdk:1.0.0'

使用前需要实现 SonicRuntime 和 SonicSessionClient 接口,具体参考 VasSonic


腾讯 X5 WebView 、VasSonic 、原生 WebView 对比

通过对onPageStarted方法、onPageFinished方法查看各个 WebView 加载页面所需时间,发现 X5 、VasSonic 均比原生 WebView 快了近一倍,所以 JsBridge 有必要将第三方WebView 加载到项目中,但是加载 X5 还是 VasSonic 呢?通过查看文档发现 VasSonic 更新需要和后台服务器交互判断是否更新数据?也就是说 VasSonic 需要和后台配合才能发挥最大效果。我倾向于选择前者,因为使用起来简单得多,而后者的库也配置好了,地址如下:https://github.com/Vicent9920/JsBridge


JsBridge 注意事项:
  • App 首次就可以加载 x5 内核

    App 在启动后(例如在 Application 的 onCreate 中)立刻调用 QbSdk 的预加载接口 initX5Environment ,可参考接入示例,第一个参数传入 context,第二个参数传入 callback,不需要 callback 的可以传入 null,initX5Environment 内部会创建一个线程向后台查询当前可用内核版本号,这个函数内是异步执行所以不会阻塞 App 主线程,这个函数内是轻量级执行所以对 App 启动性能没有影响,当 App 后续创建 webview 时就可以首次加载 x5 内核了。(也可以像LitePal那样在Application初始化,但是需要文件清单配置或者继承自该Application,或者在该类传入一个静态方法初始化 JsBridge )

  • 获取系统内核的WebView或者 x5内核的WebView的宽高

com.tencent.smtt.sdk.WebView webView = new com.tencent.smtt.sdk.WebView(this);int width = webView.getView().getWidth();
  • 避免输入法界面弹出后遮挡输入光标的问题
//方法一:在AndroidManifest.xml中设置android:windowSoftInputMode="stateHidden|adjustResize"//方法二:在代码中动态设置:getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
  • 兼容视频播放

1)享受页面视频的完整播放体验需要做如下声明:
页面的Activity需要声明
android:configChanges="orientation|screenSize|keyboardHidden"
2)视频为了避免闪屏和透明问题,需要如下设置
a)网页中的视频,上屏幕的时候,可能出现闪烁的情况,需要如下设置:Activity在onCreate时需要设置:
getWindow().setFormat(PixelFormat.TRANSLUCENT);(这个对宿主没什么影响,建议声明)
在非硬绘手机和声明需要controller的网页上,视频切换全屏和全屏切换回页面内会出现视频窗口透明问题,需要如下设置
声明当前<item name="android:windowIsTranslucent">false为不透明。
特别说明:这个视各app情况所需,不强制需求,如果声明了,对体验更有利
c)以下接口禁止(直接或反射)调用,避免视频画面无法显示:

webview.setLayerType()webview.setDrawingCacheEnabled(true);

其它参考文档:X5 接入文档


WebView相关文章推荐:

腾讯浏览服务X5内核集成
Android中WebView的JavaScript代码和本地代码交互的三种方式
WebView详解与简单实现Android与H5互调
WebView写入数据到 localStorage总结