一、命名规范
代码规范先从命名规范开始,Android的命名规范主要涉及:Java源代码,xml文件,图片资源。
a)包名
①:Android的包名的命名规则一般采用域名反转作为包名,例:com.dagong
②:包名使用.作为分隔符,后面可以根据机构名称或者功能进行命名
例:后面可以采用的区分包名方式:
按照模块com.dagong.project.user
按照层级区分com.dagong.project.activity
层级下也可以在区分模块com.dagong.project.activity.user
b) 类和接口
①:类名是一个或多个单词组成,采用大驼峰命名(LocationManage)
②:遵循见名知意原则,易于记忆和联想
③:如果该类继承了Android的组件,需要使用该组件的名称为后缀(LoginActivity)
④:接口一般使用I为开头,遵循大驼峰命名法(IPullToRefresh)
c) 变量
Android变量分为成员变量,静态变量和常量
①:成员变量采用小驼峰命名规则
②:静态变量一般采用小写的s开头,后面单词的命名规则和成员变量保持一致
③:常量命名规则一般是所有的单词都是大写,中间使用_(下划线)分割(BASE_URL)
d) 方法
①:方法命名规则采用小驼峰命名法(onCreate())
②:一般使用的方法名前缀
getXX()返回某个值的方法
initXX()初始化相关方法,比如初始化布局:initView()
checkXX()和isXX()方法为boolean值的时候使用is或者check为前缀
saveXX()保存数据
clearXX()和removeXX()清除数据
updateXX()更新数据
processXX()对数据进行处理
dispalyXX()显示某某信息
draswXX()绘制数据或者效果
③:方法的参数尽可能不超过4个,需要更多的参数的时候可以采用class的方法
④:参数中尽量少使用boolean,使用boolean传参不利于代码的阅读
⑤:方法尽量不超过15行注意代码复用与解耦
⑥:如果一个方法返回的是一个错误码,可以使用异常
⑦:不使用try catch处理业务逻辑
⑧:尽可能不实用null,替代为异常或者使用空的变量,比如Collections.emptyList()
e) Layout
①:Layout的命名规则需要和使用他们的组件对应
UserInfoActivity对应的activity_user_info.xml
②:对应Andorid组件的Layout命名规则:
Activity -> activity_user_info.xml
Fragment -> fragment_sign_up.xml
Dialog -> dialog_change_password.xml
AdapterView Item -> item_user.xml
f) string和color
①:项目中使用的string,和color的值原则上都是必须放在strings.xml和colors.xml
②:建议color的命名中体现其ARGB值,比如:#ff0000
g) id命名
①:layout中使用的id的单词要全部小写,单词之间使用下划线分割
②:id命名的第一个单词使用View的缩写
h) Drawable命名
①:根据使用的控件来命名,控件的缩写在前面,中间使用使用_下划线分割
例:Action bar使用ab_,比如:ab_stacked.png
Button使用btn_
Dialgo使用dialog_
Divide使用divider_
Icon使用ic_
Menu使用menu_
Notification使用notification_
Tabs使用tab_
②:Drawable是有多个状态的,在命名中体现出状态的不同:
Normal对应_normal结尾,比如btn_order_normal.9.png
Pressed对应_pressed结尾
Focused对应_focused结尾
Disabled对应_disabled结尾
Selected对应_selected结尾
二、目录优化
a)包名结构
丨-animation....................................................动画相关的文件夹
丨-api...............................................................网络加载接口及网址的配置
丨-base............................................................各种基类
丨-bean...........................................................实体类
丨-db...............................................................数据库
丨-net..............................................................网络加载及网络拦截相关
丨-service........................................................服务相关
丨-utils............................................................各种工具类
丨-view............................................................视图的集合
丨-activity
丨-adapter
丨-fragment
丨-impl
丨-weight..........................................................自定义View
三、布局优化
a)布局文件的层级优化
①:可以通过提取公共的布局文件来实现复用
②:标签是作为标签的一种辅助扩展来使用的,主要作用是为了防止在引用布局文件时产生多余的布局嵌套。
③:是一个轻量级的View,占用资源非常小的控件,可以用来控制View的显示和隐藏
b)布局文件的注意事项
①:直接在java代码中写布局要比在xml中写的布局文件快,因为省去了解析xml的过程,但是有时候条件限制,需要通过加载xml来加载布局,比如我们的activity通过setContentView来显示界面
②:布局中不要嵌套层过多,嵌套层次过深的话,容易导致栈溢出。android中对栈的大小限制只有几k,所以我们要提高栈的使用率。多一层的嵌套布局加载,就要多耗费很长的加载时间。
③:不要过多使用LinearLayout的layout_weight属性,这个属性会减慢measure的速度
④:layout文件可以通过Hierarchy Viewer来查看存在的问题,我们可以查看我们layout每一部分计算,布局,渲染所需要的时间。尽量使layout扁平化,深度最好不要超过三层。
⑤:在android的官方文档中,推荐使用RelativeLayout和GridLayout来解决Layout深度过深的问题。
c)使用shape来避免一些规则统一,样式简单的图片使用
四、代码优化
a):合理管理内存
①:节制的使用Service
如果应用程序需要使用Service来执行后台任务的话,只有当任务正在执行的时候才应该让Service运行起来。当启动一个Service时,系统会倾向于将这个Service所依赖的进程进行保留,系统可以在LRUcache当中缓存的进程数量也会减少,导致切换程序的时候耗费更多性能。我们可以使用IntentService,当后台任务执行结束后会自动停止,避免了Service的内存泄漏。
②:当界面不可见时释放内存
当用户打开了另外一个程序,我们的程序界面已经不可见的时候,我们应当将所有和界面相关的资源进行释放。重写Activity的onTrimMemory()方法,然后在这个方法中监听TRIM_MEMORY_UI_HIDDEN这个级别,一旦触发说明用户离开了程序,此时就可以进行资源释放操作了。
③:当内存紧张时释放内存
onTrimMemory()方法还有很多种其他类型的回调,可以在手机内存降低的时候通知我们,我们应该根据回调中传入的级别来去决定如何释放应用程序的资源。
④:避免在Bitmap上浪费内存
读取一个Bitmap图片的时候,千万不要去加载不需要的分辨率。可以压缩图片等操作。
⑤:使用优化过的数据集合
Android提供了一系列优化过后的数据集合工具类,如SparseArray、SparseBooleanArray、LongSparseArray,使用这些API可以让我们的程序更加高效。HashMap工具类会相对比较低效,因为它需要为每一个键值对都提供一个对象入口,而SparseArray就避免掉了基本数据类型转换成对象数据类型的时间。
⑥:知晓内存的开支情况
使用枚举通常会比使用静态常量消耗两倍以上的内存,尽可能不使用枚举
任何一个Java类,包括匿名类、内部类,都要占用大概500字节的内存空间
任何一个类的实例要消耗12-16字节的内存开支,因此频繁创建实例也是会在一定程序上影响内存的
使用HashMap时,即使你只设置了一个基本数据类型的键,比如说int,但是也会按照对象的大小来分配内存,大概是32字节,而不是4字节,因此最好使用优化后的数据集合
⑦:谨慎使用抽象编程
在Android使用抽象编程会带来额外的内存开支,因为抽象的编程方法需要编写额外的代码,虽然这些代码根本执行不到,但是也要映射到内存中,不仅占用了更多的内存,在执行效率上也会有所降低。所以需要合理的使用抽象编程。
⑧:尽量避免使用依赖注入框架
使用依赖注入框架貌似看上去把findViewById()这一类的繁琐操作去掉了,但是这些框架为了要搜寻代码中的注解,通常都需要经历较长的初始化过程,并且将一些你用不到的对象也一并加载到内存中。这些用不到的对象会一直站用着内存空间,可能很久之后才会得到释放,所以可能多敲几行代码是更好的选择。
⑨:使用多个进程
谨慎使用,多数应用程序不该在多个进程中运行的,一旦使用不当,它甚至会增加额外的内存而不是帮我们节省内存。这个技巧比较适用于哪些需要在后台去完成一项独立的任务,和前台是完全可以区分开的场景。比如音乐播放,关闭软件,已经完全由Service来控制音乐播放了,系统仍然会将许多UI方面的内存进行保留。在这种场景下就非常适合使用两个进程,一个用于UI展示,另一个用于在后台持续的播放音乐。关于实现多进程,只需要在Manifast文件的应用程序组件声明一个android:process属性就可以了。进程名可以自定义,但是之前要加个冒号,表示该进程是一个当前应用程序的私有进程。
b)分析内存的使用情况
①:系统不可能将所有的内存都分配给我们的应用程序,每个程序都会有可使用的内存上限,被称为堆大小。不同的手机堆大小不同,如下代码可以获得堆大小:
ActivityManager manager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
int heapSize = manager.getMemoryClass();
结果以MB为单位进行返回,我们开发时应用程序的内存不能超过这个限制,否则会出现OOM。
②:Android的GC操作
Android系统会在适当的时机触发GC操作,一旦进行GC操作,就会将一些不再使用的对象进行回收。GC操作会从一个叫做Roots的对象开始检查,所有它可以访问到的对象就说明还在使用当中,应该进行保留,而其他的对系那个就表示已经不再被使用了。
③:Android中内存泄漏
Android中的垃圾回收机制并不能防止内存泄漏的出现导致内存泄漏最主要的原因就是某些长存对象持有了一些其它应该被回收的对象的引用,导致垃圾回收器无法去回收掉这些对象,也就是出现内存泄漏了。比如说像Activity这样的系统组件,它又会包含很多的控件甚至是图片,如果它无法被垃圾回收器回收掉的话,那就算是比较严重的内存泄漏情况了。 举个例子,在MainActivity中定义一个内部类,实例化内部类对象,在内部类新建一个线程执行死循环,会导致内部类资源无法释放,MainActivity的控件和资源无法释放,导致OOM,可借助一系列工具,比如LeakCanary。
c)高性能编码优化
①:避免创建不必要的对象
a.不必要的对象我们应该避免创建
b.如果有需要拼接的字符串,那么可以优先考虑使用StringBuffer或者StringBuilder来进行拼接
c.在没有特殊原因的情况下,尽量使用基本数据类型来代替封装数据类型,int比Integer要更加有效,其它数据类型也是一样。
d.当一个方法的返回值是String的时候,通常需要去判断一下这个String的作用是什么,如果明确知道调用方会将返回的String再进行拼接操作的话,可以考虑返回一个StringBuffer对象来代替,因为这样可以将一个对象的引用进行返回,而返回String的话就是创建了一个短生命周期的临时对象。
e.基本数据类型的数组也要优于对象数据类型的数组。另外两个平行的数组要比一个封装好的对象数组更加高效,举个例子,Foo[]和Bar[]这样的数组,使用起来要比Custom(Foo,Bar)[]这样的一个数组高效的多。
f.尽可能地少创建临时对象,越少的对象意味着越少的GC操作。
②:静态优于抽象
如果你并不需要访问一个对系那个中的某些字段,只是想调用它的某些方法来去完成一项通用的功能,那么可以将这个方法设置成静态方法,调用速度提升15%-20%,同时也不用为了调用这个方法去专门创建对象了,也不用担心调用这个方法后是否会改变对象的状态(静态方法无法访问非静态字段)。
这种优化方式只对基本数据类型以及String类型的常量有效,对于其他数据类型的常量是无效的。
③:使用增强型for循环语法
ArrayList手写的循环比增强型for循环更快,其他的集合没有这种情况。因此默认情况下使用增强型for循环,而遍历ArrayList使用传统的循环方式。
④:多使用系统封装好的API
系统提供不了的Api完成不了我们需要的功能才应该自己去写,因为使用系统的Api很多时候比我们自己写的代码要快得多,它们的很多功能都是通过底层的汇编模式执行的。 举个例子,实现数组拷贝的功能,使用循环的方式来对数组中的每一个元素一一进行赋值当然可行,但是直接使用系统中提供的System.arraycopy()方法会让执行效率快9倍以上。
⑤:避免在内部调用Getters/Setters方法
面向对象中封装的思想是不要把类内部的字段暴露给外部,而是提供特定的方法来允许外部操作相应类的内部字段。但在Android中,字段搜寻比方法调用效率高得多,我们直接访问某个字段可能要比通过getters方法来去访问这个字段快3到7倍。但是编写代码还是要按照面向对象思维的,我们应该在能优化的地方进行优化,比如避免在内部调用getters/setters方法。
五、安装包优化
a)Analyze APK
Android Studio 2.2版本增加了Analyze APK功能,可以直接查看apk中各个模块占用的空间,我们可以利用此功能来查看需要优化的部分
b)Lint检查去除冗余
Android Studio-> Analyze -> Run Inspection by Name输入unused resources,此功能可以筛选出无用的资源,看一下分析结果
c)图片资源的优化
①:尽量只采用一套图片资源,建议(xxhdpi)
②:TinyPng有损压缩
③:使用webp格式
④:使用shape来代替形状规则,色彩简单的图片
d)开启shrinkResources去除依赖库中的无用资源
通过lint我们已经去除了很多无用的资源,但是我们依赖的一些官方库和第三方库中也可能引用了资源图片,此时可以在gradle中使用shrinkResources去除无用资源。
e)代码混淆
通过在gradle中配置resConfig可以选择支持打包的语言,从而去掉世界各地的语言支持
f)使用微信资源压缩打包
微信资源压缩打包原理一句话总结就是:直接处理安装包,不依赖源码,不依赖编译过程,仅仅输入一个安装包,得到一个混淆包。主要是混淆资源ID长度(例如将res/drawable/icon.png混淆成r/s/a.png),同时利用7z深度压缩,大大减小apk体积。
具体源码和使用方法在github中:https://github.com/shwenzhang/AndResGuard
g)避免重复库
避免重复库看似简单,但其中有玄机,要当心你引用的库中又引用了哪个第三方库,这样就容易出现功能重复的库了,常见的如图片加载的库:Glide、Picasso、ImageLoader等等。可通过studio查看External Libraries目录中是否有重复库的存在,减小apk体积。
h)使用更小的库
顾名思义,更小的库代表体积更小,很好理解。有些时候同样功能的库在大小上的悬殊有可能是很大的,如果对某个库的要求不是非常苛刻,尽量选择更小的库,这会减小apk的体积,这种方案不是决定性因素,可作为参考。
i)删除无用的so库
我们在加入百度地图等SDK时,会引入很多很多的so包,这些so文件占用很大的体积,像x86和x86_64都是跑在模拟器上面的,我们无需去支持,可以直接删除,或者在defaultConfig中配置:
j)风险评估
方法条例
适用范围
易用性
风险指数
备注
lint检查去冗余
全部
易
无
使用一套图片资源
非极高UI要求的APP
易
无
TinyPng有损压缩
非极高UI要求的APP
易
无
使用jpg格式
仅限非透明大图
易
中
使用webp格式
仅限4.0+设备
中
中
使用shape背景
全部
易
无
开启shrinkResources去无用资源
全部
易
无
代码混淆
全部
易
无
删除无用语言
非全球国际化应用
易
无
使用微信资源压缩打包
全部
中
中
要配置白名单
避免重复库
全部
中
中
使用更小的库
全部
中
中
删除无用so包
限对特定设备不兼容(例如模拟器)
易
高