为什么你的app会出现卡顿?Android开发: 关于性能需要考
刚做Android开发时,只管完成任务,将需求完成,以能完成一款界面酷炫的app为自豪。然而,随着代码量的增加,越来越意识到,一款成功的移动端产品,光有酷炫的外衣还不够,还需要在各方面都优秀。
试想,拿着一款app,第一眼看去,页面静态效果完美,但试着去操作,点击按钮半天不见反馈,滑动时伴着卡顿效果,甚至时不时弹出个“应用程序无响应”,用户有何感想;又或者,装上app后,原本充一次电可以用一天,现在半天就没电了,偶尔放在口袋里手机感觉会发烫。其实这些,都是一些看得见或看不见的性能问题,作为一名合格的开发者,我们应该尽量去解决。
性能优化,我认为主要从这几个方面考虑(个人观点,不对或者遗漏的欢迎指出和补充):
1、适当的UI布局
2、高效的代码/语法,合理的数据结构和算法
3、减少不必要的内存开销,降低垃圾回收频率
4、合理使用线程、缓存、预加载等解决性能瓶颈
5、其它优化(网络、图片、资源等 )
1、 适当的UI布局,自定义view
①.布局优化
使用RelativeLayout替代多次嵌套的LinearLayout;使用include、merge、ViewStub标签;减少冗余的层级嵌套及复杂布局;该使用GONE的地方不要使用INVISIBLE;使用weight时,设置width或height为0dp,减少运算;减少非必要的layout背景设置;利用lint进行布局优化,减少非必要元素。
②.自定义view
draw、measure、layout中尽量不做耗时操作;使用一些自定义itemView替换复杂嵌套的item,减少measure和layout次数;使用clipRect()确认绘制区域,减少不必要的绘制;文本绘制时,使用StaticLayout进行文本换行,而不必自己进行计算;绘制bitmap时如果有需要先压缩;
③.使用RecyclerView
使用RecyclerView替换listview,或scrollview,listview,gridview等的嵌套布局;不但可以减少层级,而且RecyclerView在增删item时更有效率,不需要刷新所有item
2、 高效的代码/语法 ,合理的数据结构和算法
可以肯定的是,这一条是所有程序员都希望做到的,也是任何阶段程序员都有能力去做的,而更加可以肯定的是,绝大多数程序员(包括我)都做得不够。包含了以下几点(想到哪写到哪,可能有点乱):
①.优先使用增强型for循环(forEach)
在做循环操作时,优先使用forEach (内部实现是iterator,效率与使用iterator相差不大,且代码简洁易于理解,缺点是无法进行remove操作,有remove操作可以直接使用iterator),ps:ArrayList除外,因为其使用数组作为存储结构,get操作非常高效,使用for循环的遍历效率比forEach高
②.运算操作使用移位,合理使用浮点型,少使用除法(其实这对于大部分app的性能而言只是九牛一毛,但使用到的地方较多,所以列出来讲一下)
在Android上,整型处理速度高于浮点型两倍以上。使用移位运算符也是考虑处理速度,理论上x>>2 运算速度是 x*2的2倍,乘法运算效率也优于除法
③.让curson的打开/关闭操作更合理,io流及时关闭
curson的打开及关闭是比较耗时的操作,在确保使用完毕后再关闭。io流用完及时关闭,不然会影响到文件被其他程序使用,出现不可控的异常情况。
④.context的使用
周期长的context引用使用application context,不要直接使用Activity,以免造成内存泄漏。activity实例确保仅在Activity生命周期内引用。切记避免静态context。
⑤.合理使用SoftRefrerence和WeakReference
确保内存不足时能得到释放。如Bitmap等...
⑥.使用webview需要销毁
在Activity/Fragment生命周期结束时调用webview.destory()
⑦.String/StringBuilder/StringBuffer合理使用
纯字符串拼接时(如“I” + " am "+"Thinkin Liu"),String更高效,因为在编译时,就已经将其拼接成一个字符串。而在不需要考虑线程安全的情况下,StringBuilder比StringBuffer及String更高效。
⑧.ArrayList,LinkedList等数据结构合理使用
在顺序增加add(Object)和查找get操作比较多时,使用ArrayList更有优势
而在无序的增删中,使用LikedList更占优势
⑨.使用SparseArray替代HashMap<Integer, Object>
SparseArray内存分配优化、使用binarySearch等让其更有效率,相应的有SparseBooleanArray,SparseIntArray等
⑩.序列化Serializable和Parcelable
在内存间数据传输时推荐使用Parcelable,可减少大量的临时变量,防止GC频繁调用;读写数据时,Parelable是在内存中直接读写,而Sericalizable是通过使用IO流的形式将数据读写入在硬盘上
3、减少不必要的内存开销,降低垃圾回收频率
其实在上面(二)已经涉及到了内存开销和减少GC,下面也会涉及到(四)。但这点是内存性能问题的本质,单独提下:
①.避免非必要对象的创建
对象的频繁创建和销毁,能使内存的使用率过高,出现内存抖动现象,进而导致GC频繁。
②.避免频繁代码调用System.gc()
4、合理使用线程、缓存、预加载、延时加载等解决性能瓶颈
①.做耗时操作时,将其放入线程中
如文件读写、数据库操作、网络访问、耗时计算等,不要在UI线程中进行,以免影响到UI的刷新
②.使用线程池
使用线程池,而不是单独的开启线程,线程池可以更便利的管理线程,同时还包含复用机制
③.合理的在子线程中sleep()
子线程需要进行耗时长的操作时(如文件上传),合理的每隔一段时间sleep(10),将cpu让给其他线程(目标是UI线程),页面刷新会更顺畅
④.合理的利用缓存
如listview中复用convertView,使用ViewHolder;图片的三级缓存机制;网络请求的数据缓存...这些缓存可以让下一次用到时加载速度更快。
⑤.合理的预加载和延时加载
如图片浏览时,将前后图预加载,解决滑动切换后的黑屏问题;列表中的头像延时加载处理,可以让滑动更顺畅。
5、其它优化(网络、图片、资源等 )
①.网络优化
开启长连接(减少tcp三次握手及四次挥手,缺点是资源占用);使用缓存(根据版本号判断是否需要返回新的数据,减少数据传输);列表页预加载;将多个请求合并;区分不同网络(wifi/4G高并发,2G优先低并发,优先核心请求);设置TimeOut;数据进行压缩;使用ip替代域名进行请求(省去dns开销);选择高效的网络框架(如okhttp)
②.图片/资源优化
使用bitmap时压缩加载;加载大图时候考虑使用BitmapRegionDecoder;使用完bitmap及时recycle();使用.9图;使用矢量图VectorDrawable;
③.了解并使用库函数
Java标准库和Android Framework中包含了大量的库函数,不少函数采用native实现,通常情况下比用Java实现同样功能的效率要高。
④.工具的使用
利用Android各种工具进行内存,cpu,电量等分析,如何使用网上不少,这里不多说了
ps:先写这么多了,感觉有点乱,其实主要涉及到的就是cpu/gpu(计算、处理、渲染等),内存(内存泄漏,内存抖动,内存溢出)的优化。
以上只是临时想到的一些性能优化点,有不对的请指出,有缺少的也麻烦补充下,多谢!