您的位置:首页 > 移动开发 > Android开发

Android内存优化建议

2016-08-25 22:30 218 查看
        这篇博客主要是总结一些内存优化技巧,大体包括编码优化、Bitmap优化、优化ListView减少内存开销、布局优化、其他优化:

        编码优化:

        (1):使用更加轻量级的数据结构

        使用ArrayMap和SparseArray代替我们常见的HashMap,因为对于HashMap来说,它本身是由数组加链表实现的,通常为了他的mapping操作,我们需要开辟一段数组空间,但是很多情况下这段数组空间是不会得到合理利用的,针对这一点,我们可以使用ArrayMap来代替他,ArrayMap的实现原理是通过两个数组实现的,一个数组用来存储key值对应的hash值,这个数组中的hash值是顺序存储的,另一个数组中存储的是对应于第一个数组hash顺序的键值对,对于put操作,首先先计算出当前key对应的hash值,并将其放入到第一个数组中,注意必须是顺序的,也就是说可能会对第一个数组进行排序,那么在排序的过程中,第一个数组在发生相应调整的同时第二个数组也要跟着进行调整,在找到key值在第一个数组中对应的hash所处的位置之后,会直接查看第二个数组中对应于这个位置index处是否存在键值对,如果存在的话,则会向上或者向下搜索一个空闲的位置,将当前键值对插入,如果不存在的话,则直接插入就可以了;对于get操作的话,首先通过key值计算出来一个hash值,然后根据该hash值通过二分查找的方法找到他在第一个数组中的位置index,接着查看第二个数组中的index位置处的键值对的key值是否equals当前键,如果equals的话,则返回该键值对应的value就可以了,如果不equals的话,则通过向下或者向上搜索的方式去查找,知道找到或者到达数组边界为止;除此之外呢,HashMap需要为每一个键值对都提供一个对象入口,而SparseArray避免了基本数据类型转换为对象数据类型的封箱和解箱操作;

        (2):对常量使用static-final修饰符

        对于基本数据类型以及String类型的常量,尽量声明为是static-final类型的,这样的话,这些常量会在dex文件的初始化器中进行初始化,而不再是通过类的<clinit>方法初始化了;

        (3):减少枚举类型的使用

        因为枚举类型在使用的过程中会产生大量的中间元素;

        (4):如果存在大量的字符串拼接操作的话,使用StringBuffer或者StringBuilder代替+运算符,因为使用+运算符的话会产生大量的中间元素;

        (5):采用非静态内部类或者匿名内部类时

        因为对于非静态内部类或者匿名内部类的话,默认情况下是存在一个外部类的引用的,如果我们的内部类的存活期限不会超过外部类的话,那么是不会出现什么问题的,但是如果内部类的存活期限要长于外部类的话,就会出现即使外部类已经不再使用了,但是它所占用的内存空间还是不会得到释放的情况出现,这种现象很频繁的出现在我们使用Handler的时候,如果Activity已经退出,但是Handler还有延迟处理的消息没有处理完,那么这时候就会导致Activity不能回收导致内存泄漏,这时候的解决方案是将Handler声明为static类型,在外部类中实例化一个外部类的WeakReference(弱引用),并且在Handler初始化的时候传入这个对象给Handler,这样在Handler内部使用WeakReference对象来引用外部类成员;

        Bitmap优化:

        (1):尽量使用较小的图片

        使用较小的图片,不仅可以减少OOM异常,同时也能减少InflateException异常,因为我们可能在刚刚加载图片的时候就可能因为内存不足而加载失败,只时候报的是InflateException异常;

        (2):减少Bitmap多占用的内存空间

        减少Bitmap占用内存空间的方法有:进行适当的图片压缩以及选用合适的图片解码器;具体来讲的话,在我们将图片加载到内存之前,可以通过设置加载图片的inJustDecodeBounds属性为false,这样的话只会将图片的头部信息加载到内存,我们可以得到图片的宽度和高度,接着根据显示图片的控件的宽度和高度计算出一定的压缩比,将值设置到图片的inSampleSize属性,接着设置加载图片的inJustDecodeBounds属性为true,这时候再次加载图片的时候,加载到内存中的就是压缩过后的啦;此外,采用不同的图片解码方式,占用的内存空间也是不同的,对于ARGB_8888编码的图片,其每个像素占用4个字节,对于其他的图片编码方式,每个像素占用的字节数也是有所不同的;

        (3):使用缓存机制减少Bitmap占用内存的大小

        我们可以使用LRUCache内存缓存和DiskLruCache来减少Bitmap占用的内存空间大小;

        (4):使用inBitmap高级属性

        Bitmap的inBitmap高级属性会大大提升Android系统在分配和释放Bitmap占用空间上的效率,具体来讲的话,对于每一个想要加载的Bitmap,inBitmap属性会告知图片解码器优先使用已经在内存中的Bitmap区域,而不是重新开辟一段内存空间,但是要想真正使用好inBitmap高级属性的话,需要具备两个条件,我们新申请的Bitmap大小必须小于等于旧Bitmap的大小,此外新旧Bitmap的图片解码器类型也应该一致,要使用ARGB_8888就都是ARGB_8888,不过,为了优化,我们可以设置一个可重用Bitmap的对象池,这样后续的Bitmap到来之后都可以从池子里面找到一个适合自己的模板去复用啦!

        优化ListView减少内存开销:

        (1):重用ConvertView,在ListView中Android的RecycleBin机制是通过生产者消费者模式实现的,任何移出屏幕的Item在下一时刻都会被移入屏幕的Item重新利用起来,我们应该充分利用这种机制,因为构造View是需要通过LayoutInflater的inflate方法通过pull解析的方式进行的,这个过程是涉及到IO操作的;

        (2):尽量减少findViewById的次数,因为findViewById方法也是涉及到IO操作的;

        (3):如果存在加载大量图片的话,在快速滑动的时候应该停止图片的加载;

        (4):尽量保证Adapter的hasStables方法返回true,这样在调用Adapter的notifyDataSetChanged方法的时候,如果当前ListView中的Item元素没有发生变化的话,就不需要重绘ListView了;

        (5):尽量减少每个Item的层级,这样会减少视图重绘带来的内存开销;

        (6):使用RecycleView代替ListView,对于ListView来说,只要有一个Item发生变化,都会回调Adapter的notifyDataSetChanged方法,该方法会导致整个ListView重绘,但是RecycleView是支持局部刷新的,哪个Item发生变化,只需要重绘该Item就可以了,没必要重绘全部View,还有就是RecycleView提供了Item的插入、删除的动画效果,在性能和个性化定制方面更加出色;

        (7):开启硬件加速;

        (8):分页加载;

        布局优化:思想就是尽量减少布局文件的层级;

        (1):如果布局既可以通过RelativeLayout又可以通过LinearLayout实现的话,选择通过LinearLayout实现,因为RelativeLayout的功能相对于LinearLayout来说更加复杂,布局过程需要更多的CPU开销;如果某些布局能够通过RelativeLayout来实现,也可以通过嵌套LinearLayout来实现,这时候应该选择RelativeLayout来实现,因为嵌套相当于增加了布局的层级,带来的性能损失比布局RelativeLayout更大;

        (2):使用<include>和<merge>标签来进行布局重用,减少布局的层级;关于<include>和<merge>的使用,大家可以查看郭神的这篇博客,我在此仅仅总结下:在<include>标签中可以指定一个layout属性,填写需要引入的布局名就可以啦,此外呢,我们可以在<include>中覆写所有layout属性,并且<include>中设置之后的属性值将会替换到原先复用布局文件中的对应属性,对于非layout属性则无法在<include>标签中进行覆写,如果我们想要在<include>中覆写layout属性,必须将layout_width和layout_height属性同时覆写,否则不会有任何效果;<merge>标签是配合<include>起作用的,因为在我们将某一布局文件<include>到某一布局中的时候,会出现多余的布局,即此布局是重复的,本来外层布局已经可以达到效果了,你又加了一层布局,布局层次增加,增加了绘制的开销,如果将要加进来的布局使用<merge>标签的话,那么他就会真正意义上把当前布局添加到原先布局中去,而不会产生多余的布局,出现布局嵌套现象;

        (3):使用<ViewStub>标签,这个标签能够做到动态加载我们的布局,不同于我们通过GONE的方式来设置某个View是否可见,ViewStub是View的一种,但是它没有大小,没有绘制功能,也不参与布局,资源消耗非常低,这点是不同于我们通过GONE方式设置View不可见的,因为通过GONE方式设置View不可见了说到底还是会绘制View的,只不过你看不到而已;需要注意的是,虽然ViewStub没有宽高,但是每个布局都是必须要指定layout_width和layout_height属性的,我们通常想要显示某一个ViewStub的时候可以通过setVisibility(View.VISIBLE)和LayoutInflater的inflate方法将其显示出来,除此之外,ViewStub是不能和<merge>配合使用的,因此使用ViewStub可能会带来布局嵌套的问题;

       
其他优化:

        (1):针对单例模式可能带来的内存溢出现象,在创建单例的时候,一定要注意传入的Context上下文对象的类型,最好是Application对象;

        (2):尽量不要在onDraw方法里面创建新的局部对象,因为onDraw方法被调用的比较频繁,这样会产生大量的临时对象,会进行频繁的GC操作,严重情况会出现内存抖动现象;

        (3):属性动画带来的内存泄漏;属性动画中存在一种无限制循环的动画,如果在Activity中播放此类动画且没有在onDestroy中去停止动画,那么动画会一直播放下去,尽管已经无法在界面上看到动画效果,并且这时候Activity的View会被动画持有,而View又持有Activity,最终导致Activity无法释放;

        (4):有节制的使用Service,在系统中如果用到Service的话来执行后台任务的话,一定要记得在需要的时候开启Service,当任务执行结束之后去停止Service,尽量避免停止Service带来的内存些泄漏问题;如果我们不自己手动结束掉Serviec的话,对于系统来讲,它是更倾向于将Service所依赖的进程保留下来的,这回导致Service所在的进程非常耗内存,系统可以并行执行的进程数会减少,严重的话会导致系统奔溃;Android官方建议我们使用IntentService来代替普通的Service;

        (5):当界面不可见的时候,释放掉所有与当前界面相关的资源,可以让系统缓存后台进程的能力增强,加强用户体验度,那么怎么知道界面可不可见呢?只需要重写Activity的onTrimMemory方法就可以了,然后在该方法里面监听TRIM_MEMORY_UI_HIDDEN级别就可以了,在这种级别下释放掉与当前界面相关的资源就可以了;onTrimMemory和onStop方法还是有很大区别的,像我们通常的onStop方法的话,他会在我们的Activity跳转的时候被回调,释放的是一些与Activity有关的资源,而onTrimMemory方法中的TRIM_MEMORY_UI_HIDDEN级别只有我们应用程序的所有UI组件全部不可见的时候会被调用,相当于我们的应用程序处于后台运行的状态下,在Activity跳转的时候是不会执行的,保证了我们在跳转Activity的时候不需要重新加载界面元素,提升了应用的响应速度;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息