Google官方 详解 Android 性能优化【史诗巨著之内存篇】

前言:首先感谢这两天大家的投票,SuperVideo将不再拉票,可以去魅族应用市场下载最新2.0版本私下体验。有想帮投的,上一篇文章中,有链接。尤其感谢鸿洋基友和明云基友,两位都有维护自己的公众号和博客,没有关注鸿洋公众号和明云的open dev公众号的,文章下面有二维码,可关注。鸿洋,一直更新着公众号,不管刮风还是下雪,工作日每天7:30准时推送一篇干货。****博客早已突破千万:blog地址为:http://blog.****.net/lmj623565791,做Android的,不用我说,应该都知道。明云(张明云),知乎专栏上写了一系列的性能优化,都相当实用,open dev也会定期送上干货,不定期汇总推荐当下优秀文章)今天来看下

杨超凡授权本公众号的Google官方的性能优化一些优化建议:点击阅读原文,可查看

杨超凡的原文:http://blog.****.net/chivalrousman/article/details/51553114,话不多说,看下正文。

1

使用AsyncTask加载Bitmap

当图片资源来自网络或者硬盘的时候,最好不要直接在主线程中加载它,例如IO资源或者数据库资源都会占用CPU,CPU 要做的事情过多,Android手机会造成卡顿得现象,好在Google 提供了解决办法–AsyncTask异步加载工具

Google官方 详解 Android 性能优化【史诗巨著之内存篇】
接着我们在主线程中执行它即可

Google官方 详解 Android 性能优化【史诗巨著之内存篇】

2

使用Lrucache缓存图片

1. 为什么要缓存图片?

对于如何高效的加载一张图片 ,我们似乎已经得心应手了,这里要泼一盆凉水给大家,因为我们的应用不仅只是加载一张图片这么简单,比如ListView, GridView or ViewPager,RecyclerView,需要立刻加载出大量的bitmap,滑动的过程不断加载bitmap,还要求不卡顿,内存够用,这似乎又是一件棘手的事情。

Google 又提供了一种解决思路:对于ListView,RecyclerView,有可见的item和不可见的item,回收不可见的item 内存,分配给可见的,这样内存得到了重复利用,避免重复创建对象,不断申请并分配新的内存空间,触发最低内存限制的危险。

所以我们要管理 这些 已经创建好的内存。

2. 使用内存缓存

  1. 为什么优先使用内存缓存?

    答:相比硬盘缓存的读取速度,读取内存中的数据更快

  2. Google官方有什么建议?

    答:Google推荐Lrucache类,底层使用用强引用封装的LinkedHashMap,来存储最近使用的对象,它自动会回收最近使用的对象当中,使用的最少的那一个对象的内存,这一点毋庸置疑值得推荐!

一种过时的做法,是用虚引用或者弱引用来标记bitmap,这种方法在Android 3.0以后已经不提倡了,因为类似JDK1.8那样,bitmap的内存是放在本地内存中的,它的回收是不确定的,有可能导致APP挂掉,切勿使用。

  1. Lrucache这么棒,我们该如何用?

    Lrucache就可以当作一种存储数据的结构,类似list,set,可以存储对象,获取对象,对应的有add() 和get()方法,它与数组一样,初始化的时候需要指定一个初始的大小。

    那么Lrucache实例 初始的大小该如何确定? 
    在计算大小之前,我们需要明确几件事情:

  • 我们的Activity和Application还有多少可用内存?

  • 第一次加载的时候,需要为多少图片分配内存?可见的item数量,决定了图片的数量,图片的数量*每张图片的内存大小就是初始化需要分配的内存。

  • 用户当前手机的屏幕尺寸和屏幕密度(为什么要这俩参数?大屏幕手机 ,初始化的时候会加载更多的item,需要的内存更大)

  • 这张图片我们要怎样配置?图片的尺寸如何设置,颜色模式如何配置?根据不同的需求,比如是做用户头像,还是信息展示?都有各自的应用场景的要求。我们需要分类判断.

  • cache大小是由内存大小决定的,而不是 它存储数据的个数决定

    这让我想起一个在项目开发中常见的bug,use a bitmap which has bean recycler

3. Lrucache使用实例

Google官方 详解 Android 性能优化【史诗巨著之内存篇】

loadBitmap的过程很简洁:如果内存缓存中有这张bitmap,则直接刷新imageview,如果bitmap为空,则启动后台线程去加载bitmap,接着刷新imageview

Google官方 详解 Android 性能优化【史诗巨著之内存篇】
异步线程 BitmapWorkerTask

Google官方 详解 Android 性能优化【史诗巨著之内存篇】
4. 磁盘缓存 

与内存缓存相结合的还有磁盘缓存,虽然磁盘读取速度较慢,但是持久存储的,不像内存缓存那样,在内存极限情况下仍然会被清理,比如后台正在执行数据加载,突然打进来一个电话,内存不足系统可能会进行垃圾回收。缓存就没有了

Google官方提供直接DiskLruCache 类

为什么要使用DiskLruCache 很明了:就是解决当内存缓存不可用的情形

内当需要频繁访问缓存的图片资源时,比如APP的画廊功能,可以考虑使用ContentProvider解决更为妥当。

5.处理运行时变更的缓存 

当屏幕旋转,或者其他原因导致Activity restart,这个时候难道又让我们重新创建大量的图像资源? 
回答是否定的,Google提供了一种解决方案:

通过在Activity中使用fragment,构造Fragment时,通过设置 setRetainInstance(true))来设置缓存, 
话不多说,直接上代码:

Google官方 详解 Android 性能优化【史诗巨著之内存篇】

3

RecyclerView GridView ListView

Google官方 详解 Android 性能优化【史诗巨著之内存篇】
Google官方 详解 Android 性能优化【史诗巨著之内存篇】

下面可能是大家经常的做法:

看上去非常棒,对吗?但是我们如何才能做的更好?

如果你能发现主线程加载图片的问题,恭喜你,有一点点进步。但是重复提醒你使用AsyncTask,显然不是我写这一节的目的。

我们还需要警惕GridView 中的并发问题,因为iGridView 会回收子View的内存空间。

并发问题

像ListView和GridView,与RecyclerView使用AsyncTask的时候,不能忽视一个问题:Android系统为了高效分配内存,这些组件都会在上下滑动的时候回收子view的内存。在滑动的时候,并不能保证AsyncTask可以完成当前任务。此外,也不能保证异步任务可以按照顺序完成。

Google在 Multithreading for Performance 提供了建议:在AsyncTask中,用弱引用来存储ImageView,通过使用弱引用来被检查ImageView是否加载完成

好吧,理论上是这样,我们开始行动:

1. 自定义Drawable去存储异步任务重的弱引用,只有这样,才能保证task执行完毕之后,ImageView才会显示图片

Google官方 详解 Android 性能优化【史诗巨著之内存篇】

2. 在执行BitmapWorkerTask之前,你需要创建 AsyncDrawable 并且绑定到ImageView上,通过如下代码:

Google官方 详解 Android 性能优化【史诗巨著之内存篇】

3. 仅仅这些代码还是不够的,Google 还引入 cancelPotentialWork()来检查是否有其他的task任务在使用当前ImageView,如果有,就会取消当前任务,取消这个做法是不是很让人眼前一亮呢!

这是cancelPotentialWork()的实现

Google官方 详解 Android 性能优化【史诗巨著之内存篇】
4. 我们还需要get方法获得相关的ImageView:

Google官方 详解 Android 性能优化【史诗巨著之内存篇】
5. 最后就是更新task中的onPostExecute()检查任务是否取消:

Google官方 详解 Android 性能优化【史诗巨著之内存篇】
至此,我们已经了解Google 对于并发加载的解决方案,只需要在getView()实现它们就可以啦!

在GridView解决并发

Google官方 详解 Android 性能优化【史诗巨著之内存篇】
Google官方 详解 Android 性能优化【史诗巨著之内存篇】

这样解决并发的办法,同理适用于ListView, Recycler 
这也是热门加载框架ImageLoader,Volley 的加载原理。原理还是Google提出的!

这样就可以流畅的展示图片。

4

View Holder设计模式

我们的代码可能会调用findViewById(),尤其是当滑动ListView,RecyclerView,GridView的时候,会使得app性能变得糟糕。甚至Adapter会返回一个已经被Android系统回收的View,可是你仍然需要初始化加载这个view并刷新它。

Google提出了使用 “View Holder” 设计模式

1. 首先创建 ViewHolder类,存储子View内部所有需要展示的布局

Google官方 详解 Android 性能优化【史诗巨著之内存篇】
2. 接着填充ViewHolder并且存储到布局中

Google官方 详解 Android 性能优化【史诗巨著之内存篇】

‘view holder’ 设计模式同样也适用于ListView,RecyclerView,GridView

关注鸿洋微信公众号,扫一扫下方二维码或者长按识别二维码,即可关注。Google官方 详解 Android 性能优化【史诗巨著之内存篇】

关注open Dev微信公众号,扫一扫下方二维码或者长按识别二维码,即可关注。

Google官方 详解 Android 性能优化【史诗巨著之内存篇】