Android WebView — 实现保存页面功能
作者:访客发布时间:2023-12-25分类:程序开发学习浏览:91
在开发公司App期间遇到一个需求,在游戏页中使用WebView
展示游戏网页,退出游戏页再次进入时,如果是同一个游戏就直接回到退出时的页面。按照一般的做法,在页面关闭后销毁WebView
,再次进入游戏页时,不论是否为同个游戏肯定会重新加载。
实现保存页面功能
之前同事分享了一篇提高WebView
渲染效率的文章,其中提到可以提前通过MutableContextWrapper
创建WebView
并缓存起来,在需要的页面里从缓存中获取WebView
,并把MutableContextWrapper
切换为对应Activity
或Fragment
的Context
。根据文中的测试结果来看,提升的效率用户基本无法感知,但正好可以用来实现我们需要的功能。
具体方案如下:
- 在一个单例类(也可以直接用
Application
)中,创建一个Map
用于存放需要保留的WebView
和其打开的网页链接。 - 在进入页面时,判断外部传入的网页链接和缓存的网页链接是否为同一个,是就使用缓存的
WebView
,不是就销毁缓存的WebView
并创建一个新的。 - 关闭页面时,将
MutableContextWrapper
设置为Application
的Context
,并将WebView
从页面布局中移除。
示例代码如下:
- 单例类
object WebVIewCacheController {
// 经过实际测试需要如此实现
val webViewContextWrapperCache = MutableContextWrapper(ExampleApplication.exampleContext)
// Key为网页链接,Value为WebView
val webViewCache = ArrayMap<String, WebView?>()
}
- 示例页面
class ReservePageExampleActivity : AppCompatActivity() {
private lateinit var binding: LayoutReservePageExampleActivityBinding
private var currentWeb: WebView? = null
private val webChromeClient = object : WebChromeClient() {
override fun onProgressChanged(view: WebView, newProgress: Int) {
super.onProgressChanged(view, newProgress)
binding.pbWebLoadProgress.run {
post { progress = newProgress }
if (newProgress >= 100 && visibility == View.VISIBLE) {
postDelayed({ visibility = View.GONE }, 500)
}
}
}
}
private val webViewClient = object : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
binding.pbWebLoadProgress.run { post { visibility = View.VISIBLE } }
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = LayoutReservePageExampleActivityBinding.inflate(layoutInflater).also {
setContentView(it.root)
}
onBackPressedDispatcher.addCallback(object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// 处理系统返回事件
handleBackPress()
}
})
intent.getStringExtra(PARAMS_LINK_URL)?.let { websiteUrl ->
// 切换Context
WebVIewCacheController.webViewContextWrapperCache.baseContext = this
// 获取缓存
val cacheWebsiteUrl = WebVIewCacheController.webViewCache.entries.firstOrNull()?.key
currentWeb = WebVIewCacheController.webViewCache.entries.firstOrNull()?.value
if (websiteUrl == cacheWebsiteUrl) {
// 加载同个网页,使用缓存的WebView
currentWeb?.let {
// 确保控件没有父控件
removeViewParent(it)
// 添加到页面布局最底层。
binding.root.addView(it, 0)
}
} else {
// 加载不同网页,释放旧的WebView并创建新的
createWebView(websiteUrl)
}
}
}
private fun createWebView(webSiteUrl: String) {
releaseWebView(currentWeb)
WebVIewCacheController.webViewCache.clear()
currentWeb = WebView(WebVIewCacheController.webViewContextWrapperCache).apply {
initWebViewSetting(this)
// 设置背景为黑色,根据自己需求可以忽略
setBackgroundColor(ContextCompat.getColor(this@ReservePageExampleActivity, R.color.color_black_222))
layoutParams = ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_PARENT, ConstraintLayout.LayoutParams.MATCH_PARENT)
// 确保控件没有父控件
removeViewParent(this)
// 添加到页面布局最底层。
binding.root.addView(this, 0)
loadUrl(webSiteUrl)
// 缓存WebView
WebVIewCacheController.webViewCache[webSiteUrl] = this
}
}
@SuppressLint("SetJavaScriptEnabled")
private fun initWebViewSetting(webView: WebView) {
val settings = webView.settings
settings.cacheMode = WebSettings.LOAD_DEFAULT
settings.domStorageEnabled = true
settings.allowContentAccess = true
settings.allowFileAccess = true
settings.allowFileAccessFromFileURLs = true
settings.allowUniversalAccessFromFileURLs = true
settings.useWideViewPort = true
settings.loadWithOverviewMode = true
settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
settings.javaScriptEnabled = true
settings.javaScriptCanOpenWindowsAutomatically = true
webView.webChromeClient = webChromeClient
webView.webViewClient = webViewClient
}
private fun handleBackPress() {
if (currentWeb?.canGoBack() == true) {
currentWeb?.goBack()
} else {
minimize()
}
}
private fun minimize() {
// 切换Context
WebVIewCacheController.webViewContextWrapperCache.baseContext = applicationContext
// 暂时先把WebView移出布局
currentWeb?.let { binding.root.removeView(it) }
finish()
}
private fun releaseWebView(webView: WebView?) {
webView?.run {
loadDataWithBaseURL(null, "", "text/html", "utf-8", null)
clearHistory()
clearCache(false)
binding.root.removeView(this)
destroy()
}
}
private fun removeViewParent(view: View) {
try {
val parent = view.parent
(parent as? ViewGroup)?.removeView(view)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
效果如图:
示例
演示代码已在示例Demo中添加。
ExampleDemo github
ExampleDemo gitee
内存占用问题
WebView
通常会占用不少内存,在我司的App中其占用内存基本不会小于100M,甚至能到200M以上。在WebView
占用了大量内存的情况下,如果App中还有其他的功能对内存需求较高,就容易出现OOM。其实在页面销毁时正确的销毁WebView
可以释放其占用的内存,但就无法实现我们需要的功能,因此需要另寻他法。
跟leader讨论后,决定采用子进程的方案,即WebView
单独运行在子进程中,不同进程的内存分配是独立的,所以基本可以解决OOM问题。
子进程的配置很简单,在AndroidManifest
中配置一下WebView
所在页面即可,如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
......>
<activity
android:name=".web.reserve.ReservePageExampleActivity"
android:process=":webviewpage" />
</application>
</manifest>
独立进程也会带来一些新的问题 :
- 跨进程通信。
这点在我司的App中基本无需额外处理,因为其他页面与游戏页的交互仅仅只有传入网页链接,通过Bundle
带入即可。若Bundle
无法满足需求,可以考虑使用广播、Message
、ContentProvider
、AIDL
等跨进程通信方案。
- 子进程初始化时,会有一小段白屏时间(与应用冷启动一样)。
初始化白屏的体验效果不好,这边提供一个思路,在WebView
所在页面的前置页加载数据的同时初始化子进程,等子进程初始化完成后再结束加载,并配置android:windowDisablePreview
来隐藏启动页面时的白屏。
- 程序开发学习排行
-
- 1鸿蒙HarmonyOS:Web组件网页白屏检测
- 2HTTPS协议是安全传输,为啥还要再加密?
- 3HarmonyOS鸿蒙应用开发——数据持久化Preferences
- 4记解决MaterialButton背景颜色与设置值不同
- 5鸿蒙HarmonyOS实战-ArkUI组件(RelativeContainer)
- 6鸿蒙HarmonyOS实战-ArkUI组件(Stack)
- 7[Android][NDK][Cmake]一文搞懂Android项目中的Cmake
- 8Android广播如何解决Sending non-protected broadcast问题
- 9鸿蒙HarmonyOS实战-ArkUI组件(mediaquery)
- 最近发表
-
- WooCommerce最好的WordPress常用插件下载博客插件模块的相关产品
- 羊驼机器人最好的WordPress常用插件下载博客插件模块
- IP信息记录器最好的WordPress常用插件下载博客插件模块
- Linkly for WooCommerce最好的WordPress常用插件下载博客插件模块
- 元素聚合器Forms最好的WordPress常用插件下载博客插件模块
- Promaker Chat 最好的WordPress通用插件下载 博客插件模块
- 自动更新发布日期最好的WordPress常用插件下载博客插件模块
- WordPress官方最好的获取回复WordPress常用插件下载博客插件模块
- Img to rss最好的wordpress常用插件下载博客插件模块
- WPMozo为Elementor最好的WordPress常用插件下载博客插件模块添加精简版