一篇文章搞定《WebView的优化及封装》

news2024/12/23 17:47:36

一篇文章搞定《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"

        @Volatile
        private var instance: WebViewPool? = null

        fun 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() //自定义的webViewClient
                view.webChromeClient = BaseWebChromeClient() //自定义的webChromeClient
                webViewPool.push(view)
            }
            false
        }
    }

    //3、提供从pool中获取WebView
    fun getWebView(context: Context): BaseWebView {
        synchronized(lock) {
            val webView: BaseWebView
            if (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 MutableContextWrapper
            contextWrapper.baseContext = context

            // 默认设置
            webView.webChromeClient = BaseWebChromeClient()
            webView.webViewClient = BaseWebViewClient()
            return webView
        }
    }

    //4、使用结束的回收WebView
    fun recycle(webView: BaseWebView) {
        // 释放资源
        webView.release()

        // 根据池容量判断是否销毁
        val contextWrapper = webView.context as MutableContextWrapper
        contextWrapper.baseContext = webView.context.applicationContext
        synchronized(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("/") + 1
        val 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 创建 bitmap
                val dstWidth = measuredWidth / 6
                val dstHeight = measuredHeight / 6
                val snapshot = Bitmap.createBitmap(dstWidth, dstHeight, Bitmap.Config.ARGB_8888)
                // 绘制 view 到 bitmap
                val 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++
                        }
                    }
                }
                // 回收 bitmap
                snapshot.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.parent
        val method: Method = rootParent.javaClass
            .getDeclaredMethod("setDrawDuringWindowsAnimating", Boolean::class.javaPrimitiveType)
        method.isAccessible = true
        method.invoke(rootParent, true)
    } catch (e: Throwable) {
        e.printStackTrace()
    }
}

总结

上面说的都是每一步的优化,这些优化是可以进行结合的。
比如:

  • 提前预加载一个WebView模版,利用本地的H5资源。
  • 之后加入到WebView的缓冲池中。比如去定义一个TemplateWebView去专门处理一些常用的比较固定的WebView页面。
  • 之后在WebViewPool中去专门添加这种比较固定的WebView页面去缓存。
  • 在每次只需要去注入新的正文数据进行复用就OK了。

多的不BB!!!
加油!!!!奥利给。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/950546.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【云原生进阶之PaaS中间件】第一章Redis-2.3.3集群模式

1 集群模式 Redis集群是一个提供在多个Redis节点之间共享数据的程序集。它并不像Redis主从复制模式那样只提供一个master节点提供写服务,而是会提供多个master节点提供写服务,每个master节点中存储的数据都不一样,这些数据通过数据分片的方式被自动分割到不同的master节点上…

手写实现call() apply() bind()函数,附有详细注释,包含this指向、arguments讲解

手写实现call() apply() bind()函数是很经典的问题&#xff0c;但是能掰扯清楚的文章确实不算多&#xff0c;于是笔者才决定写下本文&#xff0c;希望能给读者带来一些启发&#xff0c;如有错误欢迎指正。 目录 补充知识 函数中的this指向 类数组对象arguments call() 原理…

Leedcode19. 删除链表的倒数第 N 个结点

给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5] 示例 2&#xff1a; 输入&#xff1a;head [1], n 1 输出&#xff1a;[] 示例 3&#xff1a; 输入&#xff1…

MPI之持久化通信句柄与非持久化通信句柄

MPI_Isend & MPI_Send 创建临时通信句柄 在前面的文章中举了例子&#xff0c;我们使用MPI_Isend接口发送数据时&#xff0c;有个传出参数request&#xff0c;该参数是创建的通信句柄&#xff0c; 实际上该句柄是一个临时句柄&#xff0c;即只用于一次性发送数据的场景&…

uniapp iOS打包证书申请流程——mac

如何在 Mac 创建 iOS 打包证书&#xff1f; 文章目录 如何在 Mac 创建 iOS 打包证书&#xff1f;会员 VS 非会员权限步骤添加设备创建标识符生成证书生成描述文件 前提&#xff1a; Mac 电脑Apple ID 申请Apple ID成为开发者 developer 注意&#xff1a; 登录 Apple ID 成为开…

2023年数字孪生行业研究报告

第一章 行业概况 1.1 定义 数字孪生&#xff08;Digital Twin&#xff09;是一种先进的建模技术&#xff0c;它通过创建一个物理实体的虚拟复制品&#xff0c;以实时模拟、预测和优化实体的行为和性能。这个虚拟模型会同步收集和分析来自其物理对应物的数据&#xff0c;从而提…

【DRONECAN】(三)WSL2 及 ubuntu20.04 CAN 驱动安装

【DRONECAN】&#xff08;三&#xff09;WSL2 及 ubuntu20.04 CAN 驱动安装 前言 这一篇文章主要介绍一下 WSL2 及 ubuntu20.04 CAN 驱动的安装&#xff0c;首先说一下介绍本文的目的。 大家肯定都接触过 ubuntu 系统&#xff0c;但是我们常用的操作系统都是 Windows&#x…

JavaSE基础(2)

1 方法的使用 思维导图 1.1 什么是方法 方法就是一个代码片段. 类似于 C 语言中的 “函数”。方法存在的意义(不要背, 重在体会): 是能够模块化的组织代码(当代码规模比较复杂的时候).做到代码被重复使用, 一份代码可以在多个位置使用.让代码更好理解更简单.直接调用现有方法…

基于金枪鱼群算法优化的BP神经网络(预测应用) - 附代码

基于金枪鱼群算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码 文章目录 基于金枪鱼群算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码1.数据介绍2.金枪鱼群优化BP神经网络2.1 BP神经网络参数设置2.2 金枪鱼群算法应用 4.测试结果&#xff1a;5…

肠道微生物群肾衰竭

编者推荐 该研究应用多组学分析&#xff08;代谢组学分析微生物组学分析&#xff09;分析了人类ESRD肠道微生物组组成、尿毒症毒素和肾衰竭之间的关系&#xff0c;使用独立队列和无菌动物模型对多组学结果及研究提出的ESRD机制假设进行验证&#xff0c;首次从肠道微生物的角度…

qt第一天

#include "widget.h" #include "ui_widget.h" #include "QDebug" Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);this->resize(QSize(800,600)); //使用匿名对象&#xff0c;调用重…

嵌入式Linux开发实操(十五):nand flash接口开发

# 前言 flash memory,分NAND和NOR: 如果说nor flash有个特点就是能执行代码,NOR并行接口具有地址和数据总线,spi flash更是主要用于存储代码,SPI(或QSPI)NOR代码可就地执行(XiP),一般系统要求flash闪存提供相对较高的频率和数据缓存的clocking。而nand flash主要用于…

QT Creator工具介绍及使用

一、QT的基本概念 QT主要用于图形化界面的开发&#xff0c; QT是基于C编写的一套界面相关的类库&#xff0c;如进程线程库&#xff0c;网络编程的库&#xff0c;数据库操作的库&#xff0c;文件操作的库等。 如何使用这个类库&#xff1a;类库实例化对象(构造函数) --> 学习…

Django静态文件媒体文件文件上传

文章目录 一、静态文件和媒体文件1.在django中使用静态文件实践2.在django中使用媒体文件 二、文件上传单文件上传实践多文件上传 一、静态文件和媒体文件 媒体文件: 用户上传的文件&#xff0c;叫做media 静态文件:存放在服务器的css,js,image,font等 叫做static1.在django中…

【Flutter】使用Android Studio 创建第一个flutter应用。

前言 首先下载好 flutter sdk和 Android Studio。 FlutterSDK下载 Android Studio官网 配置 我的是 windows。 where.exe flutter dart查看flutter安装环境。 如果没有&#xff0c;自己在环境变量的path添加下flutter安装路径。 在将 Path 变量更新后&#xff0c;打开一个…

QTday1(第一个QT界面、常用类与组件)

一、Xmind整理&#xff1a; Assistant帮助文档的使用&#xff1a; 设计师界面的介绍&#xff1a; 各文件之间调用方式&#xff1a; 二、上课笔记整理&#xff1a; 1.第一个QT界面 ①创建自定义类时需要指定父类 ②第一个界面的相关操作 #include "mainwindow.h"…

比Python快3.5万倍的Mojo融资7亿,LLVM之父:不会威胁到Python,该恐惧的应该是C++

近日&#xff0c;Modular AI 公司宣布成功融资 1 亿美元&#xff08;约 7.29 亿人民币&#xff09;&#xff0c;据称这是继去年 3000 万美元融资之后的第二轮融资。 Modular AI 称他们未来的愿景是通过 AI 引擎和 Mojo 为全球开发者提供 AI 基础设施。 Modular AI 是 Chris La…

maven本地安装jar包install-file,解决没有pom的问题

背景&#xff1a; 公司因为权限问题&#xff0c;没有所有的代码&#xff0c;内部maven还在搭建&#xff0c;所以需要拿到同事的jar包&#xff0c;本地install&#xff1a; mvn install:install-file -DgroupIdcom..framework -DartifactIdcloud-api -Dversion1.0.0-SNAPSHOT …

Blender 3D建模要点

3d模型可以为场景的仿真模拟带来真实感,它还有助于更轻松地识别场景中的所有内容。 例如,如果场景中的所有对象都是简单的形状,如立方体和圆形,则很难在仿真中区分对象。 1,碰撞形状与视觉形状 像立方体和球体这样的简单形状,通常被称为“基本体”,通常用作碰撞块。 与…