为啥WebView在横向时不添加JavascriptInterface

Posted

技术标签:

【中文标题】为啥WebView在横向时不添加JavascriptInterface【英文标题】:Why is WebView not adding the JavascriptInterface when in landscape orientation为什么WebView在横向时不添加JavascriptInterface 【发布时间】:2021-12-14 09:05:52 【问题描述】:

我已经为我的应用程序创建了一个用 html 编写的用户手册。它有一个内容页面,其中包含指向其他部分的链接。我将 html 文件加载到 WebView 中,一切正常。我想要做的是当用户从纵向切换到横向时,保持与以前相同的滚动位置,反之亦然,从横向切换回纵向。我用 onScrollPositionChange 方法创建了一个类 WebScrollListener。这作为 javascriptInterface 添加到 WebView,因此 Javascript 设置方法中变量的值。我第一次从纵向到横向运行良好,但是一旦进入横向,WebScrollListener 就会停止监听滚动事件。这是我的 Fragment 的代码。

 public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) 

           int fragmentId = scenarioViewModel.getCurrentFragmentId();
        // Inflate the layout for this fragment
        View root = inflater.inflate(R.layout.fragment_user_manual, container, false);

        if (savedInstanceState != null) 
            // orientation changed has occurred
            initialScrollElement = savedInstanceState.getString("scrollElement");
            initialScrollMargin = savedInstanceState.getInt("scrollMargin");
        

        webview = root.findViewById(R.id.help_text);

        hashTag = getHashTag(fragmentId);

        webview.getSettings().setJavaScriptEnabled(true);
        WebViewClient webViewClient = new MyWebViewClient();
        webview.setWebViewClient(webViewClient);

        scrollListener = new WebScrollListener();
        webview.addJavascriptInterface(scrollListener, "WebScrollListener");
        webview.loadUrl("file:///android_asset/app-user-manual.html");

        return root;
    

    public void onSaveInstanceState(@NonNull Bundle outState) 
        super.onSaveInstanceState(outState);
        outState.putString("scrollElement", scrollListener.element);
        outState.putInt("scrollMargin", scrollListener.margin);
    

   private class MyWebViewClient extends WebViewClient 

        public void onPageFinished(WebView view, String url) 

            if (initialScrollElement != null) 
                // It's very hard to detect when web page actually finished loading;
                // At the time onPageFinished is called, page might still not be parsed
                // Any javascript inside <script>...</script> tags might still not be executed;
                // Dom tree might still be incomplete;
                // So we are gonna use a combination of delays and checks to ensure
                // that scroll position is only restored after page has actually finished loading
                view.postDelayed(new Runnable() 
                    @Override
                    public void run() 
                        StringBuilder buf=new StringBuilder("javascript:scrollToPosition('"+initialScrollElement+"','"+initialScrollMargin+"')");
                        Log.d("MyWebViewClient", "returned string:"+buf.toString());
                        webview.loadUrl(buf.toString());
                        initialScrollElement = null;
                    
                , 300);
             else 
               if (url.contains("#") != true) 
                    // We only go into this method if the user has clicked on the help button
                    // and we want to go to a specific hashtag.  The page gets reloaded at that hashtag.
                    if (!hashTag.isEmpty()) 
                        String myurl = url + hashTag;
                        // this gets loaded again so as to get the position of the hashtag
                        webview.loadUrl(myurl);
                    
                
                super.onPageFinished(view, url);
            
        
    

 public class WebScrollListener 

        private String element;
        private int margin;

        @JavascriptInterface
        public void onScrollPositionChange(String topElementCssSelector, int topElementTopMargin) 
            Log.d("WebScrollListener", "Scroll position changed: " + topElementCssSelector + " " + topElementTopMargin);
            element = topElementCssSelector;
            margin = topElementTopMargin;
        
    

javascript 包含在 html 文件中,如下所示:

<script type="text/javascript" src="scroll-position.js"></script>

这里是javascript代码:

    // We will find first visible element on the screen 
    // by probing document with the document.elementFromPoint function;
    // we need to make sure that we dont just return 
    // body element or any element that is very large;
    // best case scenario is if we get any element that 
    // doesn't contain other elements, but any small element is good enough;
    var findSmallElementOnScreen = function() 
      var SIZE_LIMIT = 1024;
      var elem = undefined;
      var offsetY = 0;
      while (!elem) 
          var e = document.elementFromPoint(100, offsetY);
          if (e.getBoundingClientRect().height < SIZE_LIMIT) 
              elem = e;
           else 
              offsetY += 50;
          
      
      return elem;
  ;

  // Convert dom element to css selector for later use
  var getCssSelector = function(el) 
      if (!(el instanceof Element)) 
          return;
      var path = [];
      while (el.nodeType === Node.ELEMENT_NODE) 
          var selector = el.nodeName.toLowerCase();
          if (el.id) 
              selector += '#' + el.id;
              path.unshift(selector);
              break;
           else 
              var sib = el, nth = 1;
              while (sib = sib.previousElementSibling) 
                  if (sib.nodeName.toLowerCase() == selector)
                     nth++;
              
              if (nth != 1)
                  selector += ':nth-of-type('+nth+')';
          
          path.unshift(selector);
          el = el.parentNode;
      
      return path.join(' > ');    
  ;

  // Send topmost element and its top offset to java
  var reportScrollPosition = function() 
      var elem = findSmallElementOnScreen();
      if (elem) 
          var selector = getCssSelector(elem);
          var offset = elem.getBoundingClientRect().top;
          WebScrollListener.onScrollPositionChange(selector, offset);
      
  

  // We will report scroll position every time when scroll position changes,
  // but timer will ensure that this doesn't happen more often than needed
  // (scroll event fires way too rapidly)
  var previousTimeout = undefined;
  window.addEventListener('scroll', function() 
      clearTimeout(previousTimeout);
      previousTimeout = setTimeout(reportScrollPosition, 200);
  );

  function scrollToPosition (selectorToRestore, positionToRestore) 
    var previousTop = 0;
    var check = function() 
        var elem = document.querySelector(selectorToRestore);
        if (!elem) 
         setTimeout(check, 100);
          return;
        
        var currentTop = elem.getBoundingClientRect().top;
        if (currentTop != previousTop) 
          previousTop = currentTop;
          setTimeout(check,100);
         else 
          window.scrollBy(0, currentTop - positionToRestore);
        
    
    check();
  

任何帮助将不胜感激。

【问题讨论】:

【参考方案1】:

onCreate 入口点您的 webview 添加条件一切正常,除了 JavascriptInterface 在方向更改后永远不会工作:

public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 
    
    webView = root.findViewById(R.id.webView);

    if (webView != null) 
        String URL = (savedInstanceState == null) ? "" : savedInstanceState.getString("URL");
        if (URL.equals("")) 
            webView.loadUrl("yourhome.html");    
         else 
            webView.loadUrl(URL);   // this is saved in onSaveInstanceState 
        
    
    return root;
 

【讨论】:

这不起作用。 onSavedInstanceState 中保存的 URL 是已加载的 URL,但不是页面加载后滚动到的位置。因此,当方向改变时,它不会回到滚动到的最后一个位置,而是回到用户第一次加载页面时所处的位置。问题在于 WebScrollListener 与 javascript 的接口。纵向滚动时,我可以看到 Logcat 当前滚动位置的输出。当我转到横向和滚动时,Logcat 中没有输出,这意味着没有从 javascript 调用 WebScrollListener【参考方案2】:

问题出在 javascript 函数 findSmallAlementOnScreen 中。此函数的 SIZE_LIMIT 为 1024,但使用调试器时我发现在横向上超过了这个值,因此从未找到顶部元素。

此方法现在只需两行代码即可完美运行。

 var findSmallElementOnScreen = function() 
    var range = document.caretRangeFromPoint(0,0); // screen coordinates of upper-left corner of a scolled area (in this case screen itself)
    var element = range.startContainer.parentNode; // this an upper onscreen element
    return element;
  ;

【讨论】:

以上是关于为啥WebView在横向时不添加JavascriptInterface的主要内容,如果未能解决你的问题,请参考以下文章

从纵向模式旋转到横向模式时不调整子视图

为啥 webview_flutter 在添加到应用程序(iOS)的情况下不起作用?

为啥将结果添加到 List 时不执行方法调用?

为啥我添加到我的 xcode 项目的音频文件在模拟器中可用,但在我在 iPhone 上部署应用程序时不可用

ios:在webview上允许横向

WKWebView 在后台时不运行 JavaScript