Android开发问题记录-webview goBack键失效

在Android 4.4以上系统中,自定义WebView遇到goBack()回退功能失效的问题,表现为大约率无法正常回退。经过版本复查、找不同、验证改动等步骤,发现问题与`android:targetSdkVersion`提升到23有关。进一步测试发现,当`targetSdkVersion`小于18时,回退功能正常。问题最终定位在WebChromeClient中onProgressChanged的js注入处理,去掉该处理后功能恢复。解决办法是在js注入时针对KitKat及以上版本使用`evaluateJavascript`,其余版本使用`loadUrl`。然而,为何仅在特定情况下`loadUrl`影响历史栈,以及为何仅在onProgressChanged中使用会有问题,仍然是未解之谜。

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

现象

    在Android4.4及以上的系统中,使用自定义的webview打开连接,在页面中再点击进入二级或三级页面,通过

   if (this.mWebView.canGoBack()) {
            this.mWebView.goBack();
            mActionBar.setCloseOnClickListener(new View.OnClickListener() {


                @Override
                public void onClick(View v) {
                    GlobalUtils.hideKeyboard(getActivity());
                    finish();
                }
            });
        } else {
            super.onBackPressed();
        }

做回退功能,发现不能正常回退。(此问题并非每次都出现,而是大概率出现。)


定位问题:

  1. 版本复查:由于属于版本迭代开发,此问题在新版本中被发现,先排查老版本是否存在此问题。结果:老版本不复现;下一步:找新老版本的不同。
  2. 找不同:对比新老版本不同。结果:webview相关没有相关改动,怀疑与系统版本相关;下一步:从怀疑点入手进行改动的验证。新老版本的AndroidManifest.xml中的android:targetSdkVersion不同,由17升为23。(由此牵出android:targetSdkVersion的含义及作用,后面的文章中会总结)
  3. 验证改动:将现有新版本的android:targetSdkVersion改为17,。结果:新版本的goBack功能正常;下一步:验证此字段的临界值。
  4. 进一步验证此参数:通过测试,当android:targetSdkVersion的版本小于登录18时,goBack均正常;下一步:矛头转向19webview。(写到这里大家可能就想到了,Android4.4之后webview的改动,由Webkit引擎换为Chrome,此改动在后续文章中总结)
  5. 19webview的验证:着重验证19及以上的webview,通过写最简demo(即只有webivew加载,其余均不做设置)。结果:最简webview可用;下一步:查看自定义webview基本webview的不同点,对可疑点进行一一验证
  6. 对比自定义webview与最简webview的使用:对于各种设置及js注入进行验证。结果:WebChromeClient中onProgressChanged的js注入处理去掉,功能恢复正常(此js注入方法并非只出现在此方法中,在onPageStarted、onPageFinished中均存在);下一步:js注入的方法入手(loadUrl("javascript:"))
  7. js注入:对比去掉此处的js注入与加上js注入的情况
  8. 查看webview历史栈:调用WebView.copyBackForwardList()得到历史栈及当前url的index等相关信息
  9. 对比历史栈信息:结合抓包工具及log信息,发现栈内信息存在两种情况,一种,符合预期;另一种,跟预期不同(此对比并非用同一个url来做的处理,由于URL中的内部实现会有不同,所以出现的现象也有差别)
  10. js注入方式的不同:查看
    • com.android.webview.chromium.WebViewChromium中的loadUrl其中有这么一段注释
    // For backwards compatibility, apps targeting less than K will have JS URLs evaluated
    // directly and any result of the evaluation will not replace the current page content.
    // Matching Chrome behavior more closely; apps targetting >= K that load a JS URL will
    // have the result of that URL replace the content of the current page.(翻译:为了向后兼容,target小于KitKAT的应用可直接执行js URL且执行的任何结果均不会影响当前页面的内容。为了与Chrome行为更加贴近;target大于等于KITKAT的应用加载js url时将会产生js url替换当前页面内容的结果)

结论:

    在js注入时,做如下处理:

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // 对于系统19及以上使用evaluateJavascript来做异步js注入
    mWebView.evaluateJavascript(JAVASCRIPT_URL.substring(JAVASCRIPT_SCHEME.length()), null);
    } else { // 其余的直接用loadUrl的方式处理
   mWebView.loadUrl(JAVASCRIPT_URL);
    }

仍存在的疑惑:

  1. 为什么在19及以上的系统,运用loadUrl的方式,会影响历史栈?
  2. 为什么只有在onProgressChanged中用loadUrl注入会存在问题?(在onPageStarted、onPageFinished等方法中,运用此方法均为正常)

<think>好的,用户遇到了window.history.back()无效问题,需要我来帮忙解决。首先,我需要了解用户的具体使用场景。可能是在UniApp的web-view组件中,或者是普通的网页开发?根据之前的对话历史,用户之前询问过UniApp中web-view的返回问题,所以这次的问题很可能也是在UniApp环境下出现的。 接下来,我要分析可能导致window.history.back()无效的原因。常见的原因可能有几个方面。首先,历史记录栈为空,这时候调用back()自然没有反应。其次,可能是在某些移动端环境下,比如iOS的WebView,默认行为可能被限制。另外,跨域问题或者页面内JavaScript错误也可能导致back()失效。还有可能是第三方框架或库干扰了浏览器的历史记录管理。 然后,我需要考虑如何验证这些可能性。比如,用户是否在调用back()之前确认过历史记录的存在?可以通过console.log(window.history.length)来检查。如果是UniApp的web-view,可能需要检查原生容器是否拦截了返回事件,或者是否有其他监听器阻止了默认行为。 用户可能没有意识到不同平台之间的差异,比如iOS和Android在处理物理返回时的不同行为。在UniApp中,可能需要结合原生的监听来处理返回操作,特别是在iOS上需要主动监听返回事件,而不是依赖默认行为。 另外,用户可能在某些单页应用(SPA)中使用了前端路由,比如Vue Router或React Router,这时候window.history.back()可能不会触发预期的页面导航,因为前端路由库可能接管了历史记录的管理。这时候需要确认是否正确地使用了前端路由的API,比如router.go(-1)代替。 还有可能用户在某些异步操作中调用了back(),但此时页面还未完全加载,导致无法正确执行。或者存在某些事件监听器,比如beforeunload,阻止了页面的回退。 接下来,我需要将这些可能的原因整理成结构化的解决方案,帮助用户逐步排查问题。可能需要分步骤指导用户检查历史记录栈、平台差异、第三方库影响、代码错误等。同时,提供替代方案,比如使用uni.navigateBack()或者结合UniApp的web-view组件方法,如canBack()和back()。 另外,用户可能需要调试方法,比如在页面中注入JavaScript代码来监控历史记录的变化,或者使用UniApp提供的evalJs方法来检查web-view内部的状态。还需要提醒用户注意跨域问题可能导致的安全限制,以及如何处理iOS和Android的不同行为。 最后,总结常见问题排查表,帮助用户快速定位问题,并提供优化建议,比如避免直接操作history对象,改用更可靠的前端路由方法,或者结合原生和H5的混合控制逻辑。</think>以下是针对`window.history.back()`失效问题的系统性排查指南及解决方案: --- ### 一、核心问题定位 `window.history.back()`失效通常由以下原因导致: ```mermaid graph TD A[失效表现] --> B{历史记录栈是否为空?} B -->|是| C[无操作] B -->|否| D{是否跨域/特殊容器?} D -->|是| E[安全限制] D -->|否| F{存在代码拦截?} F -->|是| G[事件监听阻止] F -->|否| H[框架路由冲突] ``` --- ### 二、分场景解决方案 #### 场景1:历史记录栈为空 **现象**:首次加载页面或单页应用无历史记录 **验证方法**: ```javascript console.log('当前历史记录长度:', window.history.length) ``` **解决方案**: ```javascript if (window.history.length > 1) { window.history.back() } else { // 降级处理 window.close() // 浏览器环境 // 或调用宿主关闭方法(如UniApp) uni.postMessage({ action: 'CLOSE_WEBVIEW' }) } ``` --- #### 场景2:WebView容器限制(常见于Hybrid App) **特征**:在Android/iOS原生容器中失效 **解决方案**: ```javascript // 判断是否在WebView环境 const isWebView = !!window.plus || navigator.userAgent.includes('Mobile') function safeBack() { if (isWebView) { // 与原生容器通信 if (window.uni) { uni.postMessage({ action: 'SYSTEM_BACK' }) } else if (window.JSBridge) { JSBridge.call('triggerBack') } } else { window.history.back() } } ``` --- #### 场景3:前端路由冲突(Vue/React SPA) **特征**:URL变化但视图未更新 **解决方案**: ```javascript // Vue Router示例 import router from '@/router' function hybridBack() { if (window.history.length > 1 && router.currentRoute.meta.isWebViewPage) { window.history.back() } else { router.go(-1) } } // React Router示例 import { useNavigate } from 'react-router-dom' function BackButton() { const navigate = useNavigate() const handleBack = () => { if (window.history.state && window.history.state.idx > 0) { window.history.back() } else { navigate(-1) } } return <button onClick={handleBack}>返回</button> } ``` --- ### 三、特殊容器处理方案 #### 微信小程序WebView ```javascript // 微信JS-SDK方案 document.addEventListener('WeixinJSBridgeReady', () => { WeixinJSBridge.call('closeWindow') }) // 降级方案 try { wx.miniProgram.navigateBack() } catch (e) { window.history.back() } ``` --- ### 四、调试技巧 #### 历史记录监控器 ```javascript // 注入调试代码 const historyWatcher = () => { const originPush = history.pushState const originReplace = history.replaceState history.pushState = function(...args) { console.log('[History] PUSH:', args[2]) return originPush.apply(this, args) } history.replaceState = function(...args) { console.log('[History] REPLACE:', args[2]) return originReplace.apply(this, args) } window.addEventListener('popstate', (e) => { console.log('[History] POP:', location.href) }) } ``` --- ### 五、跨平台兼容方案 ```javascript const universalBack = (webviewRef) => { // 平台检测 const isIOS = /iPad|iPhone|iPod/.test(navigator.platform) const isAndroid = /android/i.test(navigator.userAgent) if (isIOS && window.webkit) { // iOS WebView特殊处理 webkit.messageHandlers.backHandler.postMessage({}) } else if (isAndroid && window.AndroidInterface) { AndroidInterface.handleBack() } else if (window.history.length > 1) { window.history.back() } else { window.close() } } ``` --- ### 六、常见问题排查表 | 现象 | 快速检测方法 | 解决方案 | |----------------------|-----------------------------|-----------------------------| | 点击无任何反应 | `console.log(history.length)` | 添加历史记录检测逻辑 | | iOS设备失效 | 检查`window.plus`是否存在 | 使用`plus.key`监听物理返回 | | Vue/React路由跳转异常 | 对比`router.go(-1)`的效果 | 改用框架路由方法 | | 微信内置浏览器失效 | 检查`wx.miniProgram`API | 接入微信JS-SDK | | 页面自动刷新 | 观察`popstate`事件触发次数 | 移除冗余的事件监听器 | --- ### 七、高级优化建议 1. **混合控制策略**: ```javascript let lastBackTime = 0 function smartBack() { if (Date.now() - lastBackTime < 300) return lastBackTime = Date.now() if (window.history.length > 1) { window.history.back() setTimeout(() => { if (location.href === window.history.current) { uni.navigateBack() } }, 100) } } ``` 2. **错误边界处理**: ```javascript try { window.history.back() } catch (e) { console.error('History操作失败:', e) window.location.href = '/fallback' } ``` --- 通过以上方案,可系统性地解决`window.history.back()`失效问题。建议优先使用框架级路由控制,在混合开发场景中始终做好平台检测和降级处理。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值