Native JsBridge源码解析 深入理解JsBridge

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

  • 引入JsBridge库
//at your porject gradle
repositories {
    // ...
    maven { url "https://jitpack.io" }
}

//at you app gradle
dependencies {
    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端收发消息
//注册 WebViewJavascriptBridgeReady
function 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 中被置空的。

//置空startupMessage
if (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);

}
//修改DefaultHandler
public class DefaultHandler implements BridgeHandler {


    @Override
    public void handler(String name,String data, CallBackFunction function) {
        if(function != null){
            function.onCallBack("DefaultHandler response data");
        }
    }

}

//通过Js信息查找Handler
JsBridgeHandler 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.ConsoleMessagecom.tencent.smtt.export.external.interfaces.ConsoleMessage
android.webkit.CacheManagercom.tencent.smtt.sdk.CacheManager(deprecated)
android.webkit.CookieManagercom.tencent.smtt.sdk.CookieManager
android.webkit.CookieSyncManagercom.tencent.smtt.sdk.CookieSyncManager
android.webkit.CustomViewCallbackcom.tencent.smtt.export.external.interfaces.IX5WebChromeClient.CustomViewCallback
android.webkit.DownloadListenercom.tencent.smtt.sdk.DownloadListener
android.webkit.GeolocationPermissionscom.tencent.smtt.export.external.interfaces.GeolocationPermissionsCallback
android.webkit.HttpAuthHandlercom.tencent.smtt.export.external.interfaces.HttpAuthHandler
android.webkit.JsPromptResultcom.tencent.smtt.export.external.interfaces.JsPromptResult
android.webkit.JsResultcom.tencent.smtt.export.external.interfaces.JsResult
android.webkit.SslErrorHandlercom.tencent.smtt.export.external.interfaces.SslErrorHandler
android.webkit.ValueCallbackcom.tencent.smtt.sdk.ValueCallback
android.webkit.WebBackForwardListcom.tencent.smtt.sdk.WebBackForwardList
android.webkit.WebChromeClientcom.tencent.smtt.sdk.WebChromeClient
android.webkit.WebHistoryItemcom.tencent.smtt.sdk.WebHistoryItem
android.webkit.WebIconDatabasecom.tencent.smtt.sdk.WebIconDatabase
android.webkit.WebResourceResponsecom.tencent.smtt.export.external.interfaces.WebResourceResponse
android.webkit.WebSettingscom.tencent.smtt.sdk.WebSettings
android.webkit.WebSettings.LayoutAlgorithmcom.tencent.smtt.sdk.WebSettings.LayoutAlgorithm
android.webkit.WebStoragecom.tencent.smtt.sdk.WebStorage
android.webkit.WebViewcom.tencent.smtt.sdk.WebView
android.webkit.WebViewClientcom.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.gradle

compile '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总结

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值