一篇文章搞定《WebView的优化及封装》
一篇文章搞定《WebView的优化及封装》
- 前言
- WebView的过程分析
- 确定优化方案
- 一、预加载,复用缓冲池(初始化优化)
- 优化的解析说明
- 具体的实现
- 二、预置模版(请求、渲染优化)
- 优化的解析说明
- 具体的实现
- 1、离线包
- 2、预获取数据、JS内容注入
- 3、内联离线资源文件
- 三、拦截请求与共享缓存(请求、渲染优化)
- 优化的解析说明
- 具体代码的实现
- 其他问题的处理
- DNS、CDN优化(请求优化)
- 白屏检测(异常处理)
- 问题的解析说明
- 问题的解析思路
- 发现问题后的对策
- 代码实现
- 版本问题带来的白屏(异常处理)
- 总结
前言
上篇对WebView大家肯定都有了一个基本的认知,和入门。
本篇继续对他进行一些工程上的优化。
主要原因:WebView加载过慢,影响用户体验,毕竟原生是秒开的。
WebView的过程分析
我们只有知道他的加载Web页面的过程了,之后对每步的耗时进行分析,才能去确定我们要优化的方向,和优化点。
下面我们就来有意识的去分析这个加载Web页面的过程。
整体的话主要分为下面三个阶段
- 初始化阶段:也就是创建WebView
- 网络阶段:也就是请求资源的过程
- 渲染阶段:页面的DOM渲染(文字、图片、等等)
详细过程的时间(引用百度APP统计的详细时间)
他将整个过程分为4个阶段,因为图片加载时发生在正文、整体页面渲染之后,再去JS请求网络的。
- 整体过程:初始化 Webview -> 请求页面 -> 下载数据 -> 解析HTML -> 请求 JS/CSS资源 -> DOM 渲染 -> 解析 JS 执行 -> JS 请求数据 -> 解析渲染 -> 下载渲染图片
- 总时长:初始化组件260ms + 请求HTML数据加HTML解析270ms + 请求JS/CSS资源解析加正文数据1400ms + 图片下载加渲染600ms。共计2530ms,2.5秒那对用户来说可真是一个《漫长的季节》
确定优化方案
- 初始化:首先初始化肯定是比较好优化的,毕竟可以通过预加载,缓存,复用等多种手段。
- HTML处理过程:可以将一些公共内容,在本地加载(预置模版,静默更新)
- JS/CSS资源和正文数据:可以将一些公共内容,在本地加载(预置模版,静默更新),提前获取(比如列表请求,页面数据请求等其他原生的数据请求)
- 图片资源:可以拦截JS的请求、用缓存、本地、原生请求。
OK,那么就按照这几步挨个看看吧。
(PS:简单实现、主讲思路。根据具体的业务场景,添油加醋)
一、预加载,复用缓冲池(初始化优化)
我们可以选择在合适的时机 预加载 WebView 并存入 缓存池 中,等要用到时再直接从缓存池中取,从而缩短显示首屏页面的时间
优化的解析说明
目的:缩短完成首屏页面的时间。
原理:用空间换时间的做法,采用缓存池。
三个需要注意的问题:
- 触发时机如何选
- IdleHandler 来解决。通过 IdleHandler 提交的任务只有在当前线程关联的 MessageQueue 为空的情况下才会被执行,因此通过 IdleHandler 来执行预创建可以保证不会影响到当前主线程任务
- Context 如何选
- MutableContextWrapper 是系统提供的 Context 包装类,其内部包含一个 baseContext。MutableContextWrapper 所有的内部方法都会交由 baseContext 来实现,且 MutableContextWrapper 允许外部替换它的 baseContext。
- 因此我们可以在一开始的时候使用 Application 作为 baseContext,等到 WebView 和 Activity或者Fragment绑定的时候,切换到当前组件的Context。当组件销毁之后再切换回Application。
- 复用的WebView模版(结合下面的预置模版有奇效)
- 可以把已经加载解析过HTML、CSS、JS的webview缓存起来,后面用
具体的实现
- 双重检验锁,的单例实现
- 利用IdleHandler来初始化
- 获取和回收方法
/*** Author: mql* Date: 2023/8/30* Describe : WebView的缓存复用池*/
class WebViewPool private constructor(){//1、使用双重检验锁,进行单例的实现companion object{private const val TAG = "WebViewPool"@Volatileprivate var instance: WebViewPool? = nullfun getInstance() : WebViewPool{return instance ?: synchronized(this) {instance ?: WebViewPool().also { instance = it }}}}//2、初始化//采用Stack进行存储复用private val webViewPool = Stack<BaseWebView>()//保证线程同步的安全private val lock = byteArrayOf()private var poolMaxSize = 1/*** 设置 webview 池容量*/fun setMaxPoolSize(size: Int) {synchronized(lock) { poolMaxSize = size }}/*** 初始化webview 放在list中*/fun init(context: Context, initSize: Int = poolMaxSize) {Looper.myQueue().addIdleHandler{Log.d(TAG, "init WebViewPool WebViewCacheStack Size = " + webViewPool.size)if(webViewPool.size < poolMaxSize){val view = BaseWebView(MutableContextWrapper(context))view.webViewClient = BaseWebViewClient() //自定义的webViewClientview.webChromeClient = BaseWebChromeClient() //自定义的webChromeClientwebViewPool.push(view)}false}}//3、提供从pool中获取WebViewfun getWebView(context: Context): BaseWebView {synchronized(lock) {val webView: BaseWebViewif (webViewPool.size > 0) {webView = webViewPool.pop()Log.d(TAG, "getWebView from webViewPool")} else {webView = BaseWebView(MutableContextWrapper(context))Log.d(TAG, "getWebView from create")}val contextWrapper = webView.context as MutableContextWrappercontextWrapper.baseContext = context// 默认设置webView.webChromeClient = BaseWebChromeClient()webView.webViewClient = BaseWebViewClient()return webView}}//4、使用结束的回收WebViewfun recycle(webView: BaseWebView) {// 释放资源webView.release()// 根据池容量判断是否销毁val contextWrapper = webView.context as MutableContextWrappercontextWrapper.baseContext = webView.context.applicationContextsynchronized(lock) {if (webViewPool.size < poolMaxSize) {webViewPool.push(webView)} else {webView.destroy()}}}
}
使用的示例
//初始化 Application
WebViewPool.getInstance().setMaxPoolSize(min(Runtime.getRuntime().availableProcessors(), 3))
WebViewPool.getInstance().init(applicationContext)//获取WebView
private val mWebView by lazy { WebViewPool.getInstance().getWebView(this) }//回收Webview
fun onDestroy() {WebViewPool.getInstance().recycle(this)
}
二、预置模版(请求、渲染优化)
我们旨在减少网络请求HTML、CSS、JS、数据内容的时间。还有减少解析HTML的时间。所以需要用缓存 or 保存的思想去做。
优化的解析说明
目的:减少网络请求时间
原理:空间换时间
三个需要注意的问题:
- 离线包:离线具有固定模版特性的HTML、JS、CSS
- 每次打包时均预置最新的模板文件到客户端中,每套模板文件均有特定的版本号
- App 在后台定时去静默更新(时机:可以在启动App和前后台切换的时候去检查的)
- 预获取数据、JS内容注入:提前获取数据内容,利用JS进行注入
- 列表页接口返回列表数据的时候带上JS的数据内容下发
- 利用JS的方法调用进行数据的注入,展示。
- ps:虽然减少了JS请求数据的时间,但是在前一步请求列表的时候,会消耗一些流量
- 如果完全离线内联好HTML、JS、CSS文件。再加载WebView
- 正常来说,WebView 需要在加载完主 HTML 之后再去加载 HTML 中的 JS 和 CSS,需要多次 IO 操作
- 我们提前把他内联在一起,能减少这多次IO
其实我们在正常的业务中只实现离线包就可以了。后面的内容如果追求极致的话。倒是可以去尝试一下。
具体的实现
1、离线包
这个说一下具体的流程,就不附带代码了,代码涉及到业务。因为离线包的一个静默更新会涉及到后端的小伙伴的配合。
具体使用倒是可以给大家模拟一个简单代码去看看
具体流程的流程图如下:
那么在有了离线包之后,我们怎么去使用呢?或者说怎么拦截请求,去使用本地的资源?
- 判断URL并解析,看看是不是本地资源有的
- 加载本地资源
- 包装WebResourceResponse返回资源内容
override fun shouldInterceptRequest(view: WebView,request: WebResourceRequest
): WebResourceResponse? {val url = request.url.toString()// 判断请求的URL是否为本地资源,如果是则加载本地资源if (isLocalResource(url)) {try {// 加载本地的HTML、JS、CSS资源val inputStream = getLocalResource(url)val mimeType = getMimeType(url)return WebResourceResponse(mimeType, "UTF-8", inputStream)} catch (e: IOException) {e.printStackTrace()}}return super.shouldInterceptRequest(view, request)
}private fun isLocalResource(url: String): Boolean {// 判断URL是否为本地资源的逻辑// 例如判断URL是否以特定的路径开头等//比如:val url = webRequest.url.toString()return url.startsWith("file:///android_asset/")
}private fun getLocalResource(url: String): InputStream {// 加载本地资源的逻辑// 根据URL读取本地的文件或输入流并返回
}private fun getMimeType(url: String): String {// 获取资源的MIME类型// 根据URL的后缀或其他信息判断MIME类型并返回
}
2、预获取数据、JS内容注入
假设你的HTML模版是:
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>title</title><link rel="stylesheet" type="text/css" href="xxx.css"><script>function changeContent(data){document.getElementById('content').innerHTML=data;}</script>
</head>
<body><div id="content"></div>
</body>
</html>
假设你提前在获取列表的时候,下发的JSON数据。
json data =
{"id" : 1,"webview_data" : "哈哈哈"
}
之后你就可以去把“哈哈哈”通过JS进行注入
webView.evaluateJavascript("javascript:changeContent('${data.webview_data}')") {}
3、内联离线资源文件
在 Android 端内联 HTML、JS 和 CSS 文件的一种常见方法是使用 WebView 的 loadDataWithBaseURL 方法。这个方法允许你加载一个包含 HTML 内容的字符串,并指定一个基本的 URL,以便 WebView 使用该 URL 加载相关的资源。
WebView webView = findViewById(R.id.webView);// 读取 HTML 文件内容
String htmlContent = readFileAsString("main.html");// 读取 JS 文件内容
String jsContent = readFileAsString("main.js");// 读取 CSS 文件内容
String cssContent = readFileAsString("main.css");// 构建完整的 HTML 内容
String fullHtmlContent = "<html><head><style>" + cssContent + "</style></head><body>" + htmlContent + "</body><script>" + jsContent + "</script></html>";// 设置基本的 URL,用于 WebView 加载相关资源
String baseUrl = "file:///android_asset/";// 加载内联的 HTML 内容
webView.loadDataWithBaseURL(baseUrl, fullHtmlContent, "text/html", "UTF-8", null);
这个示例假设 HTML、JS 和 CSS 文件在 assets 目录下,通过 readFileAsString 方法来读取文件内容。你需要自行实现这个方法。
请注意,这种内联方式适用于较小的文件,当文件内容较大时,可能会导致 WebView 初始化过程较慢。
三、拦截请求与共享缓存(请求、渲染优化)
旨在减少JS加载图片和其他可缓存数据的请求时间。
如今的 WebView 页面往往是图文混排的,图片是资讯类应用的重要表现形式。
优化的解析说明
目的:减少请求图片资源带来的时间延迟。
原理:空间换时间
三个需要注意的问题:
- 拦截请求:WebViewClient 提供了一个 shouldInterceptRequest 方法用于支持外部去拦截请求,WebView 每次在请求网络资源时都会回调该方法,方法入参就包含了 Url,Header 等请求参数。
- 缓存资源:我们可以通过该方法来主动拦截并完成图片的加载操作,这样我们既可以使得两端的资源文件得以共享,也避免了多次 JS 调用带来的效率问题,还将图片资源加入到了缓存当中。
- 缓存资源形式:
- 移动端已经预置了离线包(已经缓存了图片到本地)
- 通过 OkHttp 本身的 Cache 功能来实现资源缓存 (之后通过拦截器去自定义缓存,达到两端统一缓存)
- 通过Glide统一去加载图片。实现资源缓存共享
总结来说就是:拒绝JS的复杂图片请求,想象从本地加载、或者利用Android原生加载。享受缓存并共享缓存。
最后包装WebResourceResponse返回资源。
具体代码的实现
- shouldInterceptRequest拦截
- 本地缓存的内容去assets找
- 可缓存的内容,主要就是图片资源。通过Glide去加载,顺便用上Glide的缓存(岂不是比你自己实现强啊)
override fun shouldInterceptRequest(view: WebView,request: WebResourceRequest
): WebResourceResponse? {var webResourceResponse: WebResourceResponse? = null// 1、如果是 assets 目录下的文件if (isAssetsResource(request)) {webResourceResponse = assetsResourceRequest(view.context, request)}// 2、如果是可以缓存的文件if (isCacheResource(request)) {webResourceResponse = cacheResourceRequest(view.context, request)}if (webResourceResponse == null) {webResourceResponse = super.shouldInterceptRequest(view, request)}return webResourceResponse
}
- 解析URL
- 判断是否是本地资源
- 获取本地资源包装webResourceResponse
/*** 判断是否是本地资源*/
private fun isAssetsResource(webRequest: WebResourceRequest): Boolean {val url = webRequest.url.toString()return url.startsWith("file:///android_asset/")
}/*** assets 文件请求*/
private fun assetsResourceRequest(context: Context,webRequest: WebResourceRequest
): WebResourceResponse? {val url = webRequest.url.toString()try {val filenameIndex = url.lastIndexOf("/") + 1val filename = url.substring(filenameIndex)val suffixIndex = url.lastIndexOf(".")val suffix = url.substring(suffixIndex + 1)val webResourceResponse = WebResourceResponse(getMimeTypeFromUrl(url),"UTF-8",context.assets.open("$suffix/$filename"))webResourceResponse.responseHeaders = mapOf("access-control-allow-origin" to "*")return webResourceResponse} catch (e: Exception) {e.printStackTrace()}return null
}
- 是可以缓存的内容
- 判断是否是缓存内容
- 利用Glide进行缓存
/*** 判断是否是可以被缓存等资源*/
private fun isCacheResource(webRequest: WebResourceRequest): Boolean {val url = webRequest.url.toString()val extension = MimeTypeMap.getFileExtensionFromUrl(url)return extension == "ico" || extension == "bmp" || extension == "gif"|| extension == "jpeg" || extension == "jpg" || extension == "png"|| extension == "svg" || extension == "webp" || extension == "css"|| extension == "js" || extension == "json" || extension == "eot"|| extension == "otf" || extension == "ttf" || extension == "woff"
}private fun cacheResourceRequest(context: Context,webRequest: WebResourceRequest
): WebResourceResponse? {var url = webRequest.url.toString()var mimeType = getMimeTypeFromUrl(url)// WebView 中的图片利用 Glide 加载(能够和App其他页面共用缓存)if (isImageResource(webRequest)) {return try {val file = Glide.with(context).download(url).submit().get()val webResourceResponse = WebResourceResponse(mimeType, "UTF-8", file.inputStream())webResourceResponse.responseHeaders = mapOf("access-control-allow-origin" to "*")webResourceResponse} catch (e: Exception) {e.printStackTrace()null}}// 其他文件的缓存,根据需求去看吧,可以利用文件IO去存其他的资源。return null
}/*** 判断是否是图片* 有些文件存储没有后缀,也可以根据自家服务器域名等等*/
private fun isImageResource(webRequest: WebResourceRequest): Boolean {val url = webRequest.url.toString()val extension = MimeTypeMap.getFileExtensionFromUrl(url)return extension == "ico" || extension == "bmp" || extension == "gif"|| extension == "jpeg" || extension == "jpg" || extension == "png"|| extension == "svg" || extension == "webp"
}/*** 根据 url 获取文件类型*/
private fun getMimeTypeFromUrl(url: String): String {try {val extension = MimeTypeMap.getFileExtensionFromUrl(url)if (extension.isNotBlank() && extension != "null") {if (extension == "json") {return "application/json"}return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) ?: "*/*"}} catch (e: Exception) {e.printStackTrace()}return "*/*"
}
其他问题的处理
DNS、CDN优化(请求优化)
- DNS 优化
- DNS 也即域名解析,指代的是将域名转换为具体的 IP 地址的过程。
- 如果 WebView 访问的主域名和客户端的不一致,那么 WebView 在首次访问线上资源时,就需要先完成域名解析才能开始资源请求,这个过程就需要多耗费几十毫秒的时间。因此最好就是保持客户端整体 API 地址、资源文件地址、WebView 线上地址的主域名都是一致的。
- CDN 加速
- 通过将 JS、CSS、图片、视频等静态类型文件托管到 CDN,当用户加载网页时,就可以从地理位置上最接近它们的服务器接收这些文件,解决了远距离访问和不同网络带宽线路访问造成的网络延迟情况
白屏检测(异常处理)
问题的解析说明
用户的网络环境和系统环境千差万别,甚至 WebView 也可能发生内部崩溃。当发生问题时,用户看到的可能就直接只是一个白屏页面了,所以进一步的优化手段就是需要去检测是否发生白屏以及相应的应对措施。
问题的解析思路
- 对 WebView 进行截图,遍历截图的像素点的颜色值,如果非白屏颜色的颜色点超过一定的阈值,就可以认为不是白屏。
- 字节跳动技术团队的做法是:通过 View.getDrawingCache()方法去获取包含 WebView 视图的 Bitmap 对象,然后把截图缩小到原图的 1/6,遍历检测图片的像素点,当非白色的像素点大于 5% 的时候就可以认为是非白屏的情况,可以相对高效且准确地判断出是否发生了白屏。
发现问题后的对策
- 重试
- 降级、不走缓存、预知、直接请求线上的内容页
- 给出相应的提示、放弃展示
检测到之后,就可以进行重试、放弃优化重试、给出相应提示。具体的策略的话,看业务来定吧。
代码实现
class BaseWebView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null
) : WebView(context, attrs), LifecycleEventObserver {// 省略其他代码... inner class BlankMonitorRunnable : Runnable {override fun run() {val task = Thread {// 根据宽高的 1/6 创建 bitmapval dstWidth = measuredWidth / 6val dstHeight = measuredHeight / 6val snapshot = Bitmap.createBitmap(dstWidth, dstHeight, Bitmap.Config.ARGB_8888)// 绘制 view 到 bitmapval canvas = Canvas(snapshot)draw(canvas)// 像素点总数val pixelCount = (snapshot.width * snapshot.height).toFloat()var whitePixelCount = 0 // 白色像素点计数var otherPixelCount = 0 // 其他颜色像素点计数// 遍历 bitmap 像素点for (x in 0 until snapshot.width) {for (y in 0 until snapshot.height) {// 计数 其实记录一种就可以if (snapshot.getPixel(x, y) == -1) {whitePixelCount++}else{otherPixelCount++}}}// 回收 bitmapsnapshot.recycle()if (whitePixelCount == 0) {return@Thread}// 计算白色像素点占比 (计算其他颜色也一样)val percentage: Float = whitePixelCount / pixelCount * 100// 如果超过阈值 触发白屏提醒if (percentage > 95) {post {mBlankMonitorCallback?.onBlank()}}}task.start()}}
}
版本问题带来的白屏(异常处理)
系统版本大于等于 4.3,小于等于 6.0 之间,ViewRootImpl 在处理 View 绘制的时候,会通过一个布尔变量 mDrawDuringWindowsAnimating 来控制 Window 在执行动画的过程中是否允许进行绘制,该字段默认为 false,我们可以利用反射的方式去手动修改这个属性,避免这个白屏效果。
/*** 让 activity transition 动画过程中可以正常渲染页面*/
fun setDrawDuringWindowsAnimating(view: View) {if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M|| Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {//小于 4.3 和大于 6.0 时不存在此问题,无须处理return}try {val rootParent: ViewParent = view.rootView.parentval method: Method = rootParent.javaClass.getDeclaredMethod("setDrawDuringWindowsAnimating", Boolean::class.javaPrimitiveType)method.isAccessible = truemethod.invoke(rootParent, true)} catch (e: Throwable) {e.printStackTrace()}
}
总结
上面说的都是每一步的优化,这些优化是可以进行结合的。
比如:
- 提前预加载一个WebView模版,利用本地的H5资源。
- 之后加入到WebView的缓冲池中。比如去定义一个TemplateWebView去专门处理一些常用的比较固定的WebView页面。
- 之后在WebViewPool中去专门添加这种比较固定的WebView页面去缓存。
- 在每次只需要去注入新的正文数据进行复用就OK了。
多的不BB!!!
加油!!!!奥利给。
相关文章:

一篇文章搞定《WebView的优化及封装》
一篇文章搞定《WebView的优化及封装》 前言WebView的过程分析确定优化方案一、预加载,复用缓冲池(初始化优化)优化的解析说明具体的实现 二、预置模版(请求、渲染优化)优化的解析说明具体的实现1、离线包2、预获取数据…...

FreeSWITCH 1.10.10 简单图形化界面5 - 使用百度TTS
FreeSWITCH 1.10.10 简单图形化界面5 - 使用百度TTS 0、 界面预览1、注册百度AI开放平台,开通语音识别服务2、获取AppID/API Key/Secret Key3、 安装百度语音合成sdk4、合成代码5、在PBX中使用百度TTS6、音乐文件-TTS7、拨号规则-tts_command 0、 界面预览 http://…...

DP读书:不知道干什么就和我一起读书吧
DP读书:不知道干什么就和我一起读书吧 为啥写博客:好处一:记录自己的学习过程优点二:让自己在各大社群里不那么尴尬推荐三:坚持下去,找到一个能支持自己的伙伴 虽然清楚知识需要靠时间沉淀,但在…...

【Linux】进程通信 — 信号(上篇)
文章目录 📖 前言1. 什么是信号1.1 认识信号:1.2 信号的产生:1.3 信号的异步:1.4 信号的处理: 2. 前后台进程3. 系统接口3.1 signal:3.1 - 1 不能被捕捉的信号 3.2 kill:3.2 - 1 killall 3.3 ra…...

JS弃之可惜食之无味的代码冷知识
JS代码冷知识大全 1. 变量提升与暂死 在JavaScript中,变量提升是一个有趣且容易让人误解的概念。在代码中,变量和函数声明会在其所在作用域的顶部被提升,但是初始化并不会被提升。这可能导致在声明之前就使用变量,结果为undefin…...

数据结构初阶--排序
目录 一.排序的基本概念 1.1.什么是排序 1.2.排序算法的评价指标 1.3.排序的分类 二.插入排序 2.1.直接插入排序 2.2.希尔排序 三.选择排序 3.1.直接选择排序 3.2.堆排序 重建堆 建堆 排序 四.交换排序 4.1.冒泡排序 4.2.快速排序 快速排序的递归实现 法一&a…...

赴日IT 如何提高去日本做程序员的几率?
其实想去日本做IT工作只要满足学历、日语、技术三个必要条件,具备这些条件应聘就好,不具备条件你就想办法具备这些条件,在不具备条件之前不要轻易到日本去,日本IT行业虽然要求技术没有国内那么高,但也不是随便好进入的…...

c# 使用了 await、asnync task.run 三者结合使用
在 C# 异步编程中,await 和 async 关键字结合使用可以让你更方便地编写异步代码,而无需直接使用 Task.Run。然而,有时候你可能仍然需要使用 Task.Run 来在后台线程上执行某些工作,这取决于你的代码逻辑和需求。 await 和 async 关…...

C#获取屏幕缩放比例
现在1920x1080以上分辨率的高分屏电脑渐渐普及了。我们会在Windows的显示设置里看到缩放比例的设置。在Windows桌面客户端的开发中,有时会想要精确计算窗口的面积或位置。然而在默认情况下,无论WinForms的Screen.Bounds.Width属性还是WPF中SystemParamet…...

Rn实现省市区三级联动
省市区三级联动选择是个很频繁的需求,但是查看了市面上很多插件不是太老不维护就是不满足需求,就试着实现一个 这个功能无任何依赖插件 功能略简单,但能实现需求 核心代码也尽力控制在了60行左右 pca-code.json树型数据来源 Administrative-d…...

SpringCloud学习笔记(十)_SpringCloud监控
今天我们来学习一下actuator这个组件,它不是SpringCloud之后才有的,而是SpringBoot的一个starter,Spring Boot Actuator。我们使用SpringCloud的时候需要使用这个组件对应用程序进行监控与管理 在SpringBoot2.0版本中,actuator可以…...

测试理论与方法----测试流程的第二个环节:测试计划
二、软件测试分类与测试计划 1、软件测试的分类(理解掌握) 根绝需求规格说明书,在设计阶段会产出的两个文档: 概要设计(HLD):设计软件的结构,包含软件的组成,模块之间的层次关系,模块与模块之间的调用关系…...

postgresql-子查询
postgresql-子查询 简介派生表IN 操作符ALL 操作符ANY 操作符关联子查询横向子查询EXISTS 操作符 简介 子查询(Subquery)是指嵌套在其他 SELECT、INSERT、UPDATE 以及 DELETE 语句中的 查询语句。 子查询的作用与多表连接查询有点类似,也是为…...

Linux 系统运维工具之 OpenLMI
一、前要 OpenLMI(全称 Open Linux Management Infrastructure)即开放式的 Linux 管理基础架构。OpenLMI 是一个开源项目,用于管理 Linux 系统管理的通用基础架构。它建立在现有工具基础上,充当抽象层,以便向系统管理…...

8天长假快来了,Python分析【去哪儿旅游攻略】数据,制作可视化图表
目录 前言环境使用模块使用数据来源分析 代码实现导入模块请求数据解析保存 数据可视化导入模块、数据年份分布情况月份分布情况出行时间情况费用分布情况人员分布情况 前言 2023年的中秋节和国庆节即将来临,好消息是,它们将连休8天!这个长假…...

【HSPCIE仿真】输入网表文件(5)基本仿真输出
仿真输出 1. 概述1.1 输出变量1.2 输出分析类型 2. 显示仿真结果2.1 .print语句基本语法示例 2.2 .probe 语句基本语法示例 2.3 子电路的输出2.4 打印控制选项.option probe.option post.option list.option ingold 2.5 .model_info打印模型参数 3. 仿真输出参数的选择3.1 直流…...

uni-app中使用iconfont彩色图标
uni-app中使用iconfont彩色图标 大家好,今天我们来学习一下uni-app中使用iconfont彩色图标,好好看,好好学,超详细的 第一步 首先,从iconfont官网(iconfont-阿里巴巴矢量图标库)选择自己需要的图…...

Hystrix: Dashboard流监控
接上两张服务熔断 开始搭建Dashboard流监控 pom依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocat…...

iconfont 图标在vue里的使用
刚好项目需要使用一个iconfont的图标,所以记录一下这个过程 1、iconfont-阿里巴巴矢量图标库 这个注册一个账号,以便后续使用下载代码时需要 2、寻找自己需要的图标 我主要是找两个图标 ,一个加号,一个减号,分别加入到…...

QT登陆注册界面练习
一、界面展示 二、主要功能界面代码 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QMainWindow(parent), ui(new Ui::Widget) {ui->setupUi(this);this->setFixedSize(540,410); //设置固定尺寸th…...

MySQL DATE_SUB的实践
函数简介DATE_SUB()函数从DATE或DATETIME值中减去时间值(或间隔)。 下面说明了DATE_SUB()函数的语法: DATE_SUB(start_date,INTERVAL expr unit); DATE_SUB()函数接受两个参数: start_date是DATE或DATETIME的起始值。 expr是一个字符串,用于确…...

OpenCV最常用的50个函数
Python版:OpenCV提供了众多图像处理算子和函数,涵盖了各种任务和技术。以下是OpenCV中一些常用的50个算子和函数: cv2.imread:用于读取图像文件。cv2.imshow:用于显示图像。cv2.imwrite:用于保存图像。cv2…...

Android AGP8.1.0组件化初探
Android AGP8.1.0组件化初探 前言: 前面两篇完成了从AGP4.2到 AGP8.1.0的升级,本文是由于有哥们留言说在AGP8.0中使用ARouter组件化有问题,于是趁休息时间尝试了一下,写了几个demo,发现都没有问题,跳转和传…...

文件修改时间能改吗?怎么改?
文件修改时间能改吗?怎么改?修改时间是每个电脑文件具备的一个属性,它代表了这个电脑文件最后一次的修改时间,是电脑系统自动赋予文件的,相信大家都应该知道。我们右击鼠标某个文件,然后点击弹出菜单里面的…...

2023年下半年软考报名注意事项!
考试注意事项: 分数线:所有科目成绩全部在45分以上(含45分)通过考试;三科目的话,必须每科目都及格才算通过考试,只有一个不合格的,本次考试其他两个无效。 出成绩时间:预…...

【LeetCode每日一题】——274.H指数
文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时间频度】九【代码实现】十【提交结果】 一【题目类别】 排序 二【题目难度】 中等 三【题目编号】 274.H指数 四【题目描述】 给你一个整数数组 ci…...

网络编程 day 4
1、多进程并发服务器根据流程图重新编写 #include <myhead.h>#define ERR_MSG(msg) do{\fprintf(stderr, "__%d__:", __LINE__); \perror(msg);\ }while(0)#define PORT 8888 //端口号,范围1024~49151 #define IP "192.168.11…...

【Java架构-版本控制】-Git基础
本文摘要 Git作为版本控制工具,使用非常广泛,在此咱们由浅入深,分三篇文章(Git基础、Git进阶、Gitlab搭那家)来深入学习Git 文章目录 本文摘要1.Git仓库基本概念1.1 远程仓库(Remote)1.2 本地库(Repository) 2. Git仓库…...

ubuntu 挂载硬盘操作
1. 查看磁盘 sudo fdisk -l 2. 查看UUID sudo blkid记录下待挂载硬盘的UUID, 后面要使用 ps. 如果报错,检查是否已格式化硬盘 查看新硬盘的盘符,我的是/dev/sda,用下述命令格式化 sudo mkfs -t ext4 /dev/sda3. 创建挂载点 我的是在/mnt…...

关于商品活动的H5页面技术总结
背景 在单个html文件里面使用vue3、jquery等其他第三方js库,实现规定的页面效果,其中主要功能是从商品json数据中读取数据,然后可以通过搜索框、下拉框、左侧菜单来筛选商户信息。 页面布局 技术要点: 1、通过路由来进行页面布…...