网易首页 > 网易号 > 正文 申请入驻

深入探索 Android 包瘦身(中)

0
分享至

码个蛋(codeegg) 第 945 次推文

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

复习上篇:《》

资源瘦身方案探索

众所周知,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 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 duplicatedResources = findDuplicatedResources(apFile);
removeZipEntry(apFile, "resources.arsc");
if (arscFile.exists()) {FileInputStream arscStream = null;ResourceFile resourceFile = null;try {arscStream = new FileInputStream(arscFile);
resourceFile = ResourceFile.fromInputStream(arscStream);List chunks = resourceFile.getChunks();
HashMap toBeReplacedResourceMap = new HashMap(1024);
// 处理arsc并删除重复资源Iterator> iterator = duplicatedResources.entrySet().iterator();while (iterator.hasNext()) {Map.Entry 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 != null) {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 = trueif (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 = nulluse7zip = trueuseSign = true// 打开这个开关,会keep住所有资源的原始路径,只混淆资源的名字keepRoot = false// 设置这个值,会把arsc name列混淆成相同的名字,减少string常量池的大小fixedResName = "arg"// 打开这个开关会合并所有哈希值相同的资源,但请不要过度依赖这个功能去除去冗余资源mergeDuplicatedRes = truewhiteList = [// 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"}
/*** 可选:如果不设置则会默认覆盖assemble输出的apk**/// finalApkBackupPath = "${project.rootDir}/final.apk"
/*** 可选: 指定v1签名时生成jar文件的摘要算法* 默认值为“SHA-1”**/// 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() {@Overridepublic 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.fieldNamedef 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 trueexclude "ldpi", "tvdpi", "xxxhdpi"compatibleScreens 'small', 'normal', 'large', 'xlarge'}}...}

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

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

10、资源在线化

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

11、统一应用风格

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

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
上海发生日本人被刺伤事件,日本提出交涉

上海发生日本人被刺伤事件,日本提出交涉

凤眼论
2026-05-20 16:17:28
杨梅丑闻还没完,荔枝又出事了!商贩大把撒药浸泡,场面触目惊心

杨梅丑闻还没完,荔枝又出事了!商贩大把撒药浸泡,场面触目惊心

谭谈社会
2026-05-21 12:17:39
英伟达业绩炸裂!净利润暴涨211%,9成收入来自数据中心,自研CPU将年入千亿

英伟达业绩炸裂!净利润暴涨211%,9成收入来自数据中心,自研CPU将年入千亿

芯东西
2026-05-21 10:12:45
沪指跌逾1% 下跌个股近4300只

沪指跌逾1% 下跌个股近4300只

财联社
2026-05-21 14:24:07
为了给中国人留下好印象,一生出访无数的普京,这次终于破了例

为了给中国人留下好印象,一生出访无数的普京,这次终于破了例

福建睿平
2026-05-20 14:09:24
一小学购买45把雨伞后“全损”退货!商家:伞身写有学生姓名,雨伞总价338.14元;目前商家已隐藏相关视频,称家委会已致歉并补足损失

一小学购买45把雨伞后“全损”退货!商家:伞身写有学生姓名,雨伞总价338.14元;目前商家已隐藏相关视频,称家委会已致歉并补足损失

极目新闻
2026-05-21 01:02:08
陈克明鸡蛋挂面被指玩文字游戏,鸡蛋含量极低,客服回应:是鸡蛋风味面

陈克明鸡蛋挂面被指玩文字游戏,鸡蛋含量极低,客服回应:是鸡蛋风味面

新浪财经
2026-05-18 16:17:35
女大学生宿舍试穿抹胸裙走红,曼妙身材圈粉无数,露脸后却遭恶评

女大学生宿舍试穿抹胸裙走红,曼妙身材圈粉无数,露脸后却遭恶评

捣蛋窝
2026-05-21 06:40:07
追觅CEO俞浩就loser言论致歉:我脑子抽掉了,不该说不喜欢追觅就是loser,很抱歉伤到了一些人,希望大家给我们改正的机会

追觅CEO俞浩就loser言论致歉:我脑子抽掉了,不该说不喜欢追觅就是loser,很抱歉伤到了一些人,希望大家给我们改正的机会

洪观新闻
2026-05-21 11:45:57
花200架波音订单的钱,其实是给C919的发动机自由买了个倒计时!

花200架波音订单的钱,其实是给C919的发动机自由买了个倒计时!

达文西看世界
2026-05-20 15:04:34
星途EX7护航APEC,中国高端汽车迎来一次“国事级验证”

星途EX7护航APEC,中国高端汽车迎来一次“国事级验证”

AutoBusiness
2026-05-19 12:10:45
越扒瓜越大!交大女学生吞奖金再添猛料,不止想进体制内这么简单

越扒瓜越大!交大女学生吞奖金再添猛料,不止想进体制内这么简单

观史搜寻着
2026-05-21 05:41:15
争议!国际足联重大错误:U17世界杯抽签分档公布 竟然漏掉中国队

争议!国际足联重大错误:U17世界杯抽签分档公布 竟然漏掉中国队

念洲
2026-05-21 08:02:07
荔枝大量上市,销量却猛跌,人们不爱吃荔枝吗?网友说出3个原因

荔枝大量上市,销量却猛跌,人们不爱吃荔枝吗?网友说出3个原因

小谈食刻美食
2026-05-21 07:29:40
人民大会堂女服务万里挑一!她们是啥编制?选拔标准有多严?

人民大会堂女服务万里挑一!她们是啥编制?选拔标准有多严?

复转这些年
2026-05-20 19:44:43
中国房价下跌曲线竟与日本高度重合,此后日本经济长达三十年都在下行

中国房价下跌曲线竟与日本高度重合,此后日本经济长达三十年都在下行

风向观察
2026-05-21 10:01:57
台海突发海空对峙!

台海突发海空对峙!

安安说
2026-05-21 10:26:13
第三批禁足名单将出炉!曝足协今日公布,已有133人遭禁足

第三批禁足名单将出炉!曝足协今日公布,已有133人遭禁足

奥拜尔
2026-05-21 13:32:06
一场西决伤三个!马刺雷霆各有两大核心受伤

一场西决伤三个!马刺雷霆各有两大核心受伤

体坛周报
2026-05-21 14:53:18
今夏告别!皇马34岁队长设宴邀请全队 唯独不请主帅 彻底决裂

今夏告别!皇马34岁队长设宴邀请全队 唯独不请主帅 彻底决裂

叶青足球世界
2026-05-21 08:22:44
2026-05-21 15:19:00
码个蛋
码个蛋
码个蛋
808文章数 298关注度
往期回顾 全部

科技要闻

好到离谱也不够!英伟达交出816亿美元营收

头条要闻

小学网购45把雨伞全损退货 记者采访门卫称领导都不在

头条要闻

小学网购45把雨伞全损退货 记者采访门卫称领导都不在

体育要闻

常住人口7000的小镇,拥有了一支德甲球队

娱乐要闻

同行吐槽汪涵野心重 爆雷37万人受损

财经要闻

英伟达业绩超预!指引再新高仍不够亮眼

汽车要闻

26.98万起步 看小鹏GX如何诠释一车多能以及满配的科技与豪华

态度原创

艺术
家居
时尚
教育
健康

艺术要闻

崔雪冬 2026年油画新作

家居要闻

风格碰撞 个性与艺术

全网首档挑战Al设备拍摄短剧现场直播!

教育要闻

差点失去美国普渡大学名额!亲赴招生办求情,倾力相助终迎转机!

专家:别把PRP当作“自体干细胞”

无障碍浏览 进入关怀版