抖音Android包体积优化探索基于ReDex的DEX优化落
本文作者:冯瑞;廖斌斌;刘丰恺前言
应用安装包的体积会显著影响应用的下载速度和安装速度,按照Google的经验数据,包体积每增加1M会造成0。17的新增折损。抖音的一些实验也证明了包体积会显著影响下载激活的转化率。
Android的安装包是APK格式的,在抖音的安装包中DEX的体积占比达到了40以上,所以针对DEX的体积优化是一种行之有效的包体积优化手段。
DEX本质上是由JavaKotlin代码编译而成的字节码,因此,针对字节码进行业务无感的通用优化成为我们的一个探索方向。优化结果
终端基础技术团队和抖音基础技术团队在过去的一年里,利用ReDex在抖音包体积优化方面取得了一些明显的收益,这些优化也被同步到了其他各大App上。
在抖音、头条和其他应用上,我们的优化对APK体积的缩减普遍达到了4以上,对DEX体积的缩减则可以达到810优化思路
在android应用的构建过程中,JavaKotlin代码会先被编译成Class字节码,在这个阶段gradle提供了Transformer可以进行字节码的自定义处理,很多插件都是在这个阶段处理字节码的。然后,Class文件经过dexBuildermergeDex等任务的处理会生成DEX文件,并最终被打进安装包中。整个过程如下所示:
所以,针对字节码的优化是有2个时机可以进行的:在transformer阶段对Class字节码进行优化在DEX阶段对DEX文件进行优化
显然,对DEX进行优化是更理想的一种方式,因为在DEX文件中,除了字节码指令外,还存在跨DEX引用、字符串池这样的结构,针对这些DEX格式的优化是无法在transformer阶段进行的。
在确定了针对DEX文件进行优化的思路后,我们选择了facebook的开源框架ReDex作为优化工具,并对其进行了定制开发。
选择ReDex的原因是它提供了丰富的基础能力,ReDex的基础能力包括:读写及解析DEX的能力,同时可以在一定程度上读取并解析xml和so文件解析简单的proguardkeep规则并匹配类方法成员变量的能力对字节码进行数据流分析的能力,提供了常用的数据流分析算法对字节码进行合法性校验的能力,包括寄存器检查、类型检查等一系列的字节码优化项,每项优化称为一个pass,多个pass组成pipeline对DEX进行优化
我们基于这些能力进行了定制和扩展,并期望最终建立完善的优化体系。优化项
在抖音落地的优化项,包括facebook开源的优化和我们自研的优化,从其出发点来看,可以大致分为下面几种:通用字节码优化:通常意义下的编译优化,如常量传播、内联等,一般也可在Transformer阶段实现DEX格式优化:DEX中除了字节码指令外,还包括字符串池、类方法引用、debug信息等等,针对这些方面的优化归类为DEX格式优化针对编程语言的优化:JavaKotlin的一些语法糖会生成大量字节码,可以对这些字节码进行针对性的分析和优化提升压缩率的优化:将DEX打包成APK实质上是个压缩的过程,对DEX内容进行针对性的优化可以提升压缩率,从而产生体积更小的APK
这几种优化没有明确的标准和界线,有时一个Pass会涉及到多种,下面详细介绍一下各项优化。通用字节码优化ConstantPropagationPass
该Pass实际上包含了常量折叠和常量传播。
常量折叠是在编译期简化常量的过程,比如1y714223y0
常量传播是在编译期替代指令中已知常量的过程,比如1intx14;2inty7x2;3returny(28x2);45intx14;6inty7142;7return(7142)(28142);
上面的例子经过常量折叠常量传播优化后就会简化为1intx14;2inty0;3return0;
再经过死代码删除就可以最终变为return0。
具体的优化过程是:对方法进行数据流分析,主要针对constmove等指令,得出一个寄存器在某个位置可能的取值根据分析的结果,进行指令替换或指令删除,包括:如果值肯定是非空的,可以将对应的判空去掉,比如kotlin生成的nullcheck调用如果值肯定为空,可以将指令替换为抛空异常如果值肯定让某if分支走不到,可以删除对应的分支如果值是固定的,可以用const指令替换对应的赋值或计算指令
一个方法经过ConstantPropagationPass优化后,可能会产生一些死代码,比如例子中的inty0,这也为后续的死代码删除创造了条件。AnnoKillPass
该Pass是用来移除无用注解的。注解主要分为三种类型:SOURCE:java源码编译为class字节码就不可见,此类注解一般不用过于关注CLASS:字节码通过dx工具转成DEX就不可见,代码运行时不需要获取信息,所以一般来说也不需要关注,实测发现部分注解仍然存在于DEX中,这部分注解可以进行优化RUNTIME:DEX中仍然可见,代码运行中可以通过getAnnotations等接口获取注解信息,但是随着业务的迭代,可能获取注解信息的代码已经去掉,注解却没有下掉,这部分注解会被ReDex安全的移除
除此之外,实际上为了支持某些系统特性,编译器会自动生成系统注解,虽然注解本身是RUNTIME类型,但是可见性是VISIBILITYSYSTEMAnnotationDefault:默认注解,不能删除EnclosingClass:当前内部类申明时所在的类EnclosingMethod:当前内部类申明时所在的方法InnerClass:当前内部类名称MemberClasses:当前类的所有内部类列表MethodParameters:方法参数Signature:泛型相关Throws:异常相关
举例说明
编译器生成1MainApplication1这个匿名内部类,带有EnclosingMethod和InnerClass注解
系统提供以下接口获取类相关的信息,就是通过分析相关的系统注解来实现的Class。getEnclosingMethodClass。getSimpleNameClass。isAnonymousClass。。。。
如果代码中不存在使用这些接口获取类信息的逻辑,就可以安全的移除这部分注解,从而达到缩减包大小的目的。RenameClassesPass
该Pass通过缩减类名的字符串长度来减小包体积
比如把类名从Labcde;改为LXa;,可以类名字符串的长度,从而达到包大小缩减的目的。实际上Proguard本身已经提供类似的功能:repackageclassesX,效果如下:
但是repackageclassesX的处理会影响ReDex的InterDexPass的算法逻辑(InterDexPass可以参考下文),导致收益缩减收益测试ProguardrepackageclassesX收益:600KRedexInterDexPass收益:400K同时应用ProguardrepackageclassesX和RedexInterDexPass收益:40K
本质原因在于Proguard重命名后,影响了InterDexPass函数引用权重分配,导致InterDex收益被回收解决方案InterDexPass深入分析原理,优化权重算法先执行InterDexPass,后执行类似Proguard的repackageclassesX
权重算法优化相对来说比较复杂,同时存在众多不可确定性,比如潜在的跟其他优化的冲突,所以我们采取了第二种解决方案。
这里需要解决的一个关键点在于如何确定一个类名是否可以被安全的重命名,我们采取了一个比较取巧的方式,ReDex会分析Proguard传递上来mapping。txt文件,只要我们保持跟Proguard类重命名优化一样的处理策略,就不会引发反射native调用序列化等一系列问题。
但是执行起来还是碰到各种千奇百怪的问题,比如Signature系统注解失效问题。Signature注解的内容是非标准的类名格式,所以类重命名后简单回写字符串或者更新Type类型会导致Signature注解失效,最后通过深入解析Signature格式规避了这个问题。StringBuilderOutlinerPass
该Pass是针对StringBuilder的CallSites进行分析缩略的优化,与死代码删除搭配使用可以有不错的优化效果。
为何要优化StringBuilder呢?在Java的代码开发过程中,字符串操作几乎是我们最经常做的一件事情,无论是实际处理字符串拼接还是各种不同数据类型之间的拼接操作。而这些拼接操作都会被Java的desugar优化为StringBuilder操作。比如:varlogA1B1。0fothervar;会被优化为:1StringBuilderbuildernewStringBuilder();2builder。append(A);builder。append(1);3builder。append(B);builder。append(1。0f);4builder。append(othervar);5builder。toString();
因此我们对StringBuilder的所有Callsites进行分析,在最好情况下多个方法调用可以被优化为一个调用,这个方法是一个outline(外联)方法,具体的参数拼接和toString被隐藏在函数内部:1invokestatic{v1,v2,v3}Outline;。bind:(〔LjavalangObject)LjavalangString;
优化步骤可以被简单的分为如下几个步骤:生成一个泛型的外联方法、以及数个特定参数的方法:我们可以认为生成的方法大概是这样的1Keep2publicstaticStringbind(Object。。。args){3StringBuilderbuildernewStringBuilder();4for(inti0;iargs。length;i){5builder。append(args〔i〕);6}7returnbuilder。toString();8}收集StringBuilder的CallSites:通过抽象解释和不动点分析,分析所有的StringBuilder操作,对append、newinstance、和init方法分类。判断每次append的参数是不是immutable操作,如果增加的insn少于减少的insn即会减少代码,就对这里进行处理。生成外联方法调用:由于我们使用了泛型方法来接受参数,因此我们要对基础类型生成ValueOf的转换操作、并且删除append方法前为了防止被错误优化我们还需要插入move指令来copy原有参数(这些move指令会被后续优化正确删除)、如果参数个数还在我们生成的特定outline方法范围内我们就可以使用特定方法来生成外联函数,其余的将使用泛化的外联来接受。DEX格式优化InterDexPass
该Pass是针对跨DEX引用的优化。
跨DEX引用是指当一个DEX需要使用到另一个DEX中的类方法变量时,需要在本DEX中保存一份对应的类方法变量的id,如果2个DEX用到了相同的字符串,那么这个字符串在2个DEX都需要进行定义。所以,改变类方法变量和字符串在DEX中的分布,可以减小引用的数量,从而减小DEX的体积。从原理中也可以看出,该优化对单DEX的应用是无效的。
从上图可以看到,进行类重排后,DEX0的类引用和方法引用数量都减少了,DEX的体积也会因此减小。
具体的优化过程是:收集每个类涉及的所有引用,按照引用数量和类型计算出类的权重根据权重计算出每个类的优先级根据优先级选取一个类放入DEX中,然后调整剩余类的优先级,重复此步骤直到所有类都被处理ReBindRefsPass
该Pass是针对方法引用的优化,其原理同InterDexPass。
在字节码中,invokevirtualinterface指令需要一个方法引用,在很多情况下,这个引用指向的是子类或者实现类的引用,把这个引用替换成父类和接口的方法引用不会影响运行时逻辑,同时会减少DEX中方法引用的数量。在生成DEX的时候,方法引用的65536限制通常是最先遇到的瓶颈,该优化也可以缓解这种情况。
如上图所示,优化前caller方法的invoke指令使用的是子类引用,其伪指令如下所示,需要用到2个引用1newinstancev0,Sub12invokevirtualv0,Sub1。a()3newinstancev1,Sub24invokevirtualv1,Sub2。a()
优化后,invoke指令都指向其父类应用,2个引用可以合并为1个,减少了DEX中的引用数量1newinstancev0,Sub12invokevirtualv0,Base。a()3newinstancev1,Sub24invokevirtualv1,Base。a()针对编程语言的优化KotlinDataClassPass
该Pass是对Kotlindataclass的优化,基本思路是对dataclass的生成代码进行精简。解构声明优化
Kotlin中存在解构声明这种语法,可以更方便的创建多个变量,基本用法如下1dataclassPerson(valname:String,valage:Int)2val(name,age)person(John,20)
kotlinc会为Person类生成get方法和componentN方法,如下是伪代码表示1Person{2Stringname;3Intage;45getName():String{returnname;}6getAge():Int{returnage;}7component1():String{returnname;}8component2():Int{returnage;}9}10解构声明编译为11valnameperson。component121()13valageperson。component2()
可以看到,get和component的逻辑是一样的,所以在编译期,可以进行全局的匹配,用get替换掉component,然后再删除component。toString等生成方法优化
kotlincompiler为dataclass生成的toString具有相似的代码结构,因此可以生成一个辅助方法,然后在所有dataclass的toString方法中调用这个辅助方法,即外联,从而减少指令数量。
equals和hashCode也可以进行类似优化,但是风险相对较高,因此单独为这些优化配置了开关,业务方可以视情况开启。提升压缩率的优化RegAllocPass
DEX及其他文件经过压缩打成APK,如果能通过改变DEX的内容来提升压缩率,那么也会减小最终的包体积。RegAllocPass就是通过重新分配寄存器来提升压缩率的。
dx生成DEX时使用的是线性寄存器分配算法,其基本步骤是进行存活变量分析,然后计算出每个变量的活跃区间,再根据活跃区间依次为变量分配寄存器,超出活跃区间的寄存器可以进行再分配,其优点是运行速度快,但结果往往不是最优的。
比如下面的代码,dx分配了6个寄存器,v0v51publicstaticdoublecalculateLuminance(ColorIntintcolor){2finaldouble〔〕resultgetTempDouble3Array();3colorToXYZ(color,result);4returnresult〔1〕100;5}
相对的,ReDex使用了图着色算法进行寄存器分配,基本步骤是进行存活变量分析,并构建冲突图,冲突图的每个节点是一个变量,如果2个变量可以同时存活,就在两个节点之间建立边,最后为冲突图着色,每个颜色代表一个寄存器,着色完成即寄存器分配完成。着色法相对更慢,结果一般更优。对上面同样的代码,着色法使用了4个寄存器,v0v3。
DEX中的方法使用的寄存器越少,其内容重复率就越高,压缩率也会更大,从而减小了包体积。抖音落地
抖音是字节跳动规模最大、运行环境复杂度最高的应用之一。在ReDex落地初期,由于对复杂度估计不足,在独立灰度和全量灰度期间引起了一些问题,在解决问题的过程中,我们也逐步形成了一套迭代流程以保证优化的稳定性。下面介绍一下我们遇到过的典型问题及当前的迭代流程。遇到的问题兼容性问题
一般来说,只要按照字节码规范进行优化,就不会有兼容性问题,因为dalvikart也是按照规范去校验和运行字节码的,即使进行了错误的优化,引起的问题也应该是共性问题。但很多事都有例外,ReDex就在某品牌手机的部分Android5。x的机型上遇到了问题。
从log和一些hook来看,某品牌手机对5。x的art做了大量的魔改,可以推断其魔改存在一些问题,导致对正确的字节码的校验和运行也可能出现问题。一个可能的原因是:在ReDex进行优化时,会对一些方法体的指令顺序进行重排,这种重排是不影响方法的逻辑的,但是可能会改变一部分指令,魔改后的art在校验这样的方法时可能会报verifyerror,引起crash。
最终通过黑名单配置跳过了这些方法的优化规避了问题,在后续的优化过程中,没有再遇到类似的问题。复杂场景优化问题
抖音业务复杂,代码写法多样,给静态分析和优化增加了一些难度,也更容易遇到问题。下面是2个典型问题:空方法优化问题代码中可能存在一些空方法,排除掉反射和natvie调用等场景后,剩下的空方法应该是可以删除的。但是在做优化时,却遇到了crash,如以下代码1objectXXXSDKHelper{2init{3initXXXSDK()4}5funfakeInit(){6}7}89初始化任务10publicclassXXInitTaskimplementsRunnable{11Override12publicvoidrun(){13XXXSDKHelper。INSTANCE。fakeInit();14}15}
在初始化代码中调用fakeInit,它是一个空方法,调用它的目的是触发XXSDKHelper类加载从而执行init语句块,如果删除了这个空方法,就会导致初始化未执行,在后续的流程中抛空指针。复杂反射问题
对于Class。forname(。。。)等简单的反射用法,静态分析是可以分析出来的,但是对一些经过字符串拼接或者嵌套之后的反射,静态分析很难分析到。因此,对可能会被反射的代码进行优化需要非常小心,通常来说,匿名内部类是不会通过反射调用的,基于此前提,我们进行了匿名内部类的重命名优化,但是在灰度后,发现某些第三方SDK会通过复杂的运行时逻辑对匿名内部类进行了反射调用,最终导致了ClassNotFoundError。
复杂场景的优化问题有些是业务代码不规范造成的,但更多的是优化前提(空方法可以删除匿名内部类不会被反射)不成立所导致,所以在进行优化时首先需要对假设进行谨慎的验证。迭代流程
为了减少稳定性问题,我们总结了ReDexPass的迭代流程。
在对一项Pass有了初步构思后,组内会进行可行性讨论,如果理论上可行就进入开发和验证阶段,之后同步进行至少2轮的独立灰度验证和业务方Pass评审,最后进行全量灰度验证。其中任意一个环节发现问题,都会重新进行整个流程。
通过这个流程,我们大大减少了稳定性问题遗留到灰度阶段的可能,在不断完善迭代流程的同时我们也在探索通过加强单元测试、自动化测试等方式来提升质量。后续规划
ReDex仍然在持续迭代中,未来我们会在以下几个方向继续进行深入探索:更多包体积优化的探索和迭代,同时探索字节码优化在性能提升方面的可能性提升字节码质量更加严格的合法性校验;ReDex之前已经检测出若干自定义插件和proguard的问题,将问题拦截在了编译期,后续会继续提升该能力建立更加完善的质量验证体系;ReDex作为编译期的全局字节码优化方案,如果保证优化后的字节码质量一直是个痛点,我们会继续在单元测试、自动化测试等方向探索质量提升的手段增加编译期监控,更加快速便捷的解决编译期字节码问题,提升接入体验其他应用方向探索;如方法插桩、某些条件下的死代码扫描等。加入我们
字节跳动终端技术团队(ClientInfrastructure)是大前端基础技术的全球化研发团队(分别在北京、上海、杭州、深圳、广州、新加坡和美国山景城设有研发团队),负责整个字节跳动的大前端基础设施建设,提升公司全产品线的性能、稳定性和工程效率;支持的产品包括但不限于抖音、今日头条、西瓜视频、飞书、瓜瓜龙等,在移动端、Web、Desktop等各终端都有深入研究。
就是现在!客户端前端服务端端智能算法测试开发面向全球范围招聘!一起来用技术改变世界,感兴趣请联系fengrui。0bytedance。com,邮件主题简历姓名求职意向期望城市电话。
抖音Android基础技术团队是一个深度追求极致的团队,我们专注于性能、架构、包大小、稳定性、基础库、编译构建等方向的深耕,保障超大规模团队的研发效率和数亿用户的使用体验。目前北京、上海、杭州、深圳都有大量人才需要,欢迎有志之士与我们共同建设亿级用户APP!
可以进入字节跳动招聘官网查询抖音基础技术Android相关职位,或者联系邮件:xiaolin。ganbytedance。com,直接发送简历内推或者咨询相关信息!
每日坚果的营养价值有哪些?吃对坚果让你活力满满众所周知,坚果营养价值高,常吃对身体有益处。对它们,你了解多少呢?我们常把坚果分为木本和草本两大类:木本代表有核桃、榛子、杏仁、松子、腰果、银杏、栗子等;草本代表有花生、……
热门小学写人作文400字四篇无论是在学校还是在社会中,大家都不可避免地会接触到作文吧,作文是人们把记忆中所存储的有关知识、经验和思想用书面形式表达出来的记叙方式。相信许多人会觉得作文很难写吧,以下是小编收……
工业互联网平台赋能制造业数字化转型能力评价体系现有的研究大多从理论分析表明了工业互联网是产业发展的趋势,是制造业数字化转型的重要抓手。但在推进工业互联网平台落地实践过程中还存在着一些局限,制造企业利用工业互联网平台进行数字……
文案所以的负面情绪都是在深夜爆发的我看到操场上有一群奔跑的少年后来我才恍然大悟原来那是我的青春!宫崎骏曾经说过:当陪你坐车的人要下车时,请别有遗憾,他们只是陪你走了能陪你走的路。情jie是情节……
有关西安作文兵马俑兵马俑真不愧是世界奇迹,它展现了我们中华5000年的辉煌也表现了我中华民族的龙马精神。小编收集了有关西安作文:兵马俑,欢迎阅读。第一篇:兵马俑去年暑假,我来到了西安……
我的一天作文范文3篇我的一天作文(一)一天妈妈带我去动物园,一听要去动物园玩,我高兴得蹦蹦跳跳的。一来到动物园,我非要叫妈妈带我看老虎、狮子我先看到包公坐大堂威威赫赫的百兽之王老虎。当……
像水一样的防晒被我找到了!王一博也在用,狂晒不黑最近每天都有小姐妹来问我:该怎么样护理,才能让自己不会被小屁孩喊阿姨?不得不夸你问对人了!要我说,保持容颜,比起往脸上抹大牌护肤品更靠谱的高招当然是防晒!接下……
把抹胸衣穿出高级大牌感,这身材真够辣今天网上冲浪又双叒叕发现了一个宝藏身材博主真正地体现了那句健身话身材好,一件背心也能穿成大牌博主虽然穿的不是背心但能穿抹胸衣外出对身材的要求……
我和对手的故事初中作文在学习、工作、生活中,大家总免不了要接触或使用作文吧,写作文可以锻炼我们的独处习惯,让自己的心静下来,思考自己未来的方向。写起作文来就毫无头绪?下面是小编帮大家整理的我和对手的……
从未见过如此厚颜无耻之人浓眉又开始搞电竞了乔治这也有我欧文谈詹姆斯:我确实觉得向他学习有助于加快我对我们所参与的比赛和商业的理解。我们也进行了许多谈话,我和勒布朗,我们经常聊天。那是我的哥们,向勒布朗致敬詹姆斯:文,答应我,……
22个球队盼绿军夺冠,每队多分96万美元,少帅缺少算计没有安NBA总决赛就要开打,除去勇士和凯尔特人其他NBA28个球队都是搬个板凳看热闹,其中22个球队盼望凯尔特人夺冠,如果凯尔特人夺冠这些球队每个球队将多分196万美元。本赛季……
壮哉!中超扩军后亚洲前三,冠军球队解散,职业联赛没有球队降级还有三天,一推再推的中超联赛终于要开赛了。尽管重庆队在开赛前解散,但是足协迅速安排大连人进行递补,确保2022赛季中超联赛的扩军任务完成,毕竟这是属于领导的丰功伟绩。要知……
领克发布完全体雷神,还有全新四门GT概念车,值得期待领克汽车在6月6日的电音派对现场举行了自家的2022领克春夏发布会,本次不但发布了LynkEMotive智能电混技术,同时还展示了自家全新的四门GT电混动力概念车,采用了全新的……
外婆家的老屋作文如今,外婆一家已住进了高楼大厦,高大的楼房像一个唯物的巨人;然而,我却怀念外婆家的老屋。外婆家的老屋,在一个大院子里,好几户人家,一同在这安详的院子内生活。院子里,……
5月车市大洗牌,比亚迪吉利长安齐进前五,合资车企集体暴跌6月9日下午,乘联会公布了最新一期月度销量数据。数据显示,今年5月份,国内乘用车市场零售销量为135。4万辆,同比下降了16。9,环比增长29。7。15月累计零售效率731。5……
生命的魅力作文眨眼间,又面临着再一个初冬。繁华已落尽,昔日繁华的街上,如今是铺天盖地的雪白。枯树上,闪耀着点点晶莹,凝望着那最后一片树叶的落下,眼中便多了份温存,这是对生命的理解。从无意间发……
早上的养生长寿方式,或许不是喝水和排便?不妨来了解一下众所周知,春天是养生好季节,然而早上是保养身体的黄金时间,早上起床之后的这段时间,被养生达人称为养生的关键阶段,在这个时间段应该做好一些事情,可以使身体变得更加健康。不管……
微信这几个功能很好用,可惜第一个就没几个人知道微信这个我们天天在用的社交软件,我们很熟悉但有的功能却未必知道,比如以下这几个微信的实用功能,很好用也很实用,但知道的人却不多,一起来看看,以后日常生活里会用得着。长按相……
巩晓彬对丁彦雨航的判断是正确的,选择性错误,让伤病毁了他丁彦雨航他的离开是反面的水到渠成,一点点也不觉得震惊反倒是很多小编觉得震惊了,凡事了解这个事情没人觉得意外,高速路集团为了恶心巩晓彬去年故意高价签约了小丁,这才导致了去年的笑话……
全国希望之星英语比赛复赛演讲稿演讲稿具有观点鲜明,内容具有鼓动性的。特点。在不断进步的时代,越来越多人会去使用演讲稿,为了让您在写演讲稿时更加简单方便,下面是小编整理的全国希望之星英语比赛复赛演讲稿,仅供参……
那个缺了牙的嘴的笑容外祖父离开我已经近十年了,我真的想不到十年之后我竟会只记得他那缺了牙的嘴的笑容。在那个艳阳普照的乡下的秋天里,我每天都在向南边张望着,直到有一天的傍晚时分,一个花白胡子、……
困难向我低头五年级作文困难就像弹簧,你强它就弱,你弱它就强。是啊,人在生活中,会遇到许多困难。如果你勇敢地面对它,去战胜它,困难就会逃之夭夭,从而让你领略到战胜困难的喜悦。我就有幸体会到了这样……
廉美贞萌度爆表!金智媛顶平刘海扎马尾现身,粉丝融化太可爱韩国人气女演员金智媛近期主演《我的出走日记》,将职场社畜心声演绎地淋漓尽致,打破观众对于她过往甜美可爱的印象,知名度更上一层楼。随着戏剧播毕,她7日受邀出席服装品牌活动,焕然一……
短裤搭配什么上衣好看的图片短裤搭配什么上衣好看的图片?6月正式进入夏季,天气也变得越来越热,穿短裤的季节来了。短裤是夏季出镜率非常高的一款单品,不仅清凉舒适而且还很容易搭配,轻轻松松就能穿出显高显瘦又时……