Android开发之浅谈OOM

OOM 顾名思义,就是Out of Memory内存溢出,这是Android开发中永远值得关注的问题,当然也是非常重要的一个点。内存溢出其实就是说内存占有量超过了VM分配的最大量。
具体怎么解决OOM呢?其实你首先得知道为什么会出现OOM,通常情况下OOM发生的原因都是因为Bitmap过大或者分配特大的数组等。
这里我说个题外话,尼玛之前一家公司的老板要我们在list中显示图片的时候一次性加载完所有的图片后放入缓存中,让它不必一直加载。。然后程序各种OOM…然后老板就说,你Y的技术太烂,程序一直crash,别人怎么用?我之后想起来,我技术确实太烂,居然没有反驳他要那么做。。阿西吧!
好了,其实就是这个原因。Bitmap的OOM解决最核心的就是只加载可见范围内的Bitmap,因为每一屏你只能显示不多的元素,不可见的元素我们是不需要保存Bitmap到内存中的,所以我们只需要把可见的Bitmap保留在内存中,那些不可见的就及时释放掉,当元素滑出来的时候再去加载Bitmap。

我这里推荐两种方式,都是可以避免OOM的。

一、主动释放Bitmap的内存

这种方式不是很推荐,不过在刚刚入行的一两年内我还是使用的这种方法去减少内存消耗,但是事实证明他并非最好的方式。

方法是这样的:
1.只加载可见范围内的Bitmap
2.滑动时不加载
3.停止滑动后,开始重新加载可见区域的图片
4.释放掉不可见区域的Bitmap内存

实现原理:
1.监听GridView或者ListView的滑动事件,这个是最基础的,应该都会做,setOnscrollListener(OnscrollListener l)

2.主动调用Bitmap的recycle()方法,不过它有一个弊端,必须判断这个Bitmap是否被一个控件所引用,如果被引用,我们就不能直接调用recycle方法去回收了,这样会导致异常,crash,使用了一个已被收回的Bitmap。

3.我们必须自定义自己的线程来控制开始和暂停释放等,因为GridView或者ListView的滑动状态可能不断地变化,这样会导致我们线程中的run()方法里的逻辑比较复杂,问题就不是一时半会儿能够解决掉的。

所以这是个很复杂的方式,不推荐使用。

二、设计Cache

这种方式我觉得还算是比较靠谱的,他利用了cache,cache是一个很重要的东西,把Bitmap的内存单独放在一个地方来管理,这个地方就是cache,它的容量是固定的,我们可能会不断的向这个cache中添加元素,它可能不断的移出元素,怎么样?是不是想到了我们要实现的点上了?
这里我先介绍下LruCache

LruCache

1、这其实就是一个LinkedHashMap,任意时刻,当一个值被访问时,它就会被移动到队列的开始位置,所以这也是为什么要用LinkedHashMap的原因,因为要频繁的做移动操作,为了提高性能,所以要用LinkedHashMap。当cache满了时,此时再向cache里面添加一个值,那么,在队列最后的值就会从队列里面移除,这个值就有可能被GC回收掉。

2、如果我们想主动释放内存,也是可以的,我们可以重写entryRemoved(Boolean, K, V, V)方法。

3、这个类是线程安全的,在多线程下面使用这个类,没不会存在问题。

synchronized (cache) {  
     if (cache.get(key) == null) {  
         cache.put(key, value);  
   }}  

4、LruCache的APILevel是12,也就是说,我们在SDK 2.3.x以下是无法使用的,但是没关系,LruCache的源码不算复杂,我们可以直接把它拷贝到自己的工程目录就可以了。

AsyncTask<>
这个类也是一个很重要也很常用的类。它封装了Thread和Handler,我们使用就更加方便,不用关注Handler,我们知道,在后台线程中是不能更新UI,而很多情况下,我们在后台线程做完一件事情后,一般都会更新UI,一般的做法是向关联到UI线程的Handler发送一个message,在Handler里面去处理这个message,从而更新UI。用了AsyncTask之后,我们就不用关注Handler了。这个类有几个重要的方法:

1、onPreExecute(): 在UI线程里面调用,它在这个task执行后会立即调用。我们在这个方法里面通常是用于建立一个任务,比如显示一个等待对话框来通知用户。

2、doInBackground(Params…):这个方法从名字就可以看出,它是运行在后台线程的,在这个方法里面,去做耗时的事情,比如下载访问网络,操作文件等。这这个方法里面,我们可以调用publishProgress(Progress…)来调用当前任务的进度,调用了这个方法后,对应的onProgressUpdate(Progress…)方法会被调用,这个方法是运行在UI线程的。

3、onProgressUpdate(Progress…):运行在UI线程,在调用publishProgress()方法之后。这个方法用来在UI上显示任何形式的进度,比如你可以显示一个等待对话框,也可以显示一个文本形式的log,还可以显示toast对话框。

4、onPostExecute(Result):当task结束后调用,它运行在UI线程。

5、取消一个task,我们可以在任何时候调用cancel(Boolean)来取消一个任务,当调用了cancel()方法后,onCancelled(Object)方法就会被调用,onPostExecute(Object)方法不会被调用,在doInBackground(Object[])方法中,我们可以用isCancelled()方法来检查任务是否取消。

6、几点规则
AsyncTask实例必须在UI线程中创建
execute(Params…)方法必须在UI线程中调用。
不用手动调用onPreExecute(), onPostExecute(), doInBackground(), onProgressUpdate()方法。
一个任务只能被执行一次。

总的思路

1、始终从cache中去取Bitmap,如果取到Bitmap,就直接把这个Bitmap设置到ImageView上面。

2、如果缓存中不存在,那么启动一个task去加载(可能从文件来,也可能从网络)。

3、每一个ImageView上面都可能绑定一个task,所以,这个ImageView必须提供一个方法能得到与之相关联的task,为什么要这样做?因为在给一个ImageView绑定task之前,必须要把原先的task取消。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值