您的位置:首页 > 编程语言 > Java开发

【转】ListView多种类型优化,Java内存泄露的理解和解决,Handle造成内存泄露解决方法合集

2015-10-31 15:52 771 查看
  ListView多种类型优化:

对于ListView 的优化,网上已经被讲解过很多,ListView的优化一般优化如下几点:

  (1)Item View重用优化(防内存溢出)
  (2)View查找优化(减少执行时间)
  (3)滑动优化,在滑动的时候加载图片数据等(防滑动卡顿)
以上基本上是对应单ItemView进行的优化,对于多种ItemView的优化一般使用BaseAdapter给
提供的两个方法

getItemViewType():返回View类型,默认返回0

getViewTypeCount():
返回有多少种类型的itemView,默认返回1

只有一种类型item view的话,是不需要重写这两个方法的。

如果有多个的时候,就需要重写这个方法了,并且返回的必须满足一下要求:
(1)getItemViewType()返回值必须大于等于0,并且小于类型的个数。为什么是这个范围,是因为
     在ListView里有一个数据,用来缓存已经使用过的Item View,详细信息自己看源码
(2)getViewTypeCount()这个方法的返回值就是你可能遇到的Item
view类型的最大个数,ListView会根据这个返回值
     去创建缓存数组

如果有两种View类型的话,直接在Adapter里面重写getView()方法就可以了,根据getItemViewType()返回值创建相应的View既可。

但是,如果有六个,七个怎么办?那么Adapter的getView()方法得写多少代码,到时候维护起来会多么麻烦,自己看自己写的还好,
如果换做别人呢?非常非常的痛苦.....

我在做我们的app的时候,就遇到了这样的问题,订单列表,每种订单所对应的Item View会有很大的区别,操作起来极为不方便,如有小的
改动都会耽误很长时间。新增类型也不好处理。

我针对遇到的问题进行了一个点点的优化,可以能不是最优秀的,但是至少能解决以上问题,如果您能有更好的方式,也欢迎交流。

设计:

使用提供者的设计方式提供每种Item View,不同的ItemView会有不同的提供者,提供者需要实现一个接口:

public interface IViewProvider {

    public abstract View getItemView(View convertView, LayoutInflater inflater, Object data);

}

提供者只需要实现此接口,然后实现getItemView()方法,实现方式和Adapter的getView()的方式完全一样,减少了学习成本。

我还继承BaseAdapter专门实现了一个Adapter,名字叫MiltilViewListAdapter.java,实现了前面说的两个方法,和getView()方法

提供者只需要配置给MiltilViewListAdapter的实例即可。

一般情况下,都要传递给Adapter一个Bean集合,我的设计是,让这里面的bean都实现一个接口,用来标示它对应的哪个提供者。

使用方法:

[mw_shl_code=java,true]private ListView mListView;

        private List<IItemBean> mList = new ArrayList<IItemBean>();

        @Override

        protected void onCreate(Bundle savedInstanceState) {

                super.onCreate(savedInstanceState);

                setContentView(R.layout.activity_main);

                createData();

                mListView = (ListView) findViewById(R.id.my_listview);

                //不同之处在于多了一个provider集合,提供所有期望显示类型的provider class

                //getView的实现在provider中实现,和在adapter中用法一样

                List<Class<? extends IViewProvider>> providers = new ArrayList<Class<? extends IViewProvider>>();

                providers.add(FlightOrderViewProvider.class);

                providers.add(SticketOrderViewProvider.class);

                

                MiltilViewListAdapter adpater = new MiltilViewListAdapter(getApplication(), mList, providers);

                mListView.setAdapter(adpater);

        }[/mw_shl_code

Java内存泄露的理解和解决:

Java内存管理机制

在C++ 语言中,如果需要动态分配一块内存,程序员需要负责这块内存的整个生命周期。从申请分配、到使用、再到最后的释放。这样的过程非常灵活,但是却十分繁琐,程序员很容易由于疏忽而忘记释放内存,从而导致内存的泄露。 Java 语言对内存管理做了自己的优化,这就是垃圾回收机制。 Java 的几乎所有内存对象都是在堆内存上分配(基本数据类型除外),然后由 GC ( garbage collection)负责自动回收不再使用的内存。

上面是Java 内存管理机制的基本情况。但是如果仅仅理解到这里,我们在实际的项目开发中仍然会遇到内存泄漏的问题。也许有人表示怀疑,既然 Java 的垃圾回收机制能够自动的回收内存,怎么还会出现内存泄漏的情况呢?这个问题,我们需要知道 GC 在什么时候回收内存对象,什么样的内存对象会被 GC 认为是“不再使用”的。

Java中对内存对象的访问,使用的是引用的方式。在 Java 代码中我们维护一个内存对象的引用变量,通过这个引用变量的值,我们可以访问到对应的内存地址中的内存对象空间。在 Java 程序中,这个引用变量本身既可以存放堆内存中,又可以放在代码栈的内存中(与基本数据类型相同)。 GC 线程会从代码栈中的引用变量开始跟踪,从而判定哪些内存是正在使用的。如果 GC 线程通过这种方式,无法跟踪到某一块堆内存,那么 GC 就认为这块内存将不再使用了(因为代码中已经无法访问这块内存了)。





通过这种有向图的内存管理方式,当一个内存对象失去了所有的引用之后,GC 就可以将其回收。反过来说,如果这个对象还存在引用,那么它将不会被 GC 回收,哪怕是 Java 虚拟机抛出 OutOfMemoryError 。

Java内存泄露

 

一般来说内存泄漏有两种情况。一种情况如在C/C++ 语言中的,在堆中的分配的内存,在没有将其释放掉的时候,就将所有能访问这块内存的方式都删掉(如指针重新赋值);另一种情况则是在内存对象明明已经不需要的时候,还仍然保留着这块内存和它的访问方式(引用)。第一种情况,在 Java 中已经由于垃圾回收机制的引入,得到了很好的解决。所以, Java 中的内存泄漏,主要指的是第二种情况。

 

    可能光说概念太抽象了,大家可以看一下这样的例子:
Vector v = new  Vector( 10 );  
for  ( int  i = 1 ;i < 100 ; i ++ ){  
Object o = new  Object();  
v.add(o);  
o = null ;  


在这个例子中,代码栈中存在Vector 对象的引用 v 和 Object 对象的引用 o 。在 For 循环中,我们不断的生成新的对象,然后将其添加到 Vector 对象中,之后将 o 引用置空。问题是当 o 引用被置空后,如果发生 GC ,我们创建的 Object 对象是否能够被 GC 回收呢?答案是否定的。因为, GC 在跟踪代码栈中的引用时,会发现 v 引用,而继续往下跟踪,就会发现 v 引用指向的内存空间中又存在指向 Object 对象的引用。也就是说尽管 o 引用已经被置空,但是 Object 对象仍然存在其他的引用,是可以被访问到的,所以
GC 无法将其释放掉。如果在此循环之后, Object 对象对程序已经没有任何作用,那么我们就认为此 Java 程序发生了内存泄漏。

尽管对于C/C++ 中的内存泄露情况来说, Java 内存泄露导致的破坏性小,除了少数情况会出现程序崩溃的情况外,大多数情况下程序仍然能正常运行。但是,在移动设备对于内存和 CPU都有较严格的限制的情况下, Java 的内存溢出会导致程序效率低下、占用大量不需要的内存等问题。这将导致整个机器性能变差,严重的也会引起抛出 OutOfMemoryError ,导致程序崩溃。

一般情况下内存泄漏的避免

在不涉及复杂数据结构的一般情况下,Java 的内存泄露表现为一个内存对象的生命周期超出了程序需要它的时间长度。我们有时也将其称为“对象游离”。

例如:
public class FileSearch{  
      private byte [] content;  
      private File mFile;  
     public FileSearch(File file){  
      mFile = file;  
      }  
     public boolean hasString(String str){  
         int size = getFileSize(mFile);  
        content =  new  byte [size];  
         loadFile(mFile, content);  
         String s =  new String(content);  
         return s.contains(str);  
     }  


在这段代码中,FileSearch 类中有一个函数 hasString ,用来判断文档中是否含有指定的字符串。流程是先将mFile 加载到内存中,然后进行判断。但是,这里的问题是,将 content 声明为了实例变量,而不是本地变量。于是,在此函数返回之后,内存中仍然存在整个文件的数据。而很明显,这些数据我们后续是不再需要的,这就造成了内存的无故浪费。

要避免这种情况下的内存泄露,要求我们以C/C++ 的内存管理思维来管理自己分配的内存。第一,是在声明对象引用之前,明确内存对象的有效作用域。在一个函数内有效的内存对象,应该声明为 local 变量,与类实例生命周期相同的要声明为实例变量……以此类推。第二,在内存对象不再需要时,记得手动将其引用置空。

复杂数据结构中的内存泄露问题

在实际的项目中,我们经常用到一些较为复杂的数据结构用于缓存程序运行过程中需要的数据信息。有时,由于数据结构过于复杂,或者我们存在一些特殊的需求(例如,在内存允许的情况下,尽可能多的缓存信息来提高程序的运行速度等情况),我们很难对数据结构中数据的生命周期作出明确的界定。这个时候,我们可以使用Java 中一种特殊的机制来达到防止内存泄露的目的。

之前我们介绍过,Java 的 GC 机制是建立在跟踪内存的引用机制上的。而在此之前,我们所使用的引用都只是定义一个“ Object o; ”这样形式的。事实上,这只是 Java 引用机制中的一种默认情况,除此之外,还有其他的一些引用方式。通过使用这些特殊的引用机制,配合 GC 机制,就可以达到一些我们需要的效果。

Java中的几种引用方式

 

Java中有几种不同的引用方式,它们分别是:强引用、软引用、弱引用和虚引用。下面,我们首先详细地了解下这几种引用方式的意义。 

 

强引用

 

在此之前我们介绍的内容中所使用的引用 都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空 间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。

 

软引用(SoftReference )

SoftReference 类的一个典型用途就是用于内存敏感的高速缓存。 SoftReference  的原理是:在保持对对象的引用时保证在  JVM  报告内存不足情况之前将清除所有的软引用。关键之处在于,垃圾收集器在运行时可能会(也可能不会)释放软可及对象。对象是否被释放取决于垃圾收集器的算法 以及垃圾收集器运行时可用的内存数量。

弱引用(WeakReference )

WeakReference 类的一个典型用途就是规范化映射( canonicalized mapping )。另外,对于那些生存期相对较长而且重新创建的开销也不高的对象来说,弱引用也比较有用。关键之处在于,垃圾收集器运行时如果碰到了弱可及对象,将释放  WeakReference  引用的对象。然而,请注意,垃圾收集器可能要运行多次才能找到并释放弱可及对象。

虚引用(PhantomReference )

PhantomReference 类只能用于跟踪对被引用对象即将进行的收集。同样,它还能用于执行  pre-mortem  清除操作。 PhantomReference  必须与  ReferenceQueue  类一起使用。需要  ReferenceQueue  是因为它能够充当通知机制。当垃圾收集器确定了某个对象是虚可及对象时, PhantomReference  对象就被放在它的  ReferenceQueue  上。将  PhantomReference  对象放在  ReferenceQueue 
上也就是一个通知,表明  PhantomReference  对象引用的对象已经结束,可供收集了。这使您能够刚好在对象占用的内存被回收之前采取行动。 Reference与 ReferenceQueue 的配合使用。

GC、 Reference 与 ReferenceQueue 的交互

A、  GC无法删除存在强引用的对象的内存。

B、  GC发现一个只有软引用的对象内存,那么:

①  SoftReference对象的 referent  域被设置为 null ,从而使该对象不再引用 heap 对象。

②  SoftReference引用过的 heap 对象被声明为 finalizable 。

③  当 heap  对象的  finalize()  方法被运行而且该对象占用的内存被释放, SoftReference  对象就被添加到它的  ReferenceQueue (如果后者存在的话)。

C、  GC发现一个只有弱引用的对象内存,那么:

①  WeakReference对象的 referent 域被设置为 null , 从而使该对象不再引用heap 对象。

②  WeakReference引用过的 heap 对象被声明为 finalizable 。

③  当heap 对象的 finalize() 方法被运行而且该对象占用的内存被释放时, WeakReference 对象就被添加到它的 ReferenceQueue (如果后者存在的话)。

D、  GC发现一个只有虚引用的对象内存,那么:

①  PhantomReference引用过的 heap 对象被声明为 finalizable 。

②  PhantomReference在堆对象被释放之前就被添加到它的 ReferenceQueue 。

 

值得注意的地方有以下几点:

1、 GC 在一般情况下不会发现软引用的内存对象,只有在内存明显不足的时候才会发现并释放软引用对象的内存。

2、 GC 对弱引用的发现和释放也不是立即的,有时需要重复几次 GC ,才会发现并释放弱引用的内存对象。
3、软引用和弱引用在添加到 ReferenceQueue 的时候,其指向真实内存的引用已经被置为空了,相关的内存也已经被释放掉了。而虚引用在添加到 ReferenceQueue 的时候,内存还没有释放,仍然可以对其进行访问。

代码示例

通过以上的介绍,相信您对Java 的引用机制以及几种引用方式的异同已经有了一定了解。光是概念,可能过于抽象,下面我们通过一个例子来演示如何在代码中使用 Reference 机制。
String str  =   new  String( " hello " );  // ①   
ReferenceQueue < String >  rq  =   new  ReferenceQueue < String > ();  // ②   
WeakReference < String >  wf  =   new  WeakReference < String > (str, rq);  // ③   
str = null ;  // ④取消"hello"对象的强引用   
String str1 = wf.get();  // ⑤假如"hello"对象没有被回收,str1引用"hello"对象  
// 假如"hello"对象没有被回收,rq.poll()返回null   
Reference <?   extends  String >  ref = rq.poll();  // ⑥  

在以上代码中,注意⑤⑥两处地方。假如“hello ”对象没有被回收 wf.get() 将返回“ hello ”字符串对象, rq.poll() 返回 null ;而加入“ hello ”对象已经被回收了,那么 wf.get() 返回 null , rq.poll() 返回 Reference 对象,但是此 Reference 对象中已经没有 str 对象的引用了 ( PhantomReference 则与WeakReference 、 SoftReference 不同 )。

引用机制与复杂数据结构的联合应用

了解了GC 机制、引用机制,并配合上 ReferenceQueue ,我们就可以实现一些防止内存溢出的复杂数据类型。

例如,SoftReference 具有构建 Cache 系统的特质,因此我们可以结合哈希表实现一个简单的缓存系统。这样既能保证能够尽可能多的缓存信息,又可以保证 Java 虚拟机不会因为内存泄露而抛出 OutOfMemoryError 。这种缓存机制特别适合于内存对象生命周期长,且生成内存对象的耗时比较长的情况,例如缓存列表封面图片等。对于一些生命周期较长,但是生成内存对象开销不大的情况,使用WeakReference 能够达到更好的内存管理的效果。

附SoftHashmap 的源码一份,相信看过之后,大家会对 Reference 机制的应用有更深入的理解。
package  com. *** .widget;  
    // : SoftHashMap.java    
    import  java.util. * ;   
    import  java.lang.ref. * ;   
    import  android.util.Log;  
     
    public   class  SoftHashMap  extends  AbstractMap  {   
      /**  The internal HashMap that will hold the SoftReference.  */    
      private   final  Map hash  =   new  HashMap();   
      /**  The number of "hard" references to hold internally.  */    
      private   final   int  HARD_SIZE;   
      /**  The FIFO list of hard references, order of last access.  */    
      private   final  LinkedList hardCache  =   new  LinkedList();   
      /**  Reference queue for cleared SoftReference objects.  */    
      private  ReferenceQueue queue  =   new  ReferenceQueue();   
      // Strong Reference number   
      public  SoftHashMap()  {  this ( 100 ); }    
      public  SoftHashMap( int  hardSize)  { HARD_SIZE  =  hardSize; }    
       
      public  Object get(Object key)  {   
       Object result  =   null ;   
        //  We get the SoftReference represented by that key    
       SoftReference soft_ref  =  (SoftReference)hash.get(key);   
        if  (soft_ref  !=   null )  {   
          //  From the SoftReference we get the value, which can be   
          //  null if it was not in the map, or it was removed in   
          //  the processQueue() method defined below    
        result  =  soft_ref.get();   
          if  (result  ==   null )  {   
            //  If the value has been garbage collected, remove the   
            //  entry from the HashMap.    
           hash.remove(key);   
         }   else   {   
            //  We now add this object to the beginning of the hard   
            //  reference queue.  One reference can occur more than   
            //  once, because lookups of the FIFO queue are slow, so   
            //  we don't want to search through it each time to remove   
            //  duplicates.   
              // keep recent use object in memory   
           hardCache.addFirst(result);   
            if  (hardCache.size()  >  HARD_SIZE)  {   
              //  Remove the last entry if list longer than HARD_SIZE    
             hardCache.removeLast();   
           }    
         }    
       }    
        return  result;   
     }    
    
      /**  We define our own subclass of SoftReference which contains   
      not only the value but also the key to make it easier to find   
      the entry in the HashMap after it's been garbage collected.  */    
      private   static   class  SoftValue  extends  SoftReference  {   
        private   final  Object key;  //  always make data member final    
        /**  Did you know that an outer class can access private data   
        members and methods of an inner class?  I didn't know that!   
        I thought it was only the inner class who could access the   
        outer class's private information.  An outer class can also   
        access private members of an inner class inside its inner   
        class.  */    
        private  SoftValue(Object k, Object key, ReferenceQueue q)  {   
          super (k, q);   
          this .key  =  key;   
       }    
     }    
    
      /**  Here we go through the ReferenceQueue and remove garbage   
      collected SoftValue objects from the HashMap by looking them   
      up using the SoftValue.key data member.  */    
      public   void  processQueue()  {   
       SoftValue sv;   
        while  ((sv  =  (SoftValue)queue.poll())  !=   null )  {   
            if (sv.get() ==   null ) {  
               Log.e( " processQueue " ,  " null " );  
           } else {  
               Log.e( " processQueue " ,  " Not null " );  
           }   
         hash.remove(sv.key);  //  we can access private data!   
         Log.e( " SoftHashMap " ,  " release  "   +  sv.key);  
       }    
     }    
      /**  Here we put the key, value pair into the HashMap using   
      a SoftValue object.  */    
      public  Object put(Object key, Object value)  {   
       processQueue();  //  throw out garbage collected values first    
       Log.e( " SoftHashMap " ,  " put into  "   +  key);  
        return  hash.put(key,  new  SoftValue(value, key, queue));   
     }    
      public  Object remove(Object key)  {   
       processQueue();  //  throw out garbage collected values first    
        return  hash.remove(key);   
     }    
      public   void  clear()  {   
       hardCache.clear();   
       processQueue();  //  throw out garbage collected values    
      hash.clear();   
    }    
     public   int  size()  {   
      processQueue();  //  throw out garbage collected values first    
       return  hash.size();   
    }    
     public  Set entrySet()  {   
       //  no, no, you may NOT do that!!! GRRR    
       throw   new  UnsupportedOperationException();   
    }   
  }  

原文地址: http://henryyang.iteye.com/blog/1188328
Handle造成内存泄露解决方法:

什么是内存泄露?

Java使用有向图机制,通过GC自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收。也就是说,一个对象不被任何引用所指向,则该对象会在被GC发现的时候被回收;另外,如果一组对象中只包含互相的引用,而没有来自它们外部的引用(例如有两个对象A和B互相持有引用,但没有任何外部对象持有指向A或B的引用),这仍然属于不可到达,同样会被GC回收。

Android中使用Handler造成内存泄露的原因

Handler mHandler = new Handler() {

    @Override

    public void handleMessage(Message msg) {

        mImageView.setImageBitmap(mBitmap);

    }

}

上面是一段简单的Handler的使用。当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用(不然你怎么可能通过Handler来操作Activity中的View?)。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。另外,如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue
-> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。

内存泄露的危害

只有一个,那就是虚拟机占用内存过高,导致OOM(内存溢出),程序出错。对于Android应用来说,就是你的用户打开一个Activity,使用完之后关闭它,内存泄露;又打开,又关闭,又泄露;几次之后,程序占用内存超过系统限制,FC。

使用Handler导致内存泄露的解决方法

方法一:通过程序逻辑来进行保护。

1.在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。

2.如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。

方法二:将Handler声明为静态类。

静态类不持有外部类的对象,所以你的Activity可以随意被回收。代码如下:

static class MyHandler extends Handler {

    @Override

    public void handleMessage(Message msg) {

        mImageView.setImageBitmap(mBitmap);

    }

}

但其实没这么简单。使用了以上代码之后,你会发现,由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference):

static class MyHandler extends Handler {

    WeakReference<Activity > mActivityReference;

    MyHandler(Activity activity) {

        mActivityReference= new WeakReference<Activity>(activity);

    }

    @Override

    public void handleMessage(Message msg) {

        final Activity activity = mActivityReference.get();

        if (activity != null) {

            mImageView.setImageBitmap(mBitmap);

        }

    }

}

将代码改为以上形式之后,就算完成了。

延伸:什么是WeakReference?

WeakReference弱引用,与强引用(即我们常说的引用)相对,它的特点是,GC在回收时会忽略掉弱引用,即就算有弱引用指向某对象,但只要该对象没有被强引用指向(实际上多数时候还要求没有软引用,但此处软引用的概念可以忽略),该对象就会在被GC检查到时回收掉。对于上面的代码,用户在关闭Activity之后,就算后台线程还没结束,但由于仅有一条来自Handler的弱引用指向Activity,所以GC仍然会在检查的时候把Activity回收掉。这样,内存泄露的问题就不会出现了。

相关阅读

Android内存泄漏就这样产生了 http://www.linuxidc.com/Linux/2012-02/52952.htm

如何避免Android内存泄漏 http://www.linuxidc.com/Linux/2012-02/52951.htm

Android内存管理的原理--进程管理 http://www.linuxidc.com/Linux/2011-09/43556.htm

更多Android相关信息见Android 专题页面 http://www.linuxidc.com/topicnews.aspx?tid=11
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: