android开发的奥秘(深入探索Android包瘦身)
android开发的奥秘(深入探索Android包瘦身)
2024-11-22 10:05:08  作者:画上白衣  网址:https://m.xinb2b.cn/tech/jjw446561.html

码个蛋(codeegg) 第 945 次推文

作者:jsonchao链接:https://juejin.im/post/5e7ad1c0e51d450edc0cf053

复习上篇:《深入探索 Android 包瘦身(上)》

资源瘦身方案探索

众所周知,Android构建工具链中使用了AAPT/AAPT2工具来对资源进行处理,Manifest、Resources、Assets 的资源经过相应的 ManifesMerger、ResourcesMerger、AssetsMerger 资源合并器将多个不同 moudule 的资源合并为了 MergedManifest、MergedResources、MergedAssets。然后,它们被 AAPT 处理后生成了 R.java、Proguard Configuration、Compiled Resources。如下图左上方所示:


其中 Proguard Configuration、Compiled Resources作用如下所示:

Proguard Configuration:这是AAPT工具为Manifest中声明的四大组件与布局文件中使用的各种Views所生成的混淆配置,该文件通常存放在${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/proguard-rules/${flavorName}/${buildType}/aapt_rules.txt

Compiled Resources:它是一个Zip格式的文件,这个文件的路径通常为${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/res/resources-${flavorName}-${buildType}-stripped.ap_。在经过zip解压之后,可以发现它包含了res、AndroidManifest.xml和resources.arsc 这三部分。并且,从上面的APK构建流程中可以得知,Compiled Resources会被apkbuilder打包到APK包中,它其实就是APK资源包。因此,我们可以通过 Compiled Resources 文件来修改不同后缀文件资源的压缩方式来达到瘦身效果的。但是需要注意的是,resources.arsc 文件最好不要压缩存储,如果压缩会影响一定的性能,尤其是在冷启动时间方面造成的影响。并且,如果在 Android 6.0 上开启了 android:extractNativeLibs=”false” 的话,So 文件也不能被压缩

1、冗余资源优化

1、使用 Lint 的 Remove Unused Resource

APK的资源主要包括图片、XML,与冗余代码一样,它也可能遗留了很多旧版本当中使用而新版本中不使用的资源,这点在快速开发的App中更可能出现。我们可以通过点击右键,选中Refactor,然后点击Remove Unused Resource => preview可以预览找到的无用资源,点击Do Refactor可以去除冗余资源。如下图所示:


需要注意的,Android Lint 不会分析 assets 文件夹下的资源,因为 assets 文件可以通过文件名直接访问,不需要通过具体的引用,Lint 无法判断资源是否被用到

2、优化 shrinkResources 流程真正去除无用资源

resources.arsc中可能会存在很多无用的资源映射,我们可以使用 android-arscblamer,它是一个命令行工具,能够解析 resources.arsc 文件并检查出可以优化的部分,比如一些空的引用。

此外,当我们通过 shrinkResources true开启资源压缩,资源压缩工具只会把无用的资源替换成预定义的版本而不是移除。那么,如何高效地对无用资源自动进行去除呢?

我们可以 在 Android 构建工具执行 package${flavorName}Task 之前通过修改 Compiled Resources 来实现自动去除无用资源,具体的实现原理如下:

1)、首先,收集 Compiled Resources 中被替换的预定义版本的资源名称

通过查看 Zip 格式资源包中每个 ZipEntry 的 CRC-32 checksum 来寻找被替换的预定义资源,预定义资源的 CRC-32 定义在 ResourceUsageAnalyze 中,如下所示:

// A 1x1 pixel PNG of type BufferedImage.TYPE_BYTE_GRAYpublic static final long TINY_PNG_CRC = 0x88b2a3b0L;// A 3x3 pixel PNG of type BufferedImage.TYPE_INT_ARGB with 9-patch markerspublic static final long TINY_9PNG_CRC = 0x1148f987L;// The XML document <x/> as binary-packed with AAPTpublic static final long TINY_XML_CRC = 0xd7e65643L;

2)、然后,使用 android-chunk-utils 把 resources.arsc 中对应的定义移除。

3)、最后,删除资源包中对应的资源文件即可。

2、重复资源优化

在大型 App项目的开发中,一个App一般会有多个业务团队进行开发,其中每个业务团队在资源提交时的资源名称可能会有重复的,这将会引发资源覆盖的问题,因此,每个业务团队都会为自己的资源文件名添加前缀。这样就导致了这些资源文件虽然内容相同,但因为名称的不同而不能被覆盖,最终都会被集成到APK包中。这里,我们还是可以在 Android 构建工具执行 package${flavorName}Task 之前通过修改 Compiled Resources 来实现重复资源的去除,具体放入实现原理可细分为如下三个步骤:

1)、首先,通过资源包中的每个ZipEntry的CRC-32 checksum来筛选出重复的资源

2)、然后,通过android-chunk-utils修改resources.arsc,把这些重复的资源都重定向到同一个文件上

3)、最后,把其它重复的资源文件从资源包中删除,仅保留第一份资源

具体的实现代码如下所示:

variantData.outputs.each { def apfile = it.packageAndroidArtifactTask.getResourceFile; it.packageAndroidArtifactTask.doFirst { def arscFile = new File(apFile.parentFile, "resources.arsc"); JarUtil.extractZipEntry(apFile, "resources.arsc", arscFile); def HashMap<String, ArrayList<DuplicatedEntry>> duplicatedResources = findDuplicatedResources(apFile); removeZipEntry(apFile, "resources.arsc"); if (arscFile.exists) { FileInputStream arscStream = ; ResourceFile resourceFile = ; try { arscStream = new FileInputStream(arscFile); resourceFile = ResourceFile.fromInputStream(arscStream); List<Chunk> chunks = resourceFile.getChunks; HashMap<String, String> toBeReplacedResourceMap = new HashMap<String, String>(1024); // 处理arsc并删除重复资源 iterator<Map.Entry<String, ArrayList<DuplicatedEntry>>> iterator = duplicatedResources.entrySet.iterator; while (iterator.hasNext) { Map.Entry<String, ArrayList<DuplicatedEntry>> duplicatedEntry = iterator.next; // 保留第一个资源,其他资源删除掉 for (def index = 1; index < duplicatedEntry.value.size; index) { removeZipEntry(apFile, duplicatedEntry.value.get(index).name); toBeReplacedResourceMap.put(duplicatedEntry.value.get(index).name, duplicatedEntry.value.get(0).name); } } for (def index = 0; index < chunks.size; index) { Chunk chunk = chunks.get(index); if (chunk instanceof ResourceTableChunk) { ResourceTableChunk resourceTableChunk = (ResourceTableChunk) chunk; StringPoolChunk stringPoolChunk = resourceTableChunk.getStringPool; for (def i = 0; i < stringPoolChunk.stringCount; i) { def key = stringPoolChunk.getString(i); if (toBeReplacedResourceMap.containsKey(key)) { stringPoolChunk.setString(i, toBeReplacedResourceMap.get(key)); } } } } } catch (IOException ignore) { } catch (FileNotFoundException ignore) { } finally { if (arscStream != ) { IOUtils.closeQuietly(arscStream); } arscFile.delete; arscFile << resourceFile.toByteArray; addZipEntry(apFile, arscFile); } } }}

然后,我们再看看图片压缩这一项。

3、图片压缩

一般来说,1000行代码在APK中才会占用 5kb 的空间,而图片呢,一般都有100kb左右,所以说,对图片做压缩,它的收益明显是更大的,而往往处于快速开发的App没有相关的开发规范,UI设计师或开发同学如果忘记了添加图片时进行压缩,添加的就是原图,那么包体积肯定会增大很多。对于图片压缩,我们可以在 tinypng 这个网站进行图片压缩,但是如果App的图片过多,一个个压缩也是很麻烦的。因此,我们可以使用 McImage、TinyPngPlugin 或 TinyPIC_gradle_Plugin 来对图片进行自动化批量压缩。但是,需要注意的是,在 Android 的构建流程中,AAPT 会使用内置的压缩算法来优化 res/drawable/ 目录下的 PNG 图片,但这可能会导致本来已经优化过的图片体积变大,因此,可以通过在build.gradle设置 cruncherEnabled 来禁止 AAPT 来优化 PNG 图片,代码如下所示:

aaptOptions { cruncherEnabled = false}

此外,我们还要注意对图片格式的选择,对于我们普遍使用更多的 png或者是jpg格式来说,相同的图片转换为webp格式之后会有大幅度的压缩。对于 png 来说,它是一个无损格式,而 jpg 是有损格式。jpg 在处理颜色图片很多时候根据压缩率的不同,它有时候会去掉我们肉眼识别差距比较小的颜色,但是 png 会严格地保留所有的色彩。所以说,在图片尺寸大,或者是色彩鲜艳的时候,png的体积会明显地大于jpg

下面,我们就着重讲解下如何针对性地选择图片格式。

4、使用针对性的图片格式

Google I/O 2016中,讲到了如何选择相应的图片格式。首先,如果能用 VectorDrawable 来表示的话,则优先使用 VectorDrawable;否则,看是否支持 WebP,支持则优先用 WebP;如果也不能使用 WebP,则优先使用 PNG,而 PNG 主要用在展示透明或者简单的图片,对于其它场景可以使用 JPG 格式。简单来说可以归结为如下套路:

VD(纯色icon)->WebP(非纯色icon)->Png(更好效果) ->jpg(若无alpha通道)

图形化的形式如下所示:


使用矢量图片之后,它能够有效的减少应用中图片所占用的大小,矢量图形在 Android 中表示为 VectorDrawable 对象。它仅仅需100字节的文件即可以生成屏幕大小的清晰图像,但是,Android 系统渲染每个 VectorDrawable 对象需要大量的时间,而较大的图像需要更长的时间。因此,建议只有在显示纯色小 icon 时才考虑使用矢量图形。(我们可以利用这个 在线工具 将矢量图转换成 VectorDrawable)。

最后,如果要在项目中使用 VD,则以下几点需要着重注意:

1)、必须通过 app:arcCompat 属性来使用 svg,如果通过 src,则在低版本手机上会出现不兼容的问题

2)、可能会不兼容selector,在Activity中手动兼容即可,兼容代码如下所示:

static { AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) }

3)、不兼容第三方库

4)、性能问题:当Vector比较简单时,效率肯定比Bitmap高,复杂则效率会不如Bitmap

5)、不便于管理:建议原则为同目录多类型文件,以前缀区别,不同目录相同类型文件,以意义区分

VD类似,还有一种矢量图标iconFont,即字体图标,图标就在字体文件里面,它看着是个图标,其实却是个文字。它的优势有如下三个方面:

1)、同 VD 一样,由于 IconFont 是矢量图标,所以可以轻松解决图标适配问题

2)、图标以 .ttf 字体文件的形式存在项目中,而 .ttf 文件一般放在 assets 文件夹下,它的体积很小,可以减小 APK 的体积

3)、一套图标资源可以在不同平台使用且资源维护方便

它的 缺点也很明显,大致有如下三个方面:

1)、需要自定义 svg 图片,并将其转换为 ttf 文件,图标制作成本比较高

2)、添加图标时需要重新制作 ttf 文件

3)、只能支持单色,不支持渐变色图标

如果你想要使用 iconfont,可以在阿里的 iconfont 上寻找资源。此外,使用 Android-Iconics 可以在你的应用中便于使用任何的 iconfont 或 .svg 图片作为 drawable。最后,如果我们仅仅想提取仅需要的美化文字,以压缩 assets 下的字体文件大小,可以使用 FontZip 字体提取工具

如果不是纯色小 icon类型的图片,则建议使用WebP。只要你的AppminSdkVersion高于 14(Android 4.0 ) 即可。WebP不仅支持透明度,而且压缩率比JPEG更高,在相同画质下体积更小。但是,只有 Android 4.2.1 才支持显示含透明度的 WebP,此外,它的兼容性不好,并且不便于预览,需使用浏览器打开

对于应用之前就存在的图片,我们可以使用 PNG转换WebP 的转换工具来进行转换。但是,一个一个转换开发效率太低,因此我们可以 使用WebpConvert_Gradle_Plugin 这个 gradle 插件去批量进行转换,它的实现原理是在 mergeXXXResource Task 和 processXXXResource Task 之间插入了一个 WebpConvertPlugin task 去将 png、jpg 图片批量替换成了 webp 图片

此外,在 Gradle构建APK的过程中,我们可以判断当前AppminSdkVersion以及图片文件的类型来选用是否能使用WebP,代码如下所示:

boolean isPNGWebpConvertSupported { if (!isWebpConvertEnable) { return false } // Android 4.0 return GradleUtils.getAndroidExtension(project).defaultConfig.minSdkVersion.apiLevel >= 14 // 4.0}boolean isTransparencyPNGWebpConvertSupported { if (!isWebpConvertEnable) { return false } // Lossless, Transparency, Android 4.2.1 return GradleUtils.getAndroidExtension(project).defaultConfig.minSdkVersion.apiLevel >= 18 // 4.3}def convert { String resPath = "${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/res/merged/${variant.dirName}" def resDir = new File("${resPath}") resDir.eachDirMatch(~/drawable[a-z0-9-]*/) { dir -> FileTree tree = project.fileTree(dir: dir) tree.filter { File file -> return (isJPGWebpConvertSupported && (file.name.endsWith(SdkConstants.DOT_JPG) || file.name.endsWith(SdkConstants.DOT_JPEG))) || (isPNGWebpConvertSupported && file.name.endsWith(SdkConstants.DOT_PNG) && !file.name.endsWith(SdkConstants.DOT_9PNG)) }.each { File file -> def shouldConvert = true if (file.name.endsWith(SdkConstants.DOT_PNG)) { if (!isTransparencyPNGWebpConvertSupported) { shouldConvert = !Imaging.getImageInfo(file).isTransparent } } if (shouldConvert) { WebpUtils.encode(project, webpFactorQuality, file.absolutePath, webp) } } }}

最后,这里再补充下在平时项目开发中对 图片放置优化的大概思路,如下所示:

1)、聊天表情出一套图 => hdpi

2)、纯色小 icon 使用 VD => raw

3)、背景大图出一套 => xhdpi

4)、logo 等权重比较大的图片出两套 => hdpi,xhdpi

5)、若某些图在真机中有异常,则用多套图

6)、若遇到奇葩机型,则针对性补图

然后,我们来讲解下资源如何进行混淆。

5、资源混淆

同代码混淆类似,资源混淆将 资源路径混淆成单个资源的路径,这里我们可以使用AndroidResGuard,它可以使冗余的资源路径变短,例如将res/drawable/wechat变为r/d/a

AndroidResGuard 项目地址

下面,我们就使用 AndroidResGuard来对资源进行混淆。

1、AndroidResGuard 实战

1、首先,我们在项目的根 build.gradle 文件下加入下面的插件依赖:

classpath 'com.tencent.mm:AndResGuard-gradle-plugin:1.2.17'

2、然后,在项目 module 下的 build.gradle 文件下引入其插件:

apply plugin: 'AndResGuard'

3、接着,加入 AndroidResGuard 的配置项,如下是默认设置好的配置:

andResGuard { // mappingFile = file("./resource_mapping.txt") mappingFile = use7zip = true useSign = true // 打开这个开关,会keep住所有资源的原始路径,只混淆资源的名字 keepRoot = false // 设置这个值,会把arsc name列混淆成相同的名字,减少string常量池的大小 fixedResName = "arg" // 打开这个开关会合并所有哈希值相同的资源,但请不要过度依赖这个功能去除去冗余资源 mergeDuplicatedRes = true whiteList = [ // for your icon "R.drawable.icon", // for fabric "R.string.com.crashlytics.*", // for google-services "R.string.google_app_id", "R.string.gcm_defaultSenderId", "R.string.default_web_client_id", "R.string.ga_trackingId", "R.string.firebase_database_url", "R.string.google_api_key", "R.string.google_crash_reporting_api_key" ] compressFilePattern = [ "*.png", "*.jpg", "*.jpeg", "*.gif", ] sevenzip { artifact = 'com.tencent.mm:SevenZip:1.2.17' //path = "/usr/local/bin/7za" } // finalApkBackupPath = "${project.rootDir}/final.apk" // digestalg = "SHA-256"}

4、最后,我们点击右边的项目 module/Tasks/andresguard/resguardRelease 即可生成资源混淆过的 APK。如下图所示:


APK生成目录如下:


对于 AndResGuard 工具,主要有 两个功能,一个是资源混淆,一个是资源的极限压缩。下面,我们就来分别了解下它们的实现原理。

2、AndResGuard 的资源混淆原理

资源混淆工具主要是通过 短路径的优化,以达到减少 resources.arsc、metadata 签名文件以及 ZIP 文件大小的效果,其效果分别如下所示:

1)、resources.arsc:它记录了资源文件的名称与路径,使用混淆后的短路径 res/s/a,可以减少文件的大小

2)、metadata 签名文件:签名文件 MANIFEST.MF 与 CERT.SF 需要记录所有文件的路径以及它们的哈希值,使用短路径可以减少这两个文件的大小

3)、ZIP 文件:ZIP 文件格式里面通过其索引记录了每个文件 Entry 的路径、压缩算法、CRC、文件大小等等信息。短路径的优化减少了记录文件路径的字符串大小

3、AndResGuard 的极限压缩原理

AndResGuard使用了7-Zip 的大字典优化APK整体压缩率可以提升 3% 左右,并且,它还支持针对 resources.arsc、PNG、JPG 以及 GIF 等文件进行强制压缩(在编译过程中,这些文件默认不会被压缩)。那么,为什么 Android 系统不会去压缩这些文件呢?主要基于以下两点原因

1)、压缩效果不明显:上述格式的文件大部分已经被压缩过,因此,重新做Zip压缩效果并不明显。比如 重新压缩PNGJPG格式只能减少3%~5%的大小。

2)、基于读取时间和内存的考虑:针对于没有进行压缩的文件,系统可以使用 mmap 的方式直接读取,而不需要一次性解压并放在内存中。

此外,抖音 Android 团队还开源了针对于海外市场 App Bundle APK 的 AabResGuard 资源混淆工具,对它的实现原理有兴趣的同学可以去了解下。然后,我们再看看资源瘦身的其它方案。

6、R Field 的内联优化

我们可以通过内联 R Field来进一步对代码进行瘦身,此外,它也解决了 R Field 过多导致 MultiDex 65536 的问题。要想实现内联R Field,我们需要通过 Javassist 或者 ASM 字节码工具在构建流程中内联 R Field,其代码如下所示:

ctBehaviors.each { CtBehavior ctBehavior -> if (!ctBehavior.isEmpty) { try { ctBehavior.instrument(new ExprEditor { @Override public void edit(FieldAccess f) { try { def fieldClassName = JavassistUtils.getClassNameFromCtClass(f.getCtClass) if (shouldInlineRField(className, fieldClassName) && f.isReader) { def temp = fieldClassName.substring(fieldClassName.indexOf(ANDROID_RESOURCE_R_FLAG) ANDROID_RESOURCE_R_FLAG.length) def fieldName = f.fieldName def key = "${temp}.${fieldName}" if (resourceSymbols.containsKey(key)) { Object obj = resourceSymbols.get(key) try { if (obj instanceof Integer) { int value = ((Integer) obj).intValue f.replace("\$_=${value};") } else if (obj instanceof Integer[]) { def obj2 = ((Integer[]) obj) StringBuilder stringBuilder = new StringBuilder for (int index = 0; index < obj2.length; index) { stringBuilder.append(obj2[index].intValue) if (index != obj2.length - 1) { stringBuilder.append(",") } } f.replace("\$_ = new int{${stringBuilder.toString}};") } else { throw new GradleException("Unknown ResourceSymbols Type!") } } catch (NotFoundException e) { throw new GradleException(e.message) } catch (CannotCompileException e) { throw new GradleException(e.message) } } else { throw new GradleException("******** InlineRFieldTask unprocessed ${className}, ${fieldClassName}, ${f.fieldName}, ${key}") } } } catch (NotFoundException e) { } } }) } catch (CannotCompileException e) { } }}

这里,我们可以 直接使用蘑菇街的 ThinRPlugin。它的实现原理为:android 中的 R 文件,除了 styleable 类型外,所有字段都是 int 型变量/常量,且在运行期间都不会改变。所以可以在编译时,记录 R 中所有字段名称及对应值,然后利用 ASM 工具遍历所有 Class,将除 R$styleable.class 以外的所有 R.class 删除掉,并且在引用的地方替换成对应的常量,从而达到缩减包大小和减少Dex个数的效果。此外,最近ByteX也增加了 shrink_r_class 的gradle插件,它不仅可以在编译阶段对R文件常量进行内联,而且还可以针对 App 中无用 Resource 和无用 assets 的资源进行检查

7、资源合并方案

我们可以把所有的资源文件合并成一个大文件,而 一个大资源文件就相当于换肤方案中的一套皮肤。它的效果比资源混淆的效果会更好,但是,在此之前,必须要解决解析资源管理资源的问题。其相应的解决方案如下所示:

模拟系统实现资源文件的解析:我们需要使用自定义的方式把 PNG、JPG 以及 XML 文件转换为 Bitmap 或者 Drawable

使用 mmap 加载大资源与资源缓存池管理资源:使用 mmap 加载大资源的方式可以充分减少启动时间与系统内存的占用。而且,需要使用 Glide 等图片框架的资源缓存池 ResourceCache 去释放不再使用的资源文件

8、资源文件最少化配置

我们需要 根据 App 目前所支持的语言版本去选用合适的语言资源,例如使用了AppCompat,如果不做任何配置的话,最终APK包中会包含AppCompat中所有已翻译语言字符串,无论应用的其余部分是否翻译为同一语言。对此,我们可以通过 resConfig 来配置使用哪些语言,从而让构建工具移除指定语言之外的所有资源。同理,也可以使用 resConfigs 去配置你应用需要的图片资源文件类,如 "xhdpi"、"xxhdpi" 等等,代码如下所示:

android { ... defaultConfig { ... resConfigs "zh", "zh-rCN" resConfigs "nodpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi" } ...}

此外,我们还以 利用 Density Splits 来选择应用应兼容的屏幕尺寸大小,代码如下所示:

android { ... splits { density { enable true exclude "ldpi", "tvdpi", "xxxhdpi" compatibleScreens 'small', 'normal', 'large', 'xlarge' } } ...}

9、尽量每张图片只保留一份

比如说,我们统一只把图片放到 xhdpi这个目录下,那么在不同的分辨率下它会做自动的适配,即等比例地拉伸或者是缩小

10、资源在线化

我们可以 将一些图片资源放在服务器,然后结合图片预加载的技术手段,这些既可以满足产品的需要,同时可以减小包大小

11、统一应用风格

如设定统一的 字体、尺寸、颜色和按钮按压效果、分割线 shape、selector 背景等等。

阿里大佬十年面试了 2000 人,总结这7 条金科玉律

深入探索 Android 包瘦身(上)

Android 多线程技术哪家强?

资源瘦身的这些方法用过哪些?

  • 王阳明传习录解读(王阳明的诡辩-传习录研读)
  • 2024-11-22王阳明的诡辩-传习录研读#头条创作挑战赛#阳明心学泛滥属不属于大道废,有仁义?王阳明写了《传习录》和《拔本塞源论》,站在中华大地的道德制高点指点江山他的立论就是华夏大地已经物欲横流,道德沦丧,世风日下,大家已经丧失了成为道德。
  • 玉米戚风(怎样制作玉米戚风)
  • 2024-11-22怎样制作玉米戚风蛋黄3个、细砂糖20克、玉米油40ml、玉米汁50ml、低粉60克蛋白4个、柠檬汁少许、细砂糖40克、盐少许罐装玉米粒40克沥干水分、低粉少许蛋黄打匀后分次加入细砂糖搅至颜色变浅加入玉米油搅匀后加入玉。
  • LOL纳尔攻略(LOL10.9英雄平衡调整更新内容一览)
  • 2024-11-22LOL10.9英雄平衡调整更新内容一览LOL10.9版本的英雄调整内容一览,今天给大家带来的就是lol10.9版本中的全英雄调整内容一览,希望对大家有所帮助  星界游神巴德  W技能移动速度加成降低,冷却时间提升E技能冷却时间提升  在高。
  • 三非物品清理整治工作宣讲资料(吉安市全力推进)
  • 2024-11-22吉安市全力推进公共场所、社区、住宅小区醒目位置张贴、发放公告,宣传车深入市中心城区街、路、社区巡回广播“三禁”要求,线上线下多形式广泛宣传、唱响绿色低碳文明祭祀主旋律……连日来,吉安市各地绿色低碳文明祭祀进社区、进。
  • 华为手环4各功能介绍(华为手环4Pro独立GPS)
  • 2024-11-22华为手环4Pro独立GPS在华为nova6系列5G新品发布会上,华为正式发布了新一代的华为手环4Pro,新款手环将于12月12日正式开售,售价399元华为手环4Pro是一款主打健康运动、潮流生活方式的日常穿戴智能手环,其内置独。
  • 你睡觉吧别把孩子教傻(你家孩子真放的开)
  • 2024-11-22你家孩子真放的开前不久宝妈群里一位宝妈讲述了一件事,一个远方亲戚带她五岁的女儿,还有她弟弟家五岁的男孩,到她家里做客女孩到处翻看,吃水果专门挑大的,吃两口整个就掉了,一会一个男孩就比较好,吃的时候只挑自己喜欢的,也不。
  • 怎样保护矿产资源(如何保护矿产资源)
  • 2024-11-22如何保护矿产资源进一步健全环境保护与治理的法规体系限制或禁止不合理的乱采滥挖,防止矿产资源的损失、浪费或破坏合理开发利用矿产资源,优化资源配置,实现矿产资源的最优耗竭加大对矿山科技进步的投资,提高矿产资源开发的科学技。
  • 松树为什么四季常青(松树四季常青原因)
  • 2024-11-22松树四季常青原因松树常青的原因,主要是因为松树叶子相对较细小,而且叶子上面有一层蜡制的保护膜,可以保护叶子,使叶子水分不易流失,也使得自己一年四季都是郁郁葱葱松树四季常青主要是因为它的叶子的特有形状针状,一般都缩小呈。
  • 给猫自制湿粮(自制猫湿粮图文版)
  • 2024-11-22自制猫湿粮图文版最近有朋友问我,作为一个多猫家庭的铲屎官,你都是怎样养猫的?这个问题是很宽泛的,不过我个人认为猫主子的吃饭问题是应该排在第一位的今天我就给大家分享一下我饲喂我家猫主子的一些经验猫咪吃猫粮,这看似一个很。
  • 凹凸世界法尔法拉获得(法尔法拉抽取性价比分析)
  • 2024-11-22法尔法拉抽取性价比分析虚拟歌姬法尔法拉是由莱耶斯编写程序制作而成的,作为女团的主唱,她的歌声里始终充满爱意她是温柔治愈的大姐姐,虽然偶尔有些迷糊,但无论何时都能保持乐观的笑颜,真心地爱着这个世界,爱着自己的家人【法尔法拉】。
  • 凉拌萝卜干的家常做法(拌萝卜干的家常做法)
  • 2024-11-22拌萝卜干的家常做法淡味萝卜干一把,盐2勺,糖1勺,辣椒粉2勺,生抽1勺,花椒油1勺,植物油1勺,芝麻油适量,白芝麻适量,味精适量准备好一把萝卜干把萝卜干用清水冲洗一下,再泡在清水中20分钟左右,让萝卜干吸水膨胀成图中的。
  • 吴绮莉近照情况如何(浅谈吴绮莉坎坷的一生)
  • 2024-11-22浅谈吴绮莉坎坷的一生今天小编和大家聊聊吴绮莉的感情故事!话不多说下面跟随小编一起来看看吧!小编对吴绮莉不是很熟悉,翻阅资料找到吴绮莉出演了《中环英雄》里饰演马诗婷,在《胜者为王》里饰演聂希桐,在《海马歌舞厅》里饰演配角,。