欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

解决Android平台中应用程序OOM异常的方法

程序员文章站 2023-12-14 12:30:22
在android平台上面,应用程序oom异常永远都是值得关注的问题。通常这一块也是程序这中的重点之一。这下我就如何解决oom作一点简单的介绍。 首先,oom就是内存溢出,...

在android平台上面,应用程序oom异常永远都是值得关注的问题。通常这一块也是程序这中的重点之一。这下我就如何解决oom作一点简单的介绍。

首先,oom就是内存溢出,即out of memory。也就是说内存占有量超过了vm所分配的最大。

怎么解决oom,通常oom都发生在需要用到大量内存的情况下(创建或解析bitmap,分配特大的数组等),在这样的一种情况下,就可能出现oom,据我现在了解到,多数oom都是因为bitmap太大。所以,这里我就专门针对如何解决bitmap的oom。其实最核发的就是只加载可见范围内的bitmap,试想这样一种情况,在gridview或listview中,数据量有5000,每一屏只显示20个元素,那么不可见的,我们是不需要保存bitmap在内在中的。所以我们就是只把那么可见的bitmap保留在内存中,那些不可见的,就释放掉。当元素滑出来时,再去加载bitmap。

这里我有两种方式,都可以避免oom。

一、主动释放bitmap的内存
这种方式我简单说一下,不太推荐,这也是我最开始使用的一种方法,但最后证明它不是最好的。(不推荐)

它的本质思路是:
  1、只加载可见区域的bitmap

  2、滑动时不加载

  3、停止滑动(idle)后,开始重新加载可见区域的图片

  4、释放滑出可见区域的bitmap的内在。

它比较复杂:
    1、我们需要监听gridview/listview的滑动事件,这个很简单做到,abslistview#setonscrolllistener(onscrolllistener l)

    2、主动调用bitmap#recycle()方法,它会导致一个问题,必须判断这个bitmap是否被一个view(imageview等)所引用,如果被引用,我们不能简单地调用recycle()方法,这样会导致异常,说是view使用了一个已经被回收的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取消。

以上就是解决应用程序oom异常的方法,希望对大家的学习有所帮助。

上一篇:

下一篇: