从Android内存到图片缓存优化
在我们买Android手机的时候必看的两个参数,那就是RAM和ROM的大小,这两个参数对我们来说当然是越大越好。RAM就是我们今天的主角Android内存。
我们使用Windows时有一个习惯:为了保证我们想要运行的程序有足够的内存空间,我们经常会手动把不必要的程序关掉,或者用一个内存清理的软件,时不时地清理一下内存。基于这样一个习惯,在Android所有的安全软件和助手软件中,几乎都会必带的一个功能模块,那就是清理手机内存。这个清理的动作在Android的手机上是不是必须的呢?答案是否定的。为什么Android不需要主动清除内存?我们得从Android系统内存管理特性来说说,在Java虚拟机里有这么一句经典的话:“Java 和C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外的人想进去,墙里面的人却想出来。”这句话里透露了“垃圾收集技术”在Java虚拟机里面的重要作用,其实Android内存管理引进了这个“垃圾收集技术”的类似技术—Low Memory Killer。这也是Android和Linux内存管理上的区别。当我们退出应用程序,实际上应用程序还在后台,当后台的程序越来越多,那么占用的内存就会越来越多。当可使用的内存少到内存管理设定的一个阀值的时候,就会触发Low Memory Killer 去按照一定的规则,从当前后台应用列表中找出最合适的进程并杀死它,来达到满足新开启的应用内存需求。所以说,只要我们手机使用的内存低于那个阀值,不管后台有多少个应用程序,你新打开的应用程序的运行速度是完全不受内存任何影响的。同时当你要打开一个后台程序的时候也能快速的启动起来。因此当你残忍的杀掉后台应用程序的时候也是在残忍的抹杀Android设计者们的心血啊!
除了Android自动管理内存的设计,还有另外一个特点,那就是Android每一个应用程序可申请使用的最大内存是固定受限的。不会像Windows那样因为某一个应用程序太耗费内存而导致系统卡顿。
Android是如何限制应用内存的呢?我们需要从Google公司自己设计用于Android平台的Java虚拟机—Dalvik说起。Dalvik 经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个Dalvik 应用作为一个独立的Linux 进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。也就是说这样做让我们所有应用程序都是运行在各自独立的Dalvik虚拟机中,相互不受影响。熟悉Java虚拟机的都知道, 虚拟机的两个配置项-Xms和-Xmx( JVM初始分配的堆内存和JVM最大允许分配的堆内存),其实在我们Eclipse很卡的时候,我们也会去适当增大第二个参数来让我们的Eclipse跑的更顺畅。Android每一个应用程序都是在各自单独的Dalvik上执行,只要在Dalvik配置上这两个参数也就限定了Android应用程序能获得的最大内存。Android这个配置是Rom开发人员根据手机出厂RAM的大小在build.prop文件通过dalvik.vm.heapstartsize=8m和dalvik.vm.heapgrowthlimit=64m类配置的。
因为Android对应用内存的这一限定,对于需要加载大量图片的应用来说,带来了一个令人头痛的问题,那就是OOM(内存溢出)。于是乎大量图片缓存的框架就孕育而生,被广泛使用的有Universal-Image-Loader、Volley和Picasso等。
Universal-Image-Loader庞大、自定义性强,它为我们提供了磁盘缓存、内存硬缓存、内存软引用缓存、以及各种缓存策略,比如说,先进先出,最近最少使用,使用频率等等。在代码控制上也为我们提供了限制同时多次装载同一张图片,ImageView(图片显示控件)复用使用最新的图片下载请求,以及我们的列表,宫格界面快速滑动暂停下载等等。而Volley短小,精悍,它把缓存部分交给了用户自己去实现,但是控制上应该说Volley更胜一筹,原因是,在Universal-Image-Loader控制同时多次装载同一张图片是会阻塞线程池的工作线程,等其中一个线程加载完毕,其他线程才能唤醒从缓存中去读取图片。而Volley则在加入到工作队列之前就把这些相同的请求都剔除出来了,保证每一个工作的线程,都是在下载不同的图片。而且在控制的实现上Volley也相比Universal-Image-Loader利用ReentrantLock来限制同一张图片被多次下载、利用同步锁控制快速滑动时暂停所有工作线程等来的更巧妙。
虽然他们都基本解决了我们应用程序的OOM问题。当我们程序猿都以为图片加载问题万事大吉的时候,你就错了。曾经一个某某团购公司的VP,拿着他们自己的APP和他们友商的APP,放到我面前说为什么我们这个APP的图片显示出来就是比他们的慢,时延长?于是我从虚拟机、手机可获得最大内存、手机CPU核数、手机分辨率、图片尺寸等等各个方面各种情况分析了个遍。他听得很认真,但是我从他的眼神里看到了茫然。后来我回去想,我应该怎样讲才能让他听明白呢?再后来发现还是我自己没有找到问题的根源,也没有讲明白!出现这种问题,必然就是我们的应用没有把这个手机资源利用到极致。
怎样才算是对手机资源充分利用呢?我觉得我们需要清楚以下问题:
第一个问题:您的手机最多能获得多少内存?不同的手机dalvik.vm.heapgrowthlimit的设置不一样,所以图片内存缓存的值不能所有的手机都固定在一个值,一帮情况下设置为能获取最大内存的1/8。
第二个问题:您手机的CPU核数?试想一下如果你单核的CPU开两个线程去下载,八核的CPU也只是开两个线程去下载。这样对八核的CPU公平吗!
第三个问题:您手机的分辨率是多少?试想一下你如果是480X320分辨率的手机,你一定加载一张1280X720的图片来显示,不仅不会带来任何显示方面的优势,反而给你的内存添堵了。所以优秀的应用不同分辨率对图片一定要有不同的处理。
第四个问题:您希望预加载多少张图片(N),每张图片的分辨率(X*Y)和缓存格式(ARGB_4444, ARGB_8888,RGB_565),这样就能知道每张图片占用多少内存。这里说的预加载的意思就是你希望你的内存(最大内存的1/8)里面放多少张图片,也就是说如果图片缓存格式是RGB_565(每个像素点占16位就是2个字节)的情况下。最大内存*1/8 >= N*(X*Y)*2一般情况下内存缓存的图片加载起来就不会显示慢或者有时延。如果内存中的图片越多那么显示起来就会越流畅。这个时候就需要在图片数量和质量上做一个平衡了。
第五个问题:为您的应用特点定制合适的缓存策略。比如LUR,FIFO,UsingFreq等等策略能保证你换出内存的图片确实是你将来一段时间不需要的。
如果你写出来的应用在图片缓存方面,不管你用的是什么框架,上面五个问题,你都了然于胸,并做好了很好的解决方案。那么我觉得已近很完美了。
是不是可以更完美呢?当然,终极方案,虽然有作弊嫌疑,也是Android官方不建议使用的方法。那就是增大应用可获取的最大内存。其实在build.prop中不仅会通过dalvik.vm.heapstartsize配置应用初始化堆内存,dalvik.vm.heapgrowthlimit配置应用最大可使用的堆内存。还会通过dalvik.vm.heapsize配置让应用获得更大的内存。在Sumsang Note2上这个值配置到了dalvik.vm.heapsize 128M,魅族3上配置居然到了512M。天啊,512M是什么概念?那就是你会拍着胸脯说,您还是觉得图片加载慢是吧,没问题,我再给您优化优化,So Easy!当然,这个只能在Android 3.0(HoneyComb)以上的版本可以通过android:largeHeap="true"来申请。虽然我们获得了更多的内存,但是不到万不得已不要使用,如果一定要用,也不要滥用。因为如果用的越多,第一会导致GC的时间变成长。第二,就会导致系统的内存急剧减少,系统内存少到一个阀值的时候,就会启动Low Memory Killer通过一些列之前设定的策略,找出最合适kill的后台进程,并杀掉。这些都是会增加系统开销,降低系统性能的。
性能 | 微信开放文档 https://developers.weixin.qq.com/miniprogram/dev/framework/audits/performance.html
7. 图片缓存
开启 HTTP 缓存控制后,下一次加载同样的图片,会直接从缓存读取,大大提升加载速度。
得分条件:所有图片均开启 HTTP 缓存