前言
在上篇文章中,咱们介绍了怎么运用大局缓存池办理来进步WebView的加载速度,源码位置:WebViewPool.kt
在本篇文章中,咱们将介绍怎么运用规划形式来封装WebView组件,以完结个性化定制并让你的App网页愈加顺利。经过本文的学习,您将把握怎么运用单例、享元、制作者、桥接和装修等规划形式来优化WebView组件,而且完结不同场景下的灵敏装备。
单例规划形式
WebViewPool的作用是办理WebView目标的缓存池,经过重复运用现已创立的WebView目标来削减内存的开销和进步功用。
关于WebViewPool来说,咱们需求确保一个类只要一个实例,因而想到的是单例规划形式。那么单例规划形式有哪些好处呢?
- 节省资源:因为只要一个实例在内存中,可以防止创立多个目标所带来的不必要的资源消耗
- 简化调用:因为单例目标可以被大局拜访,因而可以简化调用过程,削减对目标之间传递参数等杂乱操作的需求
- 统一办理:因为只要一个实例存在,可以更方便地进行统一办理
- 进步灵敏性:在某些情况下,单例规划形式可以进步代码的灵敏性,例如经过运用单例形式来办理数据库连接池,可以防止频频地创立和毁掉连接目标,然后进步体系功用
internal class WebViewPool private constructor() {
private lateinit var mUserAgent: String
private lateinit var mWebViewPool: Array<PkWebView?>
companion object {
const val WEB_VIEW_COUNT = 3
val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
WebViewPool()
}
}
/**
* 初始化WebView池
*/
fun initWebViewPool(context: Context?, userAgent: String = "") {
if (context == null) return
mWebViewPool = arrayOfNulls(WEB_VIEW_COUNT)
mUserAgent = userAgent
for (i in 0 until WEB_VIEW_COUNT) {
mWebViewPool[i] = createWebView(context, userAgent)
}
}
/**
* 获取webView
*/
fun getWebView(): PkWebView? {
checkIsInitialized()
for (i in 0 until WEB_VIEW_COUNT) {
if (mWebViewPool[i] != null) {
val webView = mWebViewPool[i]
mWebViewPool[i] = null
return webView
}
}
return null
}
private fun checkIsInitialized() {
if (!::mWebViewPool.isInitialized) {
throw UninitializedPropertyAccessException("Please call the PkWebViewInit.init method for initialization in the Application")
}
}
/**
* Activity毁掉时需求开释当时WebView
*/
fun releaseWebView(webView: PkWebView?) {
checkIsInitialized()
webView?.apply {
stopLoading()
removeAllViews()
clearHistory()
destroy()
(parent as ViewGroup?)?.removeView(this)
for (i in 0 until WEB_VIEW_COUNT) {
if (mWebViewPool[i] == null) {
mWebViewPool[i] = createWebView(webView.context, mUserAgent)
return
}
}
}
}
private fun createWebView(context: Context, userAgent: String): PkWebView? {
val webView = PkWebView(context)
WebViewManager.instance.apply {
initWebViewSetting(webView, userAgent)
initWebChromeClient(webView)
initWebClient(webView)
}
return webView
}
}
享元规划形式
- 咱们经过创立一个固定巨细的目标池,以防止频频地创立和毁掉WebView实例。
- 在初始化时,将创立WebView实例并存储在目标池中。
- 当需求WebView时,从目标池中获取空闲的WebView实例,而不是每次都创立新的实例。
- 经过这种办法,可以节省内存和CPU资源,并进步使用程序功用。
制作者规划形式
在之前,咱们对WebView缓存池初始化的时分,运用了一个PkWebViewInit类
class PkWebViewInit private constructor() {
companion object {
val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
PkWebViewInit()
}
}
fun init(context: Context?,userAgent:String="") {
WebViewPool.instance.initWebViewPool(context,userAgent)
}
}
之前WebViewPool中对WebView进行参数设置,如WebViewSetting、WebViewClient等都是写在架构库里边的,关于用户来说是不可改变的,咱们需求支撑用户可以进行扩展或许修正,应该怎么办?
现在假设咱们有个需求:用户需求动态设置UserAgent,WebViewSetting、WebViewClient和WebView的数量等,最终创立一个用户自界说的WebView。
这种将一个杂乱目标的构建过程分解为多个简略目标的构建过程,使得相同的构建过程可以创立不同的表示,运用的规划形式就是制作者规划形式
因为代码过长,这儿只截取部分代码,咱们可以自行查看源码PkWebViewInit,以下是部分代码
class PkWebViewInit private constructor() {
private var mWebViewController: WebViewController = WebViewController()
fun initPool() {
WebViewPool.instance.initWebViewPool(mWebViewController.P)
}
class Builder constructor(context: Context) {
private val P: WebViewController.WebViewParams
private var mPkWebViewInit: PkWebViewInit? = null
init {
P = WebViewController.WebViewParams(context)
}
/**
* 设置WebViewSetting
*/
fun setWebViewSetting(webViewSetting: InitWebViewSetting): Builder {
P.mWebViewSetting = webViewSetting
return this
}
/**
* 设置UserAgent
*/
fun setUserAgent(userAgent: String = ""): Builder {
P.userAgent = userAgent
return this
}
/**
* 设置webView的count
*/
fun setWebViewCount(count: Int): Builder {
P.mWebViewCount = count
return this
}
fun build() {
if (mPkWebViewInit == null) {
create()
}
mPkWebViewInit!!.initPool()
}
private fun create(): PkWebViewInit {
val pkWebViewInit = PkWebViewInit()
P.apply(pkWebViewInit.mWebViewController, P)
mPkWebViewInit = pkWebViewInit
return pkWebViewInit
}
}
}
internal class WebViewController {
var P: WebViewParams? = null
private set
class WebViewParams(val context: Context) {
var mWebViewCount: Int = 3
var userAgent: String = ""
var mWebViewSetting: InitWebViewSetting = DefaultInitWebViewSetting()
var mWebViewClientCallback: WebViewClientCallback = DefaultWebViewClientCallback()
var mWebViewChromeClientCallback: WebViewChromeClientCallback =
DefaultWebViewChromeClientCallback()
fun apply(controller: WebViewController, P: WebViewParams) {
controller.P = P
}
}
}
上述经过Builder规划形式将一切客户端传入的参数封装到WebViewController.WebViewParams,在build之后将WebViewController.WebViewParams参数传给了WebViewPool.initWebViewPool进行创立WebView缓存池
战略规划形式:对WebViewSetting进行封装
当咱们需求经过不同的完结类对某个办法进行具体的完结,以便在运行时动态切换成不同的战略,这时分就可以考虑用战略规划形式
interface InitWebViewSetting {
fun initWebViewSetting(webView: WebView, userAgent: String? = null)
}
class DefaultInitWebViewSetting :InitWebViewSetting{
override fun initWebViewSetting(webView: WebView, userAgent: String?) {
val webSettings: WebSettings = webView.settings
WebView.setWebContentsDebuggingEnabled(true)
webSettings.apply {
//答应网页JavaScript
javaScriptEnabled = true
allowFileAccess = true
useWideViewPort = true
// 支撑混合内容(https和http共存)
mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
// 敞开DOM存储API
domStorageEnabled = true
// 敞开数据库存储API
databaseEnabled = true
//不答应WebView中跳转到其他链接
setSupportMultipleWindows(false)
setSupportZoom(false)
builtInZoomControls = false
//cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK
}
if (!TextUtils.isEmpty(userAgent)) {
webSettings.userAgentString = userAgent
}
CookieManager.setAcceptFileSchemeCookies(true)
CookieManager.getInstance().setAcceptCookie(true)
}
}
咱们可以完结InitWebViewSetting的接口,重写initWebViewSetting办法,然后经过PkWebViewInit#Builder.setWebViewSetting完结不同的WebViewSetting战略
桥接规划形式:对WebViewClient进行封装
WebViewClient可以拦截WebView的各种工作,如页面的开端加载、加载完结、加载过错等,并答应开发者自界说相应的处理逻辑,然后完结更灵敏、定制化的WebView行为。 通常咱们一般的写法
public class PkWebViewClient extends WebViewClient {
private ProgressBar mProgressBar;
public PkWebViewClient(ProgressBar progressBar) {
mProgressBar = progressBar;
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
mProgressBar.setVisibility(ProgressBar.VISIBLE);
}
@Override
public void onPageFinished(WebView view, String url) {
mProgressBar.setVisibility(ProgressBar.GONE);
}
}
webView.setWebViewClient(new PkWebViewClient())
咱们需求是用户可自界说WebViewClient,那必定有人说,咱们可以持续用战略规划形式呀
interface InitWebViewClient {
fun initWebViewClient(webView: WebView, userAgent: String? = null)
}
class DefaultInitWebViewSetting :InitWebViewSetting{
override fun initWebViewClient(webView: WebView, userAgent: String? = null) {
webView.setWebViewClient(new PkWebViewClient())
}
}
可是关于用户来说,他并不想自己再次调用setWebViewClient,或许来说,用户的行为其实是不确定的,他或许调用setWebViewClient,也有或许调用setWebViewChromeClient办法,所以setWebViewClient这个办法咱们需求内部设置了,把PkWebViewClient这个完结供给出去。
关于客户端来说最简略的办法只需求完结一个接口,并重写接口中的一切的办法就可以完结自己想要的逻辑是最好的。
interface WebViewClientCallback {
fun onPageStarted(view: WebView, url: String,fragment: WebViewFragment?)
fun onPageFinished(view: WebView, url: String, fragment: WebViewFragment?)
fun shouldOverrideUrlLoading(view: WebView, url: String,fragment: WebViewFragment?): Boolean
fun onReceivedError(view: WebView?, err: Int, des: String?, url: String?,fragment: WebViewFragment?)
}
class ReplaceWebViewClient:WebViewClientCallback {
override fun onPageStarted(view: WebView, url: String, fragment: WebViewFragment?) {
}
override fun onPageFinished(view: WebView, url: String, fragment: WebViewFragment?) {
}
override fun shouldOverrideUrlLoading(
view: WebView,
url: String,
fragment: WebViewFragment?
): Boolean {
return false
}
override fun onReceivedError(
view: WebView?,
err: Int,
des: String?,
url: String?,
fragment: WebViewFragment?
) {
}
}
初始化的时分直接调用setWebViewClient即可
PkWebViewInit.Builder(this)
.setWebViewClient(ReplaceWebViewClient())
.build()
WebView架构库中咱们需求将回调与WebViewClient进行绑定
class MyWebViewClient constructor(val webViewClientCallback: WebViewClientCallback?) :
WebViewClient() {
private var fragment: WebViewFragment? = null
override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) {
if (fragment == null) {
fragment = WebViewManager.instance.getFragment()
}
fragment?.onPageStarted(view, url)
webViewClientCallback?.onPageStarted(view, url, fragment)
}
override fun onPageFinished(view: WebView, url: String) {
if (fragment == null) {
fragment = WebViewManager.instance.getFragment()
}
fragment?.onPageFinished(view, url)
webViewClientCallback?.onPageFinished(
view,
url,
fragment
)
}
}
这样MyWebViewClient和WebViewClientCallback就进行联动,接着webView调用setWebViewClient即可 WebViewPool.createWebView
private fun createWebView(
params: WebViewController.WebViewParams, userAgent: String
): PkWebView {
val webView = PkWebView(params.context)
params.apply {
mWebViewSetting.initWebViewSetting(webView, userAgent)
webView.webViewClient=MyWebViewClient(params.mWebViewClientCallback)
}
return webView
}
按道理上面其完结已可以达到咱们想要的作用,可是咱们会发现这儿createWebView其实是干了两件工作,
- 创立WebView
- webView设置WebViewClient参数
也就违反了单一职责,那什么是单一职责呢? 但一职责是指一个类或许办法只要一项职责,即只负责一件工作。这个准则旨在下降类或许办法的杂乱度,进步代码的可保护性和可读性。假如一个类或许办法负责多个职责,那么它的修正和保护会变得困难,也容易引起意外的影响。因而,单一职责准则是面向目标编程中非常重要的一个规划准则。
所以这儿将webView.webViewClient=MyWebViewClient(params.mWebViewClientCallback)办法放到另一个类进行,这个类供给一个initWebViewClient办法。
internal class WebViewClientImpl(webViewClientCallback: WebViewClientCallback?) :
AbstractWebViewClient(webViewClientCallback) {
override fun initWebClient(webView: WebView) {
val webViewClient = WebViewClientImpl(webViewClientCallback)
webView.webViewClient = webViewClient
}
}
将MyWebViewClient变成笼统类,并供给initWebViewClient
class AbstractWebViewClient constructor(val webViewClientCallback: WebViewClientCallback?) :
WebViewClient() {
private var fragment: WebViewFragment? = null
abstract fun initWebClient(webView: WebView)
override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) {
if (fragment == null) {
fragment = WebViewManager.instance.getFragment()
}
fragment?.onPageStarted(view, url)
webViewClientCallback?.onPageStarted(view, url, fragment)
}
override fun onPageFinished(view: WebView, url: String) {
if (fragment == null) {
fragment = WebViewManager.instance.getFragment()
}
fragment?.onPageFinished(view, url)
webViewClientCallback?.onPageFinished(
view,
url,
fragment
)
}
}
小总结
什么是桥接规划形式
桥接规划形式是一种结构性规划形式,用于将笼统部分与完结部分别离,然后使他们可以独立的改变。桥接形式为这两个部分供给了不同的层次结构,并经过托付机制将它们连接起来。这样,可以在不修正原有代码的情况下,动态地改变它们之间的关系,以适应新的需求或平台。
长处
- 将笼统部分与完结部分别离,使得它们可以独立地改变(上面的AbstractWebViewClient和WebViewClientCallback)。
- 可以在运行时动态地改变笼统接口的完结部分,而无需修正现已存在的代码。
- 可以对完结部分进行扩展,而无需修正笼统部分的代码。
- 进步了代码的可扩展性和可保护性,使得程序更易于了解和修正
装修规划形式
用户启动WebViewActivity的时分,一般的时分都是这么调用
val bean= WebViewConfigBean("https://www.baidu.com")
val intent=Intent(this, WebViewActivity::class.java)
intent.putExtra(WebViewHelper.LIBRARY_WEB_VIEW,bean)
startActivity(intent)
用户有时分并不知道你里边的的key是多少,也不并关怀,只想传入一个url,你就能打开就行了,所以咱们就可以创立一个类统一办理WebViewActivity的启动
class DefaultH5IntentConfigImpl {
fun startActivity(context: Context?, url: String) {
context?.startActivity(
Intent(context, WebViewActivity::class.java)
.putExtra(WebViewConstants.LIBRARY_WEB_VIEW, WebViewConfigBean(url))
)
}
fun startActivityForResult(context: Activity?, url: String, requestCode: Int) {
val intent = Intent(context, WebViewActivity::class.java)
.putExtra(WebViewConstants.LIBRARY_WEB_VIEW, WebViewConfigBean(url))
context?.startActivityForResult(intent, requestCode)
}
fun startActivityForResult(context: Fragment?, url: String, requestCode: Int) {
if (context == null || context.context == null) return
val intent = Intent(context.context, WebViewActivity::class.java)
.putExtra(WebViewConstants.LIBRARY_WEB_VIEW, WebViewConfigBean(url))
context.startActivityForResult(intent, requestCode)
}
fun startActivityForResult(
context: FragmentActivity?,
launcher: ActivityResultLauncher<Intent>?,
url: String
) {
val intent = Intent(context, WebViewActivity::class.java)
.putExtra(WebViewConstants.LIBRARY_WEB_VIEW, WebViewConfigBean(url))
launcher?.launch(intent)
}
fun startActivityForResult(
context: Fragment?,
launcher: ActivityResultLauncher<Intent>?,
url: String
) {
if (context == null || context.context == null) return
val intent = Intent(context.context, WebViewActivity::class.java)
.putExtra(WebViewConstants.LIBRARY_WEB_VIEW, WebViewConfigBean(url))
launcher?.launch(intent)
}
}
这是最根底的startActivity,假如用户想对这根底的startActivity修正呢?所以咱们就可以界说一个接口
interface H5IntentConfig {
fun startActivity(context: Context?, url: String)
fun startActivityForResult(context: Activity?, url: String, requestCode: Int)
fun startActivityForResult(context: Fragment?, url: String, requestCode: Int)
fun startActivityForResult(
context: FragmentActivity?,
launcher: ActivityResultLauncher<Intent>?,
url: String
)
fun startActivityForResult(
context: Fragment?,
launcher: ActivityResultLauncher<Intent>?,
url: String
)
}
之后让DefaultH5IntentConfigImpl完结H5IntentConfig接口即可,随后创立H5Utils
class H5Utils(decoratorConfig: H5IntentConfig = DefaultH5IntentConfigImpl())
根底的startActivity功用已完结,可是此时用户说我想在不改根底的startActivity的根底之上,对url进行重新拼接,比方加上token,应该怎么做?这时分我就可以用装修规划形式,对H5IntentConfig功用进行扩展
abstract class AbstractH5IntentConfigDecorator(private val decoratorConfig: H5IntentConfig) :
H5IntentConfig {
override fun startActivity(context: Context?, url: String) {
decoratorConfig.startActivity(context, url)
}
override fun startActivityForResult(context: Activity?, url: String, requestCode: Int) {
decoratorConfig.startActivityForResult(context, url, requestCode)
}
override fun startActivityForResult(context: Fragment?, url: String, requestCode: Int) {
decoratorConfig.startActivityForResult(context, url, requestCode)
}
override fun startActivityForResult(
context: Fragment?,
launcher: ActivityResultLauncher<Intent>?,
url: String
) {
decoratorConfig.startActivityForResult(context,launcher,url)
}
override fun startActivityForResult(
context: FragmentActivity?,
launcher: ActivityResultLauncher<Intent>?,
url: String
) {
decoratorConfig.startActivityForResult(context,launcher,url)
}
}
class H5Utils(decoratorConfig: H5IntentConfig = DefaultH5IntentConfigImpl()) :
AbstractH5IntentConfigDecorator(decoratorConfig)
运用也就变得简略了
H5Utils(ReplaceH5ConfigDecorator())
.startActivityForResult(
this, launcher,
"https://qa-xbu-activity.at-our.com/mallIndex")
根底startActivity功用替换
class ReplaceH5IntentConfigImpl:H5IntentConfig {
override fun startActivity(context: Context?, url: String) {
Log.e("TAG","ReplaceH5IntentConfigImpl url:$url")
}
override fun startActivityForResult(context: Activity?, url: String, requestCode: Int) {
}
override fun startActivityForResult(context: Fragment?, url: String, requestCode: Int) {
}
override fun startActivityForResult(
context: FragmentActivity?,
launcher: ActivityResultLauncher<Intent>?,
url: String
) {
}
override fun startActivityForResult(
context: Fragment?,
launcher: ActivityResultLauncher<Intent>?,
url: String
) {
}
}
装修规划形式扩展功用修正,对url扩展,添加token
class ReplaceH5ConfigDecorator:AbstractH5IntentConfigDecorator(ReplaceH5IntentConfigImpl()){
override fun startActivity(context: Context?, url: String) {
var replaceUrl=url
replaceUrl+="/token?=111"
super.startActivity(context, replaceUrl)
}
}
结语
经过运用规划形式,咱们可以更好地封装WebView,并进步Android使用的代码质量和开发效率。在上文中,咱们用到了单例规划形式、享元规划形式、制作者规划形式、战略规划形式、桥接规划形式和装修规划形式。
以战略形式为例,咱们将WebViewClient初始化的职责别离出俩,并经过界说接口和完结类来完结高度灵敏的定制化。这种办法使得咱们可以快速、方便地完结个性化需求,一起也让整个使用的代码结构愈加清晰和易于保护。
根据实际情况选择适宜的形式进行规划和完结,既能进步使用的可扩展性和可保护性,也可以帮组开发人员更好地了解和把握面向目标编程的思维。
本项目源码:PkWebView