前言

在上篇文章中,咱们介绍了怎么运用大局缓存池办理来进步WebView的加载速度,源码位置:WebViewPool.kt

在本篇文章中,咱们将介绍怎么运用规划形式来封装WebView组件,以完结个性化定制并让你的App网页愈加顺利。经过本文的学习,您将把握怎么运用单例、享元、制作者、桥接和装修等规划形式来优化WebView组件,而且完结不同场景下的灵敏装备。

单例规划形式

WebView组件封装(二)——怎样用设计模式封装WebView,轻松实现个性化定制,让你的App网页更加顺畅
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,轻松实现个性化定制,让你的App网页更加顺畅

  • 咱们经过创立一个固定巨细的目标池,以防止频频地创立和毁掉WebView实例。
  • 在初始化时,将创立WebView实例并存储在目标池中。
  • 当需求WebView时,从目标池中获取空闲的WebView实例,而不是每次都创立新的实例。
  • 经过这种办法,可以节省内存和CPU资源,并进步使用程序功用。

制作者规划形式

WebView组件封装(二)——怎样用设计模式封装WebView,轻松实现个性化定制,让你的App网页更加顺畅
在之前,咱们对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进行封装

WebView组件封装(二)——怎样用设计模式封装WebView,轻松实现个性化定制,让你的App网页更加顺畅
当咱们需求经过不同的完结类对某个办法进行具体的完结,以便在运行时动态切换成不同的战略,这时分就可以考虑用战略规划形式

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进行封装

WebView组件封装(二)——怎样用设计模式封装WebView,轻松实现个性化定制,让你的App网页更加顺畅
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其实是干了两件工作,

  1. 创立WebView
  2. 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)。
  • 可以在运行时动态地改变笼统接口的完结部分,而无需修正现已存在的代码。
  • 可以对完结部分进行扩展,而无需修正笼统部分的代码。
  • 进步了代码的可扩展性和可保护性,使得程序更易于了解和修正

装修规划形式

WebView组件封装(二)——怎样用设计模式封装WebView,轻松实现个性化定制,让你的App网页更加顺畅
用户启动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