在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

介紹得物App在資源優(yōu)化上做的一些實(shí)踐

OSC開(kāi)源社區(qū) ? 來(lái)源:得物技術(shù) ? 2023-07-24 09:00 ? 次閱讀

包體積優(yōu)化中,資源優(yōu)化一般都是首要且容易有成效的優(yōu)化方向。資源優(yōu)化是通過(guò)優(yōu)化APK中的資源項(xiàng)來(lái)優(yōu)化包體積,本文我們會(huì)介紹得物App在資源優(yōu)化上做的一些實(shí)踐。

1

插件優(yōu)化

插件優(yōu)化資源在得物App最新版本上收益12MB。插件優(yōu)化的日志在包體積平臺(tái)有具體的展示,也是為了提供一個(gè)資源問(wèn)題追溯的能力。

270254d0-27b3-11ee-962d-dac502259ad0.png

1.1 插件環(huán)境配置

插件首先會(huì)初始化環(huán)境配置,如果機(jī)器上未安裝運(yùn)行環(huán)境則會(huì)去oss下載對(duì)應(yīng)的可執(zhí)行文件。

273cbcce-27b3-11ee-962d-dac502259ad0.png

1.2 圖片壓縮

在開(kāi)發(fā)階段,開(kāi)發(fā)同學(xué)首先會(huì)通過(guò)TinyPNG等工具主動(dòng)對(duì)圖片進(jìn)行壓縮,而對(duì)于三方庫(kù)和一些業(yè)務(wù)遺漏處理的圖片則會(huì)在打包的時(shí)候通過(guò)gradle插件進(jìn)行壓縮。

圖片壓縮插件使用 cwebp 對(duì)圖片進(jìn)行webp轉(zhuǎn)換,使用 guetzli 對(duì)JPEG進(jìn)行壓縮,使用pngquant對(duì)PNG 進(jìn)行壓縮,使用 gifsicle 對(duì)gif進(jìn)行壓縮。在實(shí)施對(duì)過(guò)程中,對(duì)于 res 目錄下的文件優(yōu)先使用 webp 處理,對(duì)assets 目錄下的文件則進(jìn)行同格式壓縮。下面先介紹下資源壓縮插件的工作模式和原理。

1.2.1 Res圖片壓縮

第一步,找到并遍歷 ap_ 文件

278e517e-27b3-11ee-962d-dac502259ad0.png

這里對(duì) ap_ 文件進(jìn)行一下簡(jiǎn)單介紹,ap_ 文件是由 AAPT2 生成的,AAPT2(Android 資源打包工具)是一種構(gòu)建工具,Android Studio 和 Android Gradle 插件使用它來(lái)編譯和打包應(yīng)用的資源。AAPT2 會(huì)解析資源、為資源編制索引,并將資源編譯為針對(duì) Android 平臺(tái)進(jìn)行過(guò)優(yōu)化的二進(jìn)制格式。

AAPT2這個(gè)工具在打包過(guò)程中主要做了下列工作: 把"assets"和"res/raw"目錄下的所有資源進(jìn)行打包(會(huì)根據(jù)不同的文件后綴選擇壓縮或不壓縮),而"res/"目錄下的其他資源進(jìn)行編譯或者其他處理(具體處理方式視文件后綴不同而不同,例如:".xml"會(huì)編譯成二進(jìn)制文件,".png"文件會(huì)進(jìn)行優(yōu)化等等)后才進(jìn)行打包; 會(huì)對(duì)除了assets資源之外所有的資源賦予一個(gè)資源ID常量,并且會(huì)生成一個(gè)資源索引表resources.arsc; 編譯AndroidManifest.xml成二進(jìn)制的XML文件; 把上面3個(gè)步驟中生成結(jié)果保存在一個(gè)*.ap_文件,并把各個(gè)資源ID常量定義在一個(gè) R.java R.txt中;

第二步,解壓 ap_ 文件,找到 res/drawable 、res/mipmap 、res/raw 目錄下的圖片進(jìn)行壓縮

fun compressImg(imgFile: File): Long {
    if (ImageUtil.isJPG(imgFile) || ImageUtil.isGIF(imgFile) || ImageUtil.isPNG(imgFile)) {
        val lastIndexOf = imgFile.path.lastIndexOf(".")
        if (lastIndexOf < 0) {
            println("compressImg ignore ${imgFile.path}")
            return 0
        }
        val tempFilePath =
                "${imgFile.path.substring(0, lastIndexOf)}_temp${imgFile.path.substring(lastIndexOf)}"


        if (ImageUtil.isJPG(imgFile)) {
            Tools.cmd("guetzli", "--quality 85 ${imgFile.path} $tempFilePath")
        } else if (ImageUtil.isGIF(imgFile)) {
            Tools.cmd("gifsicle", "-O3 --lossy=25 ${imgFile.path} -o $tempFilePath")
        } else if (ImageUtil.isPNG(imgFile)) {
            Tools.cmd(
                    "pngquant",
                    "--skip-if-larger --speed 1 --nofs --strip --force  --quality=75  ${imgFile.path} --output $tempFilePath"
            )
        }
        val oldSize = imgFile.length()
        val tempFile = File(tempFilePath)
        val newSize = tempFile.length()
        return if (newSize in 1 until oldSize) {
            val imgFileName: String = imgFile.path
            if (imgFile.exists()) {
                imgFile.delete()
            }
            tempFile.renameTo(File(imgFileName))
            oldSize - newSize
        } else {
            if (tempFile.exists()) {
                tempFile.delete()
            }
            0L
        }
    }
    return 0
}
圖片的壓縮收益最大,且實(shí)施簡(jiǎn)單,風(fēng)險(xiǎn)最低,是資源優(yōu)化的首選。

1.2.2Assets圖片壓縮

Assets 圖片壓縮的處理方式與 res 下差不多,區(qū)別僅僅在于掛載的 task 與 壓縮模式不同,Assets 下單資源由于是通過(guò) AssetsManager 按照名稱獲取的,且使用場(chǎng)景不可控,無(wú)法明確感知業(yè)務(wù)使用對(duì)格式是否有要求的前提下,同格式壓縮是相對(duì)穩(wěn)妥的方案。

val mergeAssets = project.tasks.getByName("merge${variantName}Assets")
mergeAssets.doLast { task ->
    (task as MergeSourceSetFolders).outputDir.asFileTree.files.filter {
        val originalPath = it.absolutePath.replace(task.outputDir.get().toString() + "/", "")
        val filter = context.compressAssetsExtension.whiteList.contains(originalPath)
        if (filter) {
            println("Assets compress ignore:$originalPath")
        }
        !filter
    }.forEach { file ->
        val originalPath = file.absolutePath.replace(task.outputDir.get().toString() + "/", "")
        val reduceSize = CompressUtil.compressImg(file)
        if (reduceSize > 0) {
            assetsShrinkLength += reduceSize
            assetsList.add("$originalPath => reduce[${byteToSize(reduceSize)}]")
        }
    }
    println("assets optimized:${byteToSize(assetsShrinkLength)}")
}

1.3 資源去重

相較于壓縮,資源的去重需要對(duì)arsc文件格式有一點(diǎn)了解。為了便于理解,這里先對(duì)arsc二進(jìn)制文件進(jìn)行一點(diǎn)簡(jiǎn)單的介紹。 resource.arsc文件是Apk打包過(guò)程中的產(chǎn)生的一個(gè)資源索引文件,它是一個(gè)二進(jìn)制文件,源碼ResourceTypes.h 定義了其數(shù)據(jù)結(jié)構(gòu)。通過(guò)學(xué)習(xí)resource.arsc文件結(jié)構(gòu),可以幫助我們深入了解apk包體積優(yōu)化中使用到的 重復(fù)資源刪除、資源文件名混淆 技術(shù)。

27d385aa-27b3-11ee-962d-dac502259ad0.png

將apk使用AS 打開(kāi)也能看到resource.arsc中存儲(chǔ)的信息

2800053a-27b3-11ee-962d-dac502259ad0.png

說(shuō)回到資源去重,去重打原理很簡(jiǎn)單,找到資源文件目錄下相同的文件,然后刪除掉重復(fù)的文件,最后到 arsc 中修改記錄,將刪除的文件索引名稱進(jìn)行替換。

由于刪除重復(fù)資源在 arsc 中只是對(duì)常量池中路徑替換,并沒(méi)有刪除 arsc 中的記錄,也沒(méi)有修改PackageChunk 中的常量池內(nèi)容,也就是對(duì)應(yīng)上圖中的 Name 字段,故而重復(fù)資源的刪除安全性比較高。

下面介紹下具體實(shí)施方案:

第一步遍歷ap文件,通過(guò) crc32 算法找出相同文件。之所以選擇 crc32 是因?yàn)?gralde 的 entry file 自帶 crc32 值,不需要進(jìn)行額外計(jì)算,但是 crc32 是有沖突風(fēng)險(xiǎn)的,故而又對(duì) crc32 的重復(fù)結(jié)果進(jìn)行 md5 二次校驗(yàn)。

第二步則是對(duì)原始重復(fù)文件的刪除

第三步修改 ResourceTableChunk 常量池內(nèi)容,進(jìn)行資源重定向

// 查詢重復(fù)資源
val groupResources = ZipFile(apFile).groupsResources()
// 獲取
val resourcesFile = File(unZipDir, "resources.arsc")
val md5Map = HashMap>()
val newResouce = FileInputStream(resourcesFile).use { stream ->
    val resouce = ResourceFile.fromInputStream(stream)
    groupResources.asSequence()
        .filter { it.value.size > 1 }
        .map { entry ->
            entry.value.forEach { zipEntry ->
                if (whiteList.isEmpty() || !whiteList.contains(zipEntry.name)) {
                    val file = File(unZipDir, zipEntry.name)
                    MD5Util.computeMD5(file).takeIf { it.isNotEmpty() }?.let {
                        val set = md5Map.getOrDefault(it, HashSet())
                        set.add(zipEntry)
                        md5Map[it] = set
                    }
                }
            }
            md5Map.values
        }
        .filter { it.size > 1 }
        .forEach { collection ->
            // 刪除多余資源
            collection.forEach { it ->
                val zips = it.toTypedArray()
                // 所有的重復(fù)資源都指定到這個(gè)第一個(gè)文件上
                val coreResources = zips[0]
                for (index in 1 until zips.size) {
                    // 重復(fù)的資源
                    val repeatZipFile = zips[index]
                    result?.add("${repeatZipFile.name} => ${coreResources.name}    reduce[${byteToSize(repeatZipFile.size)}]")
                    // 刪除解壓的路徑的重復(fù)文件
                    File(unZipDir, repeatZipFile.name).delete()
                    // 將這些重復(fù)的資源都重定向到同一個(gè)文件上
                    resouce
                        .chunks
                        .filterIsInstance()
                        .forEach { chunk ->
                            val stringPoolChunk = chunk.stringPool
                            val index = stringPoolChunk.indexOf(repeatZipFile.name)
                            if (index != -1) {
                                // 進(jìn)行剔除重復(fù)資源
                                stringPoolChunk.setString(index, coreResources.name)
                            }
                        }
                }
            }
        }


    resouce
}

1.4資源混淆

資源混淆則是在資源去重打基礎(chǔ)上更進(jìn)一步,與代碼混淆的思路一致,用長(zhǎng)路徑替換短路徑,一來(lái)減小文件名大小,二來(lái)降低arsc中常量池中二進(jìn)制文件大小。

長(zhǎng)路徑替換短路徑修改 ResourceTableChunk 即可,與重復(fù)資源處理如出一轍。

同時(shí)我們發(fā)現(xiàn) PackageChunk 中常量池中字段還是原來(lái)的內(nèi)容,但是并不影響apk的運(yùn)行。因?yàn)橥ㄟ^(guò)getDrawable(R.drawable.xxx)方式加載的資源在編譯后對(duì)應(yīng)的是getDrawable(0x7f08xxxx)這種16進(jìn)制的內(nèi)容,其實(shí)就是與 arsc 中的 ID 對(duì)應(yīng),用不上 Name 字段。而通過(guò)getResources().getIdentifier()方式調(diào)用的我們通過(guò)白名單keep住了,Name 字段在這里也是可以移除的。

        val resourcesFile = File(unZipDir, "resources.arsc")
        val newResouce = FileInputStream(resourcesFile).use { inputStream ->
            val resouce = ResourceFile.fromInputStream(inputStream)
            resouce
                .chunks
                .filterIsInstance()
                .forEach { chunk ->
                    val stringPoolChunk = chunk.stringPool
                    // 獲取所有的路徑
                    val strings = stringPoolChunk.getStrings() ?: return@forEach


                    for (index in 0 until stringPoolChunk.stringCount) {
                        val v = strings[index]


                        if (v.startsWith("res")) {
                            if (ignore(v, context.proguardResourcesExtension.whiteList)) {
                                println("resProguard  ignore  $v ")
                                // 把文件移到新的目錄
                                val newPath = v.replaceFirst("res", whiteTempRes)
                                val parent = File("$unZipDir${File.separator}$newPath").parentFile
                                if (!parent.exists()) {
                                    parent.mkdirs()
                                }
                                keeps.add(newPath)
                                // 移動(dòng)文件
                                File("$unZipDir${File.separator}$v").renameTo(File("$unZipDir${File.separator}$newPath"))
                                continue
                            }
                            // 判斷是否有相同的
                            val newPath = if (mappings[v] == null) {
                                val newPath = createProcessPath(v, builder)
                                // 創(chuàng)建路徑
                                val parent = File("$unZipDir${File.separator}$newPath").parentFile
                                if (!parent.exists()) {
                                    parent.mkdirs()
                                }
                                // 移動(dòng)文件
                                val isOk =
                                    File("$unZipDir${File.separator}$v").renameTo(File("$unZipDir${File.separator}$newPath"))
                                if (isOk) {
                                    mappings[v] = newPath
                                    newPath
                                } else {
                                    mappings[v] = v
                                    v
                                }
                            } else {
                                mappings[v]
                            }
                            strings[index] = newPath!!
                        }
                    }


                    val str2 = mappings.map {
                        val startIndex = it.key.lastIndexOf("/") + 1
                        var endIndex = it.key.lastIndexOf(".")


                        if (endIndex < 0) {
                            endIndex = it.key.length
                        }
                        if (endIndex < startIndex) {
                            it.key to it.value
                        } else {
//                            val vStartIndex = it.value.lastIndexOf("/") + 1
//                            var vEndIndex = it.value.lastIndexOf(".")
//                            if (vEndIndex < 0) {
//                                vEndIndex = it.value.length
//                            }
//                            val result = it.value.substring(vStartIndex, vEndIndex)
                            // 使用相同的字符串,以減小體積
                            it.key.substring(startIndex, endIndex) to "du"
                        }
                    }.toMap()


                    // 修改 arsc PackageChunk 字段
                    chunk.chunks.values.filterIsInstance()
                        .flatMap { it.chunks.values }
                        .filterIsInstance()
                        .forEach {
                            for (index in 0 until it.stringCount) {
                                it.getStrings()?.forEachIndexed { index, s ->
                                    str2[s]?.let { result ->
                                        it.setString(index, result)
                                    }
                                }
                            }
                        }


                    // 將 mapping 映射成 指定格式文件,供給反混淆服務(wù)使用
                    val mMappingWriter: Writer = BufferedWriter(FileWriter(file, false))
                    val packageName = context.proguardResourcesExtension.packageName
                    val pathMappings = mutableMapOf()
                    val idMappings = mutableMapOf()
                    mappings.filter { (t, u) -> t != u }.forEach { (t, u) ->
                        result?.add(" $t => $u")
                        compress[t]?.let {
                            compress[u] = it
                            compress.remove(t)
                        }
                        val pathKey = t.substring(0, t.lastIndexOf("/"))
                        pathMappings[pathKey] = u.substring(0, u.lastIndexOf("/"))
                        val typename = t.split("/")[1].split("-")[0]
                        val path1 = t.substring(t.lastIndexOf("/") + 1, t.indexOf("."))
                        val path2 = u.substring(u.lastIndexOf("/") + 1, u.indexOf("."))
                        val path = "$packageName.R.$typename.$path1"
                        val pathV = "$packageName.R.$typename.$path2"
                        if (idMappings[path].isNullOrEmpty()) {
                            idMappings[path] = pathV
                        }
                    }
                    generalFileResMapping(mMappingWriter, pathMappings)
                    generalResIDMapping(mMappingWriter, idMappings)
                }


            // 刪除res下的文件
            FileOperation.deleteDir(File("$unZipDir${File.separator}res"))
            // 將白名單的文件移回res
            keeps.forEach {
                val newPath = it.replaceFirst(whiteTempRes, "res")
                val parent = File("$unZipDir${File.separator}$newPath").parentFile
                if (!parent.exists()) {
                    parent.mkdirs()
                }
                File("$unZipDir${File.separator}$it").renameTo(File("$unZipDir${File.separator}$newPath"))
            }
            // 收尾刪除 res2
            FileOperation.deleteDir(File("$unZipDir${File.separator}$whiteTempRes"))
            resouce
        }

白名單配置必不可少,保證反射調(diào)用資源不參與混淆

createProcessPath 用于將長(zhǎng)路徑修改為短路徑

修改 PackageChunk 中的常量池,用于極致的包體裁剪,未壓縮前減小包體300kb,arsc壓縮后降低包體70kb

282ae886-27b3-11ee-962d-dac502259ad0.png

生成資源混淆mapping文件,提供給包體積服務(wù)進(jìn)行資源名稱還原使用

資源混淆的落地過(guò)程必須要謹(jǐn)慎,對(duì)存量代碼,在得物app中我們先通過(guò)字節(jié)碼掃描找出所有反射調(diào)用資源的地方,配置keep文件。對(duì)于后續(xù)業(yè)務(wù)開(kāi)發(fā)中新增的反射調(diào)用則通過(guò)測(cè)試流程及早發(fā)現(xiàn)問(wèn)題。

1.5 ARSC壓縮

Arsc 壓縮降低的體積非常可觀,壓縮后的arsc 700kb,未壓縮的約 7MB。實(shí)施起來(lái)通過(guò) 7zip對(duì) arsc文件壓縮即可。

28686652-27b3-11ee-962d-dac502259ad0.png

但是 Target Sdk 在30以上 arsc 壓縮被禁了。壓縮 resources.arsc 雖然能帶來(lái)包體上的收益,但也有弊端,它將帶來(lái)內(nèi)存和運(yùn)行速度上的劣勢(shì)。不壓縮的resources.arsc系統(tǒng)可以使用mmap來(lái)節(jié)約內(nèi)存的使用(一個(gè)app的資源至少被3個(gè)進(jìn)程所持有:自己, launcher, system),而壓縮的resources.arsc會(huì)存在于每個(gè)進(jìn)程中。

2

資源下發(fā)

Apk 中的存量大資源在打包后包體積平臺(tái)檢測(cè)出來(lái),針對(duì)問(wèn)題資源排期處理。動(dòng)態(tài)下發(fā)和無(wú)用刪除則是處理存量資源的常用手段,同時(shí)通過(guò) CI 前置管控新增資源過(guò)大的情況。

資源下發(fā)的主體主要是 so 文件和圖片,對(duì)下發(fā)的資源的管控則需可以通過(guò)平臺(tái)化管理。堵不如疏,能下發(fā)的資源就下發(fā)是包體優(yōu)化的一大利器。

288df3fe-27b3-11ee-962d-dac502259ad0.png

下發(fā)的資源通過(guò)動(dòng)態(tài)資源管理平臺(tái)進(jìn)行處理

28d0366a-27b3-11ee-962d-dac502259ad0.png

3

無(wú)用資源刪除

無(wú)用資源的檢測(cè)結(jié)合bytex的 resCheck 編譯期 與 matrix-apk-canary smail 掃描的結(jié)果,將業(yè)務(wù)可以處理的部分在平臺(tái)上展示,版本迭代過(guò)程中邊迭代邊治理,能夠有效防止無(wú)用資源的持續(xù)惡化。

28ff97de-27b3-11ee-962d-dac502259ad0.png

4

總結(jié)

本文主要介紹了得物APP資源優(yōu)化做了的一些動(dòng)作,其中對(duì)資源優(yōu)化插件的工作模式進(jìn)行了重點(diǎn)介紹。當(dāng)然,對(duì)于資源依舊有不少手段可以完善,比如提供高效簡(jiǎn)單的 9 圖下發(fā)方案,包體積平臺(tái)增加圖片相似度檢測(cè)能力、把一些次級(jí)的資源通過(guò)插件包下發(fā)都是之后可以嘗試的地方。





審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 處理器
    +關(guān)注

    關(guān)注

    68

    文章

    19382

    瀏覽量

    230464
  • 二進(jìn)制
    +關(guān)注

    關(guān)注

    2

    文章

    795

    瀏覽量

    41701
  • RAW
    RAW
    +關(guān)注

    關(guān)注

    0

    文章

    21

    瀏覽量

    3818
  • png
    png
    +關(guān)注

    關(guān)注

    0

    文章

    14

    瀏覽量

    4439

原文標(biāo)題:得物Android包體積資源優(yōu)化實(shí)踐

文章出處:【微信號(hào):OSC開(kāi)源社區(qū),微信公眾號(hào):OSC開(kāi)源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    網(wǎng)站云服務(wù)器還是服務(wù)器好一些

    網(wǎng)站云服務(wù)器還是服務(wù)器好一些網(wǎng)站選擇云服務(wù)器通常更好,因?yàn)樗峁┏杀拘б妗⒏呖蓴U(kuò)展性、高可用性和便捷的管理維護(hù),尤其適合中小企業(yè)和個(gè)人網(wǎng)站。雖然傳統(tǒng)服務(wù)器性能和數(shù)據(jù)安全上有優(yōu)勢(shì)
    的頭像 發(fā)表于 01-08 09:56 ?46次閱讀

    HarmonyOS Web開(kāi)發(fā)性能優(yōu)化指導(dǎo)

    網(wǎng)絡(luò)連接、資源下載和完整頁(yè)面渲染這些關(guān)鍵節(jié)點(diǎn)的耗時(shí)進(jìn)行優(yōu)化。 預(yù)啟動(dòng)Web渲染進(jìn)程:預(yù)啟動(dòng)Web渲染進(jìn)程指用戶可以在業(yè)務(wù)需要的Web頁(yè)面啟動(dòng)前,加載個(gè)空白的Web組件,至少
    發(fā)表于 12-06 08:41

    繼電器測(cè)試的培訓(xùn)和學(xué)習(xí)資源有哪些推薦?

    按照自己的進(jìn)度學(xué)習(xí),并且可以隨時(shí)回顧和復(fù)習(xí)課程內(nèi)容。 培訓(xùn)機(jī)構(gòu):一些專門(mén)的培訓(xùn)機(jī)構(gòu)也提供繼電器測(cè)試的培訓(xùn)課程。這些機(jī)構(gòu)通常有豐富的教學(xué)資源實(shí)踐經(jīng)驗(yàn),能夠提供系統(tǒng)全面的培訓(xùn)。選擇培訓(xùn)
    發(fā)表于 12-04 16:35

    分享一些常見(jiàn)的電路

    理解模電和數(shù)電的電路原理對(duì)于初學(xué)者來(lái)說(shuō)可能比較困難,但通過(guò)一些生動(dòng)的教學(xué)方法和資源,可以有效地提高學(xué)習(xí)興趣和理解能力。 下面整理了一些常見(jiàn)的電路,以動(dòng)態(tài)圖形的方式展示。 整流電路 單相橋式整流
    的頭像 發(fā)表于 11-13 09:28 ?367次閱讀
    分享<b class='flag-5'>一些</b>常見(jiàn)的電路

    云計(jì)算平臺(tái)的最佳實(shí)踐

    云計(jì)算平臺(tái)的最佳實(shí)踐涉及多個(gè)方面,以確保高效、安全、可擴(kuò)展和成本優(yōu)化的云環(huán)境。以下是一些關(guān)鍵的最佳實(shí)踐、云成本
    的頭像 發(fā)表于 10-24 09:17 ?388次閱讀

    esp32使用chatGPT一些有意思的事情

    ChatGPT獲得響應(yīng),我們需要進(jìn)行以下步驟:1、OpenAI網(wǎng)站上注冊(cè),并在ESP32安裝必要的庫(kù)。 2、OpenAI API創(chuàng)建
    的頭像 發(fā)表于 10-18 10:04 ?636次閱讀

    關(guān)于一些有助于優(yōu)化電源設(shè)計(jì)的新型材料

    眾所周知,人們對(duì)更高電源效率的追求正在推動(dòng)性能的全方位提升。材料科學(xué)的進(jìn)步對(duì)于優(yōu)化電源設(shè)計(jì)和開(kāi)發(fā)更高效、更緊湊和更可靠的解決方案發(fā)揮著關(guān)鍵作用。下文列出了一些有助于優(yōu)化電源設(shè)計(jì)的新材料。
    的頭像 發(fā)表于 08-29 15:26 ?462次閱讀

    自家APP掃描列表只顯示自家藍(lán)牙設(shè)備的原理

    發(fā)現(xiàn)有一些設(shè)備的app,開(kāi)了掃描設(shè)備后,app的掃描列表里邊只會(huì)顯示自家的藍(lán)牙設(shè)備,而其他家的藍(lán)牙設(shè)備則不會(huì)出現(xiàn)在該列表,求問(wèn)下這個(gè)過(guò)程
    發(fā)表于 07-07 11:06

    電后到app _main的時(shí)間較長(zhǎng),如何優(yōu)化呢?

    LOGE比LOGI可以減少180ms左右的進(jìn)入app_main 的時(shí)間 SPI FLASH的變化, 影響貌似不明顯 這么來(lái)看,最快還是靠近1s,跟CSDN上官方說(shuō)的幾十ms,差距很大,該如何優(yōu)化
    發(fā)表于 06-19 07:50

    設(shè)置應(yīng)用冷啟動(dòng)優(yōu)化案例

    應(yīng)用,詳細(xì)介紹如何進(jìn)行冷啟動(dòng)的性能優(yōu)化。 AppSpawn 預(yù)加載 可以通過(guò)預(yù)加載一些so,加快冷啟動(dòng)的速度。預(yù)加載so 配置appspawn_preload.json文件中。 文件
    發(fā)表于 04-22 16:31

    細(xì)談SolidWorks教育版的一些基礎(chǔ)知識(shí)

    SolidWorks教育版是款廣泛應(yīng)用于工程設(shè)計(jì)和教育領(lǐng)域的三維建模軟件。它具備直觀易用的操作界面和強(qiáng)大的設(shè)計(jì)功能,為學(xué)生提供了個(gè)學(xué)習(xí)和實(shí)踐的平臺(tái)。本文中,我們將詳細(xì)探討Soli
    的頭像 發(fā)表于 04-01 14:35 ?361次閱讀

    振弦采集儀橋梁工程監(jiān)測(cè)中的優(yōu)勢(shì)與實(shí)踐案例

    明顯的優(yōu)勢(shì),下面將介紹其優(yōu)勢(shì)以及一些實(shí)踐案例。 振弦采集儀橋梁工程監(jiān)測(cè)中的優(yōu)勢(shì)與實(shí)踐案例 個(gè)
    的頭像 發(fā)表于 04-01 14:03 ?304次閱讀
    振弦采集儀<b class='flag-5'>在</b>橋梁工程監(jiān)測(cè)中的優(yōu)勢(shì)與<b class='flag-5'>實(shí)踐</b>案例

    電子束光刻的參數(shù)優(yōu)化及常見(jiàn)問(wèn)題介紹

    本文從光刻圖案設(shè)計(jì)、特征尺寸、電鏡參數(shù)優(yōu)化等方面介紹電子束光刻的參數(shù)優(yōu)化,最后介紹一些常見(jiàn)問(wèn)題。
    的頭像 發(fā)表于 03-17 14:33 ?1164次閱讀
    電子束光刻的參數(shù)<b class='flag-5'>優(yōu)化</b>及常見(jiàn)問(wèn)題<b class='flag-5'>介紹</b>

    GitHub Copilot+ESP開(kāi)發(fā)使用問(wèn)題集錦()

    簡(jiǎn)潔明確:2、可以提一些開(kāi)發(fā)中遇到的問(wèn)題,GitHubCopilot會(huì)給個(gè)大概方向供參考。3、可以GitHubCopilot查詢ES
    的頭像 發(fā)表于 03-09 08:03 ?438次閱讀
    GitHub Copilot+ESP開(kāi)發(fā)使用問(wèn)題集錦(<b class='flag-5'>一</b>)

    關(guān)于智能門(mén)禁設(shè)備CCC認(rèn)證申請(qǐng)的一些經(jīng)驗(yàn)分享

    CCC認(rèn)證申請(qǐng)的一些經(jīng)驗(yàn)分享。1.了解CCC認(rèn)證要求:首先,您需要詳細(xì)了解CCC認(rèn)證的規(guī)定和要求,包括適用標(biāo)準(zhǔn)、測(cè)試項(xiàng)目、申請(qǐng)流程等內(nèi)容。CCC認(rèn)證涉及到產(chǎn)品的安
    的頭像 發(fā)表于 03-07 17:10 ?564次閱讀
    關(guān)于智能門(mén)禁設(shè)備<b class='flag-5'>做</b>CCC認(rèn)證申請(qǐng)的<b class='flag-5'>一些</b>經(jīng)驗(yàn)分享
    主站蜘蛛池模板: 午夜资源站| 国内一级野外a一级毛片| 黄在线观看在线播放720p| 亚洲免费福利视频| 午夜视频在线观看视频| 黄鳝钻进下面好爽小说| 色色视频免费网| 四虎东方va私人影库在线观看| 天天爽夜夜爽精品视频一| 成人欧美一区二区三区| 久久人人精品| 美女全黄网站免费观看| 全国最大色成免费网站| 色多多网| 四虎成人精品在永久在线观看| 亚洲人成伊人成综合网久久| 欧美一卡二卡3卡4卡无卡六卡七卡科普 | 女bbbbxxxx毛片视频丶| 午夜日批| 欧美午夜一区| 日本69av| 高h办公室| 2021国产成人精品国产| 美女扒开尿口给男人爽的视频| 亚洲精品视频专区| 亚洲综合一区二区| 永久免费观看黄网站| 中文字幕123区| 道区二区三区四区| 色91在线| 国产女人视频| 国产哺乳期奶水avav| 国产偷窥女洗浴在线观看亚洲| 久久国产高清视频| 靓装爱神12丝袜在线播放| 999www成人免费视频| 一本到午夜92版免费福利| 亚洲夜夜爱| www.av天天| 一女多夫嗯啊高h| 亚洲第一色在线|