Android App包瘦身优化实践 android包大小优化
suiw9 2024-10-29 16:44 18 浏览 0 评论
随着业务的快速迭代增长,美团App里不断引入新的业务逻辑代码、图片资源和第三方SDK,直接导致APK体积不断增长。包体积增长带来的问题越来越多,如CDN流量费用增加、用户安装成功率降低,甚至可能会影响用户的留存率。APK的瘦身已经是不得不考虑的事情。在尝试瘦身的过程中,我们借鉴了很多业界其他公司提供的方案,同时也针对自身特点,发现了一些新的技巧。本文将对其中的一些做详细介绍。
在开始讲瘦身技巧之前,先来讲一下APK的构成。
APK的构成
可以用Zip工具打开APK查看。比如,美团App 7.8.6的线上版本的格式是这样的:
可以看到APK由以下主要部分组成:
文件/目录 | 描述 |
---|---|
lib/ | 存放so文件,可能会有armeabi、armeabi-v7a、arm64-v8a、x86、x86_64、mips,大多数情况下只需要支持armabi与x86的架构即可,如果非必需,可以考虑拿掉x86的部分 |
res/ | 存放编译后的资源文件,例如:drawable、layout等等 |
assets/ | 应用程序的资源,应用程序可以使用AssetManager来检索该资源 |
META-INF/ | 该文件夹一般存放于已经签名的APK中,它包含了APK中所有文件的签名摘要等信息 |
classes(n).dex | classes文件是Java Class,被DEX编译后可供Dalvik/ART虚拟机所理解的文件格式 |
resources.arsc | 编译后的二进制资源文件 |
AndroidManifest.xml | Android的清单文件,格式为AXML,用于描述应用程序的名称、版本、所需权限、注册的四大组件 |
当然还会有一些其它的文件,例如上图中的org/
、src/
、push_version
等文件或文件夹。这些资源是Java Resources,感兴趣的可以结合编译工作流中的流程图以及MergeJavaResourcesTransform的源码看看被打入APK包中的资源都有哪些,这里不做过多介绍。
在充分了解了APK各个组成部分以及它们的作用后,我们针对自身特点进行了分析和优化。下面将从Zip文件格式、classes.dex、资源文件、resources.arsc等方面来介绍下我们发现的部分优化技巧。
Zip格式优化
前面介绍了APK的文件格式以及主要组成部分,通过aapt l -v xxx.apk
或unzip -l xxx.apk
来查看APK文件时会得到以下信息,见下面截图:
通过上图可以看到APK中很多资源是以Stored
来存储的,根据Zip的文件格式中对压缩方式的描述Compression_methods可以看出这些文件是没有压缩的,那为什么它们没有被压缩呢?从AAPT的源码中找到以下描述:
/* these formats are already compressed, or don't compress well */static const char* kNoCompressExt[] = { ".jpg", ".jpeg", ".png", ".gif", ".wav", ".mp2", ".mp3", ".ogg", ".aac", ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet", ".rtttl", ".imy", ".xmf", ".mp4", ".m4a", ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2", ".amr", ".awb", ".wma", ".wmv", ".webm", ".mkv"};
可以看出AAPT在资源处理时对这些文件后缀类型的资源是不做压缩的,那是不是可以修改它们的压缩方式从而达到瘦身的效果呢?
在介绍怎么做之前,先来大概介绍一下App的资源是怎么被打进APK包里的。Android构建工具链使用AAPT工具来对资源进行处理,来看下图(图片来源于Build Workflow):
点击图片查看大图
通过上图可以看到Manifest
、Resources
、Assets
的资源经过AAPT
处理后生成R.java
、Proguard Configuration
、Compiled Resources
。其中R.java
大家都比较熟悉,这里就不过多介绍了。我们来重点看看Proguard Configuration
、Compiled Resources
都是做什么的呢?
Proguard Configuration
是AAPT工具为Manifest
中声明的四大组件以及布局文件中(XML layouts
)使用的各种Views所生成的ProGuard配置,该文件通常存放在${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/proguard-rules/${flavorName}/${buildType}/aapt_rules.txt
,下面是项目中该文件的截图,红框标记出来的就是对AndroidManifest.xml
、XML Layouts
中相关Class的ProGuard配置。
Compiled Resources
是一个Zip格式的文件,这个文件的路径通常为${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/res/resources-${flavorName}-${buildType}-stripped.ap_
。 通过下面经过Zip解压后的截图,可以看出这个文件包含了res
、AndroidManifest.xml
和resources.arsc
的文件或文件夹。结合Build Workflow中的描述,可以看出这个文件(resources-${flavorName}-${buildType}-stripped.ap_
)会被apkbuilder
打包到APK包中,它其实就是APK的“资源包”(res
、AndroidManifest.xml
和resources.arsc
)。
我们就是通过这个文件来修改不同后缀文件资源的压缩方式来达到瘦身效果的,而在后面“resources.arsc的优化”一节中也是操作的这个文件。
笔者在自己的项目中是通过在package${flavorName}
Task(感兴趣的同学可以查看源码)之前进行这个操作的。
下面是部分代码片段:
appPlugin.variantManager.variantDataList.each { variantData ->
当然也可以在其它构建步骤中采用更高压缩率的方式来达到瘦身效果,例如采用7Zip压缩等等。
本技巧的使用需要注意以下问题:
如果音视频资源被压缩存放在APK中的话,在使用一些音频、视频API时尤其要注意,需要做好充分的测试。
resources.arsc文件最好不要压缩存储,如果压缩会影响一定的性能(尤其是冷启动时间)。
如果想在
Android 6.0
上开启android:extractNativeLibs=”false”
的话,.so 文件也不能被压缩,android:extractNativeLibs
的使用姿势看这里:App Manifest --- application。
classes.dex的优化
如何优化classes.dex的大小呢?大体有如下套路:
时刻保持良好的编程习惯和对包体积敏锐的嗅觉,去除重复或者不用的代码,慎用第三方库,选用体积小的第三方SDK等等。
开启ProGuard来进行代码压缩,通过使用ProGuard来对代码进行混淆、优化、压缩等工作。
针对第一种套路,因各个公司的项目的差异,共性的东西较少,需要case by case的分析,这里不做过多的介绍。
压缩代码
可以通过开启ProGuard来实现代码压缩,可以在build.gradle文件相应的构建类型中添加minifyEnabled true
。
请注意,代码压缩会拖慢构建速度,因此应该尽可能避免在调试构建中使用。不过一定要为用于测试的最终APK启用代码压缩,如果不能充分地自定义要保留的代码,可能会引入错误。
例如,下面这段来自build.gradle文件的代码用于为发布构建启用代码压缩:
android {
除了minifyEnabled
属性外,还有用于定义ProGuard规则的proguardFiles属性:
getDefaultProguardFile(‘proguard-android.txt')
是从Android SDKtools/proguard/
文件夹获取默认ProGuard设置。proguard-rules.pro
文件用于添加自定义ProGuard规则。默认情况下,该文件位于模块根目录(build.gradle文件旁)。
提示:要想做进一步的代码压缩,可尝试使用位于同一位置的
proguard-android-optimize.txt
文件。它包括相同的ProGuard规则,但还包括其他在字节码一级(方法内和方法间)执行分析的优化,以进一步减小APK大小和帮助提高其运行速度。在Gradle Plugin 2.2.0及以上版本ProGuard的配置文件会自动解压缩到
${rootProject.buildDir}/${AndroidProject.FD_INTERMEDIATES}/proguard-files/
目录下,proguardFiles
会从这个目录来获取ProGuard配置。
每次执行完ProGuard之后,ProGuard都会在${project.buildDir}/outputs/mapping/${flavorDir}/
生成以下文件:
文件名 | 描述 |
---|---|
dump.txt | APK中所有类文件的内部结构 |
mapping.txt | 提供原始与混淆过的类、方法和字段名称之间的转换,可以通过proguard.obfuscate.MappingReader 来解析 |
seeds.txt | 列出未进行混淆的类和成员 |
usage.txt | 列出从APK移除的代码 |
可以通过在usage.txt
文件中看到哪些代码被删除了,如下图中所示android.support.multidex.MultiDex
已经被删除了:
R Field的优化
除了对项目代码优化和开启代码压缩之外,笔者在《美团Android DEX自动拆包及动态加载简介》这篇文章中提到了通过内联R Field来解决R Field过多导致MultiDex 65536的问题,而这一步骤对代码瘦身能够起到明显的效果。下面是笔者通过字节码工具在构建流程中内联R Field的代码片段(字节码的修改可以使用Javassist或者ASM,该步骤笔者采用的是Javassist)。
ctBehaviors.each { CtBehavior ctBehavior -> if (!ctBehavior.isEmpty()) { try {
其它优化手段
针对代码的瘦身还有很多优化的技巧,例如:
减少ENUM的使用(详情可以参考:Remove Enumerations),每减少一个ENUM可以减少大约1.0到1.4 KB的大小;
通过pmd cpd来检查重复的代码从而进行代码优化;
移除掉所有无用或者功能重复的依赖库。
这些优化技巧就不展开介绍了。
资源的优化
图片优化
为了支持Android设备DPI的多样化([l|m|tv|h|x|xx|xxx]dpi)以及用户对高质量UI的期待,美团App中使用了大量的图片,在Android下支持很多格式的图片,例如:PNG、JPG 、WebP,那我们该怎么选择不同类型的图片格式呢? 在Google I/O 2016
中提到了针对图片格式的选择,来看下图(图片来源于Image compression for Android developers):
通过上图可以看出一个大概图片格式选择的方法。如果能用VectorDrawable
来表示的话优先使用VectorDrawable,如果支持WebP
则优先用WebP,而PNG
主要用在展示透明或者简单的图片,而其它场景可以使用JPG
格式。针对每种图片格式也有各类的优化手段和优化工具。
使用矢量图片
可以使用矢量图形来创建独立于分辨率的图标和其他可伸缩图片。使用矢量图片能够有效的减少App中图片所占用的大小,矢量图形在Android中表示为VectorDrawable对象。 使用VectorDrawable对象,100字节的文件可以生成屏幕大小的清晰图像,但系统渲染每个VectorDrawable对象需要大量的时间,较大的图像需要更长的时间才能出现在屏幕上。 因此只有在显示小图像时才考虑使用矢量图形。有关使用VectorDrawable的更多信息,请参阅 Working with Drawables。
使用WebP
如果App的minSdkVersion
高于14(Android 4.0+
)的话,可以选用WebP格式,因为WebP在同画质下体积更小(WebP支持透明度,压缩比比JPEG更高但显示效果却不输于JPEG,官方评测quality参数等于75均衡最佳), 可以通过PNG到WebP转换工具来进行转换。当然Android从4.0才开始WebP的原生支持,但是不支持包含透明度,直到Android 4.2.1+
才支持显示含透明度的WebP,在笔者使用中是判断当前App的minSdkVersion
以及图片文件的类型(是否为透明)来选用是否适用WebP。见下面的代码片段:
boolean isPNGWebpConvertSupported() { if (!isWebpConvertEnable()) { return false
选择更优的压缩工具
可以使用pngcrush、pngquant或zopflipng等压缩工具来减少PNG文件大小,而不会丢失图像质量。所有这些工具都可以减少PNG文件大小,同时保持图像质量。
pngcrush工具特别有效:此工具在PNG过滤器和zlib(Deflate)参数上迭代,使用过滤器和参数的每个组合来压缩图像。然后选择产生最小压缩输出的配置。
对于JPEG文件,你可以使用packJPG或guetzli等工具将JPEG文件压缩的更小,这些工具能够在保持图片质量不变的情况下,把图片文件压缩的更小。guetzli工具更是能够在图片质量不变的情况下,将文件大小降低35%。
在Android构建流程中AAPT会使用内置的压缩算法来优化res/drawable/
目录下的PNG图片,但也可能会导致本来已经优化过的图片体积变大,可以通过在build.gradle
中设置cruncherEnabled
来禁止AAPT来优化PNG图片。
aaptOptions {
开启资源压缩
Android的编译工具链中提供了一款资源压缩的工具,可以通过该工具来压缩资源,如果要启用资源压缩,可以在build.gradle文件中将shrinkResources true
。例如:
android {
需要注意的是目前资源压缩器目前不会移除values/文件夹中定义的资源(例如字符串、尺寸、样式和颜色),有关详情,请参阅问题 70869。
Android构建工具是通过ResourceUsageAnalyzer来检查哪些资源是无用的,当检查到无用的资源时会把该资源替换成预定义的版本。详看下面代码片段(摘自com.android.build.gradle.tasks.ResourceUsageAnalyzer
):
public class ResourceUsageAnalyzer {
上面截图中3个byte数组的定义就是资源压缩工具为无用资源提供的预定义版本,可以看出对.png
提供了TINY_PNG
, 对.9.png
提供了TINY_9PNG
以及对.xml
提供了TINY_XML
的预定义版本。
资源压缩工具的详细使用可以参考Shrink Your Code and Resources。资源压缩工具默认是采用安全压缩模式来运行,可以通过开启严格压缩模式来达到更好的瘦身效果。
如果想知道哪些资源是无用的,可以通过资源压缩工具的输出日志文件${project.buildDir}/outputs/mapping/release/resources.txt
来查看。如下图所示res/layout/abc_activity_chooser_viewer.xml
就是无用的,然后被预定义的版本TINY_XML
所替换:
资源压缩工具只是把无用资源替换成预定义较小的版本,那我们如何删除这些无用资源呢?通常的做法是结合资源压缩工具的输出日志,找到这些资源并把它们进行删除。但在笔者的项目中很多无用资源是被其它组件或第三方SDK所引入的,如果采用这种优化方式会带来这些SDK后期维护成本的增加,针对这种情况笔者是通过采用在resources.arsc中做优化来解决的,详情看下面“resources.arsc的优化”一节的介绍。
语言资源优化
根据App自身支持的语言版本选用合适的语言资源,例如使用了AppCompat,如果不做任何配置的话,最终APK包中会包含AppCompat中消息的所有已翻译语言字符串,无论应用的其余部分是否翻译为同一语言,可以通过resConfig
来配置使用哪些语言,从而让构建工具移除指定语言之外的所有资源。下图是具体的配置示例:
android {
针对为不同DPI所提供的图片也可以采用相同的策略,需要针对自身的目标用户和目标设备做一定的选择,可以参考Support Only Specific Densities来操作。有关屏幕密度的详细信息,请参阅Screen Sizes and Densities。
对
.so
文件也可以采用类似的策略,比如笔者的项目中只保留了armeabi
版本的.so
文件。
resources.arsc的优化
针对resources.arsc
,笔者尝试过的优化手段如下:
开启资源混淆;
对重复的资源进行优化;
对被
shrinkResources
优化掉的资源进行处理。
下面将分别对这些优化手段进行展开介绍。
资源混淆
在笔者另一篇《美团Android资源混淆保护实践》文章中介绍了采用对资源混淆的方式来保护资源的安全,同时也提到了这种方式有显著的瘦身效果。笔者当时是采用修改AAPT的相关源码的方式,这种方式的痛点是每次升级Build Tools
都要修改一次AAPT源码,维护性较差。目前笔者采用了微信开源的资源混淆库AndResGuard,具体的原理和使用帮助可以参考安装包立减1M--微信Android资源混淆打包工具。
无用资源优化
在上一节中介绍了可以通过shrinkResources true
来开启资源压缩,资源压缩工具会把无用的资源替换成预定义的版本而不是移除,如果采用人工移除的方式会带来后期的维护成本,这里笔者采用了一种比较取巧的方式,在Android构建工具执行package${flavorName}
Task之前通过修改Compiled Resources
来实现自动去除无用资源。
具体流程如下:
收集资源包(
Compiled Resources
的简称)中被替换的预定义版本的资源名称,通过查看资源包(Zip格式)中每个ZipEntry
的CRC-32 checksum
来寻找被替换的预定义资源,预定义资源的CRC-32
定义在ResourceUsageAnalyzer,下面是它们的定义。
// A 1x1 pixel PNG of type BufferedImage.TYPE_BYTE_GRAY
通过android-chunk-utils把
resources.arsc
中对应的定义移除;删除资源包中对应的资源文件。
重复资源优化
目前美团App是由各个业务团队共同开发完成,为了方便各业务团队的独立开发,美团App进行了平台化改造。改造时存在很多资源文件(如:drawable、layout等)被不同的业务团队都拷贝到自己的Library下,同时为了避免引发资源覆盖的问题,每个业务团队都会为自己的资源文件名添加前缀。这样就导致了这些资源文件虽然内容相同,但因为名称的不同而不能被覆盖,最终都会被集成到APK包中,针对这种问题笔者采用了和前面“无用资源优化”一节中描述类似的策略。
具体步骤如下:
通过资源包中的每个
ZipEntry
的CRC-32 checksum
来筛选出重复的资源;通过android-chunk-utils修改
resources.arsc
,把这些重复的资源都重定向
到同一个文件上;把其它重复的资源文件从资源包中删除。
代码片段:
variantData.outputs.each { def apFile = it.packageAndroidArtifactTask.getResourceFile();
通过这种方式可以有效减少重复资源对包体大小的影响,同时这种操作方式对各业务团队透明,也不会增加协调相同资源如何被不同业务团队复用的成本。
总结
上述就是我们目前在APK瘦身方面的做的一些尝试和积累,可以根据自身情况取舍使用。当然我们还可以采取一些按需加载的策略来减少安装包的体积。最后提一点,砍掉不必要的功能才是安装包瘦身的超级大招。一个好的App的标准有很多方面,但提供尽可能小的安装包是其中一个重要的方面,这也是对我们Android开发者人员自身的提出的基本要求,要时刻保持良好的编程习惯和对包体积敏锐的嗅觉。
参考文献
Android application package (APK)
Zip (file format))
Build Workflow
Android AAPT Source Code
Reduce APK Size
Shrink Your Code and Resources
Manage Your App's Memory
Vector Drawable
Javassist
ASM
pngcrush
pngquant
zopflipng
android-chunk-utils
安装包立减1M--微信Android资源混淆打包工具
减少 APK 的大小,Android 官方这样说
Google I/O 2016 笔记:APK 瘦身的正确姿势
作者简介
建帅,Android技术专家,2015年3月加入美团点评,目前就职于到店餐饮技术部信息与交易技术中心。
到店餐饮技术部交易与信息技术中心,负责美团点评美食用户端业务,服务于数以亿计用户,通过更好的榜单、真实的评价和完善的信息为用户提供更好的决策支持,致力于提升用户体验;同时承载所有餐饮商户端线上流量,为餐饮商户提供多种营销工具,提升餐饮商户营销效率,最终达到让国人“Eat Better、Live Better”的美好愿景!我们的团队包含且不限于Android、iOS、FE、Java、PHP等技术方向,已完备覆盖前后端技术栈。只要你来,就能点亮全栈开发技能树。
不想错过技术博客更新?想给文章评论、和作者互动?第一时间获取技术沙龙信息?
请关注我们的官方微信公众号“美团点评技术团队”。
相关推荐
- 5款Syslog集中系统日志常用工具对比推荐
-
一、为何要集中管理Syslog?Syslog由Linux/Unix系统及其他网络设备生成,广泛分布于整个网络。因其包含关键信息,可用于识别网络中的恶意活动,所以必须对其进行持续监控。将Sys...
- 跨平台、多数据库支持的开源数据库管理工具——DBeaver
-
简介今天给大家推荐一个开源的数据库管理工具——DBeaver。它支持多种数据库系统,包括Mysql、Oracle、PostgreSQL、SLQLite、SQLServer等。DBeaver的界面友好...
- 强烈推荐!数据库管理工具:Navicat Premium 16.3.2 (64位)
-
NavicatPremium,一款集数据迁移、数据库管理、SQL/查询编辑、智能设计、高效协作于一体的全能数据库开发工具。无论你是MySQL、MariaDB、MongoDB、SQLServer、O...
- 3 年 Java 程序员还玩不转 MongoDB,网友:失望
-
一、什么场景使用MongoDB?...
- 拯救MongoDB管理员的GUI工具大赏:从菜鸟到极客的生存指南
-
作为一名在NoSQL丛林中披荆斩棘的数据猎人,没有比GUI工具更称手的瑞士军刀了。本文将带你围观五款主流MongoDB管理神器的特性与暗坑,附赠精准到扎心的吐槽指南一、MongoDBCompass:...
- mongodb/redis/neo4j 如何自己打造一个 web 数据库可视化客户端?
-
前言最近在做neo4j相关的同步处理,因为产线的可视化工具短暂不可用,发现写起来各种脚本非常麻烦。...
- solidworks使用心得,纯干货!建议大家收藏
-
SolidWorks常见问题...
- 统一规约-关乎数字化的真正实现(规范统一性)
-
尽管数字化转型的浪潮如此深入人心,但是,对于OPCUA和TSN的了解却又甚少,这难免让人质疑其可实现性,因为,如果缺乏统一的语义互操作规范,以及更为具有广泛适用的网络与通信,则数字化实际上几乎难以具...
- Elasticsearch节点角色配置详解(Node)
-
本篇文章将介绍如下内容:节点角色简介...
- 产前母婴用品分享 篇一:我的母婴购物清单及单品推荐
-
作者:DaisyH8746在张大妈上已经混迹很久了,有事没事看看“什么值得买”已渐渐成了一种生活习惯,然而却从来没有想过自己要写篇文章发布上来,直到由于我产前功课做得“太过认真”(认真到都有点过了,...
- 比任何人都光彩照人的假期!水润、紧致的肌肤护理程序
-
图片来源:谜尚愉快的假期临近了。身心振奋的休假季节。但是不能因为这种心情而失去珍贵的东西,那就是皮肤健康。炙热的阳光和强烈的紫外线是使我们皮肤老化的主犯。因此,如果怀着快乐的心情对皮肤置之不理,就会使...
- Arm发布Armv9边缘AI计算平台,支持运行超10亿参数端侧AI模型
-
中关村在线2月27日消息,Arm正式发布Armv9边缘人工智能(AI)计算平台。据悉,该平台以全新的ArmCortex-A320CPU和领先的边缘AI加速器ArmEthos-U85NPU为核心...
- 柔性——面向大规模定制生产的数字化实现的基本特征
-
大规模定制生产模式的核心是柔性,尤其是体现在其对定制的要求方面。既然是定制,并且是大规模的定制,对于制造系统的柔性以及借助于数字化手段实现的柔性,就提出了更高的要求。面向大规模定制生产的数字化业务管控...
- 创建PLC内部标准——企业前进的道路
-
作者:FrankBurger...
- 标准化编程之 ----------- 西门子LPMLV30测试总结
-
PackML乃是由OMAC开发且被ISA所采用的自动化标准TR88.00.02,能够更为便捷地传输与检索一致的机器数据。PackML的主要宗旨在于于整个工厂车间倡导通用的“外观和感觉”,...
你 发表评论:
欢迎- 一周热门
-
-
Linux:Ubuntu22.04上安装python3.11,简单易上手
-
宝马阿布达比分公司推出独特M4升级套件,整套升级约在20万
-
MATLAB中图片保存的五种方法(一)(matlab中保存图片命令)
-
别再傻傻搞不清楚Workstation Player和Workstation Pro的区别了
-
Linux上使用tinyproxy快速搭建HTTP/HTTPS代理器
-
如何提取、修改、强刷A卡bios a卡刷bios工具
-
Element Plus 的 Dialog 组件实现点击遮罩层不关闭对话框
-
日本组合“岚”将于2020年12月31日停止团体活动
-
SpringCloud OpenFeign 使用 okhttp 发送 HTTP 请求与 HTTP/2 探索
-
tinymce 号称富文本编辑器世界第一,大家同意么?
-
- 最近发表
-
- 5款Syslog集中系统日志常用工具对比推荐
- 跨平台、多数据库支持的开源数据库管理工具——DBeaver
- 强烈推荐!数据库管理工具:Navicat Premium 16.3.2 (64位)
- 3 年 Java 程序员还玩不转 MongoDB,网友:失望
- 拯救MongoDB管理员的GUI工具大赏:从菜鸟到极客的生存指南
- mongodb/redis/neo4j 如何自己打造一个 web 数据库可视化客户端?
- solidworks使用心得,纯干货!建议大家收藏
- 统一规约-关乎数字化的真正实现(规范统一性)
- Elasticsearch节点角色配置详解(Node)
- 产前母婴用品分享 篇一:我的母婴购物清单及单品推荐
- 标签列表
-
- dialog.js (57)
- importnew (44)
- windows93网页版 (44)
- yii2框架的优缺点 (45)
- tinyeditor (45)
- qt5.5 (60)
- windowsserver2016镜像下载 (52)
- okhttputils (51)
- android-gif-drawable (53)
- 时间轴插件 (56)
- docker systemd (65)
- slider.js (47)
- android webview缓存 (46)
- pagination.js (59)
- loadjs (62)
- openssl1.0.2 (48)
- velocity模板引擎 (48)
- pcre library (47)
- zabbix微信报警脚本 (63)
- jnetpcap (49)
- pdfrenderer (43)
- fastutil (48)
- uinavigationcontroller (53)
- bitbucket.org (44)
- python websocket-client (47)