为啥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)的情况下不起作用?