Android内存优化之内存优化手段
上一节我们说到内存泄漏中常见的几种类型,没看的小伙伴可点击此链接去查阅哦
通过分析内存泄漏问题,本身也属于内存优化的一部分,合理使用内存会对我们的Android应用得到很大的帮助,今天我们就来看下Android内存优化中抛开内存泄漏后的内存优化,通过改变代码编写风格、API转换调用等来提升我们的内存掌控能力。主要分两点去进行:
(1)UI布局的优化,包含布局嵌套的优化、绘制优化等;
(2)代码性能的优化,包含如何正确调用更省资源的API;
UI布局优化
UI(User Interface)用户界面,我们学习Android时再熟悉不过了,通过在使用Android提供的VIew、ViewGroup或者自定义的View来布局我们的用户界面,在移动终端或者其他Android延伸产品中,屏幕大部分情况都是有限的,如何正确的布局我们的元素,尤为重要。
我们先重新认识一下View,类View是Android中最基本的UI类,我们日常大部分所用到的UI组件都是继承于View类的,比如Button、TextView、LinearLayout、RelativeLayout、Widget基类等等。
Android系统处理我们的UI组件大概分以下:
(1)Android系统会把我们编写的布局文件xml转换成GPU能识别的绘制对象,其中这里会有一个DisplayList对象,该对象会在xml转换为GPU识别对象时yong用到,并且该对象保存了所有要GPU绘制的对象的数据信息;
(2)CPU将VIew处理成多维图形、计算纹理,然后通过OpenGL ES 接口调用GPU进行栅格化渲染;
图片来源于网络,这个图具有一定代表性。
(3)在进行垂直同步后进行绘制到屏幕上。
这里有必要知道一下Android绘制屏幕的频率,人的眼睛和大脑是无法写作感知到超过60fps的画面更新,超过了60这个值基本属于浪费状态,但是如果远远低于60这个值,那我们就能感知到画面不连续,出现卡顿。比如电影画面播放的帧数大概是24帧,低于这个值我们就感受到画面会不连续,当然高出这个帧基本没问题,只不过是成本考虑罢了。
我们的Android性能标准在60fps,所以在1000ms内保证60帧绘制的话,那么一帧所用到的时间可以得出:
1000 / 60 ≈ 16ms ,也就是说我们必须保证我们所用到的View的绘制一帧在16ms内,不然就会可能出现跳帧的现象,而刚刚也说到了Android系统渲染是通过一个垂直信号Vsync来保证同步的,也就是我们自定义View时在ondraw()绘制渲染需要控制好绘制时间。
合理使用布局减少嵌套层数
(1)在存在横向对齐时很多人会使用LinearLayout布局,然后跟布局又是一个LinearLayout或者RelativeLayout,造成了一个横向对齐子布局中存在多种嵌套,例如下面所示:
我们会在代码发现在跟布局RelativeLayout中有两个zibu子布局,一个是LinearLayout和一个TextView,LinearLayout内有两个子布局,分别为一个确定和取消的Button,为横向对齐排布,这个时候我们可以改进u一下写法,如下:
直接外部一层RelativeLayout,里面通过属性使用来排布,减少布局的嵌套层数,在减少布局时,我们可以使用<merge/>标签来优化界面,其中我们推荐使用<ViewStub/>、<requestFocus/>、<merge/>配合<include/>来使用,<merge/>标签在UI结构中起到非常重要的作用,通过该标签可以让我们的布局多余的嵌套层数减少,从而优化Android布局结构,加快绘制。而<ViewStub/>该标签多用于不确定的布局中,比如一些应用通过身份ID等来确定绘制哪类布局,用LayoutInflate将所需的布局进行加载进来,也就是我们所说的延迟加载,优化启动速度。
谷歌在RelativeLayout布局上推出了一个增强版的相对布局ConstaintLayout,ConstraintLayout允许你在不适用任何嵌套的情况下创建大型而又复杂的布局,所以使用RelativeLayout布局时,不妨采用ConstaintLayout来替代。
避免UI过度绘制
过度绘制通过是我们不注意布局背景颜色的设置,比如我们以下代码,根布局RelativeLayout里面设置了一个全局白色背景,而子布局LinearLayout也设置了白色背景,导致背景颜色重复绘制,增加了系统绘制开销,所以我们可以通过检查类似的写法,来优化我们的布局,加快系统绘制。
布局优化监测工具
(1)Hierarchy Viewer,层级观察器,可分析得出层级嵌套的树状图,直观;
(2)Layout OptiOptimization工具,该工具提供优化意见,可在tools文件夹下找到layoutopt.bat来启动工具;
(3)Android Lint,一款代码扫描工具,不仅仅提供布局优化意见,还提供代码优化意见;
代码性能优化
如果自己写过几万行代码了,那么我估计你都自己掌握了一套自身的代码优化手段,优秀的代码让人阅读起来会赏心悦目,以前小编曾任职过一家公司,内部代码经过了好几手,到我手上时候ji就一个变量命名我都看不出是啥意思,这让我在后面的工作中难以进行,直到后来我重构了里面的代码,这才让我工作得心应手。
命名定义优化
(1)在Java中,遵从驼峰式命名,禁止使用下划线“_xxx”开头命名,因为这本身不符合Java程序员的阅读习惯,我们说过了,除了写给自己阅读之外,还有供别人阅读,所以在命名时需要遵从驼峰式命名。
(2)类名遵从UpperCamelCase风格,首字母大写,禁止小写:
(3)常量定义全部大写,字母间用下划线间隔,如:
(4)抽象类使用Abstract或者Base开头,而异常类使用Exception结尾,如:
命名规范有很多,我这里只提一小部分,大家可以移步到阿里巴巴Java开发手册.pdf和阿里andrAndroid开发手册,里面总结的内容都很不错,至少我在里面改了好度coding坏习惯。
避免重复循环
(1)在使用for循环时会存在一种坏习惯,我们先上一段有问题的代码:
可以看到在循环体内部,重复创建了一个Object对象,导致内存中存在许多重复对象,造成内存过度浪费,我们可以将创建对象的操作搬到循环体外部:
(2)循环条件判断优化,在遍历数组时循环条件稍微改一改,得到的效果大不一样,我们先上错误的代码:
在执行条件中,每一次都会去获取数据array的长度length,造成时间的损耗,如果不是做插入或者删除操作,我们可以将数组长度存到一个临时对象当中,这样我们每次循环就不用去重复去获取数组的length对象了:
API和对象选择使用的优化
我们在选择对象或者方法时,尽量使用系统优化过的,这样会让我们的代码编写得更加出色。
(1)字符拼接的使用:String类我们使用再频繁不过了,但如果我们阅读过源码会发现,里面内部其实是通过char[]数组进行存储字符的,我们在使用String类其实就是间接使用char[]数组,初始化赋值时就会定char数组的长度,如果我们想进行字符拼接或者删减,都会触发系统去开辟一块新的内存,然后将新字符复制拼接存储,所以在频繁操作String类型的数据时我们可以选用一些比较好的类。
StringBuffer的使用,使用StringBuffer是线程安全的,在并发访问该对象时会进行加锁访问,还有一个类就是StringBuilder,该类非线程安全,这两个的选择需要考虑使用场景,如果不考虑线程安全问题,推荐使用StringBuilder,毕竟效率会比StringBuffer要高。
(2)图片对象创建的方法选择:在Android中,我们尽量不要使用setImageBitmap、setImageResource、BitmapFactory.decodeResource来设置一张大图,这些方法完成decode后,都是通过调用Java层的createBitmap()来创建Bitmap对象的,对于内存开销大,我们推荐使用BitmapFactory.decodeStream来创建一个Bitmap对象,因为该方法直接调取的是JNI>>nativeDecodeAsset()方法来完成decode的,直接节省内存,避免在Java层中做过多的开销。
(3)处理字符串时,尽量多使用诸如String.indexOf()等,这方法都是直接调用JNI的。
异常处尽量自行处理,不要直接捕获
直接捕获异常这样的坏习惯对性能会有影响,比如我们的RuntimeException中的NullPointerException,我们可以通过判空来达到目的,不要直接去catch异常,这样大大节省系统产生异常时让CPU停下来去处理的时间。我们在编写Adapter时会经常遇到活动时数组越界的异常,这类异常我们不用直接去捕获,直接判断是否越界,通过if()...else...来处理。
优化代码调用
我们一般都是习惯使用接口编程,比如使用List,如下:
采用第一种是经常遇到的,因为这样做更加明智,它允许我们实现不同的实现类,但在Android系统中这样做未必是好的习惯,因为通过这样的接口引用会花费两倍以上的时间。所以我们推荐使用第二种来编程。
控件的选择
我们在使用ListView时,不f妨直接选用更加丰富的RecyclerView,ListView一直受人诟病的内存占用问题,而在RecyclerView就得到了良好的改善,而我RecyclerView提供更加丰富的API,相比ListView可为相对好的。在使用RelativeLayout时可用ConstaintLayout,增强版的相对布局,可减少布局嵌套层数。
建议使用Drawable
虽然Bitmap能让我们直接省事,直接放进去显示就好了,但是采用Drawable会更加出色,Drawable也是能实现图像功能,使用更少的内存。
成员方法修饰优化
当一个方法经常被外部调用或者它不依赖于外部对象同时也不是Override,我们推荐将方法修饰为static,这样调用的速度回jia加快。
单例的写法
我们编写单例有很多方式,其中一种比较推荐的方式是“饿狼模式”单例,如下面代码:
synchronized直接修饰方法ti体内部,不直接修饰fa方法,这样大大加快的访问的速度,因为我们的单例大部分时候都是已经有实例的,如果修饰方法的话,我们每次都会去同步竞争,降低了方法的访问速度。
结语
写了一天文章,可能思路不太全,但是大部分都是自己的总结和遇到过的,程序员要保持学习能力才能在以后的IT浪潮中存活下来,避免被市场淘汰哈哈哈。