Android 源码系列之<十二>从源码的角度深入理解LeakCanary的内存泄露检测机制(上)

        转载请注明出处:http://blog.csdn.net/llew2011/article/details/52089842

        提到内存泄露有的小伙伴们可能遇见过,处理起来是非常棘手的。记得刚从事Android开发那会手机主流版本还是2.2的,手机内存普遍都很小,开发的时候在处理耗用内存过大的对象(比如Bitmap等)上,稍有不慎就会出现OOM,这很让人头疼,更为头疼的是发版后还是会收到有关OOM的反馈。为了解决OOM的问题就反复查代码捋逻辑,然后借助MAT工具来分析可能引发的内存泄露点,但是MAT使用起来很麻烦……直到去年著名的开源组织square在github上开源了著名的内存泄露检测库LeakCanary,它是专门用来检测内存泄露的,有了这个库后告别了MAT工具,更给力的是在程序中用一行代码就搞定了,该库从开源至今是最受欢迎的开源库之一。用了该库那么久总该有点贡献吧?因此我打算写篇文章来帮助小伙伴们从源码的角度深入理解一下LeakCanary的内存泄露检测机制,如果你对该库十分熟悉,请跳过本文(*^__^*) ……

        在分析LeakCanary源码之前,我们有必要先来了解一下Java的内存分配策略以及Android中常见的内存泄漏。

Java内存分配策略

        一、Java程序运行时的内存分配策略有三种,它们分别是静态分配,栈分配和堆分配;对应的三种存储策略所使用的内存空间分别是静态存储区、栈存储区和堆存储区。
  • 静态存储区
            静态存储区主要存放静态数据,全局static数据和常量。静态存储区在程序编译时就已经分配好,在程序整个运行期间都存在。
  • 栈存储区
            栈存储区主要存放方法体内的局部变量。当一个方法被执行时,该方法体内的局部变量在栈存储区内创建,方法执行结束后局部变量所持有的内存将会自动被释放。
  • 堆存储区
            堆存储区通常就是指在程序运行时通过关键字new出来的内存区,这部分内存在不使用时将会由Java垃圾回收器来负责回收。

        二、堆与栈的区别

  • 在方法体内定义的一些基本类型的变量和对象的引用变量都称为局部变量,这些局部变量在栈内存中分配空间。当在一个方法块中定义一个变量时,Java就会在栈中为该变量分配内存空间,当超过该变量的作用域后,该变量也就无效了,分配给它的内存空间也将被释放掉,该内存空间可以被重新使用。
  • 堆内存用来存放所有由new关键字创建的对象(包括该对象中的所有成员变量)和数组。在堆中分配的内存,将由Java垃圾回收器来自动管理。在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存的首地址,这个特殊的变量就是我们常说的引用变量。我们可以通过这个引用变量来访问堆中的对象或者数组。

Java如何管理内存

        Java的内存管理简单理解就是对象的分配和释放。在Java中对象的内存分配是通过关键字new来进行分配的(注意:基本类型除外),这些对象在堆(Heap)存储区中分配空间,而对象的释放是由GC来决定和进行回收的。在Java的世界里,内存的分配是由程序完成的,而内存的释放是由GC完成的,这种分配和释放两条线的方法简化了程序的工作,但是也加重了JVM的工作,这也是Java程序运行速度较慢得原因之一,因为GC为了能够正确的释放对象,它必须要监控每一个对象的运行状态(包括对象的申请、引用、被引用、赋值等),当GC发现一个对象不再被引用了,那么GC就会认为该对象可以被回收。
        为了更好的理解GC的工作原理,我们可以把对象假设为有向图的顶点,把引用关系看做是有向图的边,有向图的边从引用者指向被应用对象。另外,每个线程对象都可以看做一个有向图的起始点,例如大多数程序都是从main()进程开始执行,那么该图就是以main()进程顶点开始的一颗根树,在这个有向图中,根顶点可以到达的对象都是有效对象,GC将不会回收这些对象;如果某个对象与根节点不可到达,那么我们就认为这个对象将不再被引用,可以被GC回收。如下图所示:

        如上图所示,GC会选择一些它了解还存活的对象作为内存遍历的根节点(GC Roots),比如说thread stack 中的变量,JNI中的全局变量,zygote中的对象(class loader)等,然后开始对heap进行遍历。到最后,部分没有直接或者间接引用到GC Roots的就是需要回收的垃圾,就会被GC回收掉,如下图蓝色部分:

        Java使用有向图的方式进行内存管理,这样可以消除循环引用的问题,例如有三个对象A、B和C,在A中引用了B,在B中引用了C,在C中应用了A,只要它们和根进程不可到达,那么GC也是可以回收它们的,这种方式的优点是管理内存的精度很高,但是效率较低。另外一种常用的内存管理技术是使用计数器,例如COM模型采用计数器方式管理构建,它与有向图相比,精度很低(很难处理循环引用的问题),但是执行效率很高。

Android的内存溢出

        Android的内存溢出是如何发生的了?我们知道Android的虚拟机是基于寄存器的Dalvik,在不设置largeHeap的情况他它的最大堆内存空间为16M,有的设备为24M,根据不同的手机厂商该值是不同的,在设置了largeHeap的情况下该值会更大些但是也是有限制的,因此我们APP所能利用的内存空间是有限的。如果我们APP的内存超过了警戒线Android系统就会抛出OutOfMemory的异常错误,为什么会出现OOM的异常错误了?根据我们的开发经验主要原因有两个:一个是我们程序的bug导致资源得不到释放造成内存泄露(比如长期保持对某些资源的引用);另一个是保存了多个耗用内存过大的对象(比如Bitmap等)。

Android常见内存泄露

  • Context造成的内存泄漏
            Context作为应用的上下文环境,它提供了一系列访问的接口以及一些应用层的操作,例如:startActivity()、sendBroadcast()等,所以在很多时候我们都需要依赖Conetxt来访问相关资源数据,通常情况是把它作为参数传递到某一个对象中,以供该对象使用。因为Activity是Context的实现类,所以有时候为了方便我们就直接传递当前Activity作为Context来使用,但是这样做会有一个很大的内存泄漏风险,比如在项目中用到了第三方的库(友盟,Adapter等),在这些第三方库中我们并不知道传入到这些库中他们是怎么处理这个Context的,假如他们使用了静态指针来保存传入的Context对象,这样就造成了内存泄漏,所以最好的方式是我们传入一个与我们应用同生命周期的一个Context对象,而我们知道Application不仅是Context的实现类而且它的生命周期和应用的生命周期是相同的,所以我们在用到有用Context作为参数进行传递的时候尽量使用Application对象(Dialog除外),这样避免造成内存泄漏。
  • 静态变量造成的内存泄漏
            我们先看一个由静态变量造成内存泄漏的例子:
    public class MainActivity extends Activity {
    
    	private static Drawable mBackgroundDrawable;
    	
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		
    		TextView label = new TextView(this);
    		label.setText("Leaks are bad !");
    		if (null == mBackgroundDrawable) {
    			mBackgroundDrawable = getResources().getDrawable(R.id.bg);
    		}
    		label.setBackgroundDrawable(mBackgroundDrawable);
    		setContentView(label);
    	}
    }
    
           MainActivity中静态成员变量mBackgroudDrawable缓存了drawable对象,这种写法在Android 3.0之前会导致MainActivity销毁后无法被系统回收,因为在Android3.0之前View的setBackgroundDrawable()方法的源码如下所示:
    public void setBackgroundDrawable(Drawable background) {
        
        // ...... 此处省略部分代码 ......
    
        background.setCallback(this);
    
        // ...... 此处省略部分代码 ......
    }
           根据源码可知background对当前View保持了一个引用,而View又对当前的Activity保持了一个引用,当退出当前Activity时该Activity本该释放,但是因为mBackgroundDrawable是静态成员变量,它的生命周期是伴随着整个应用程序的,它间接持有了Activity的应用从而导致Activity对象不能被释放,进而导致了内存泄露。在Android3.0之前Drawable的setCallback()方法是这样的:
    public final void setCallback(Callback cb) {
        mCallback = cb;
    }
           但是在Android3.0之后写法是这样的:
    public final void setCallback(Callback cb) {
        mCallback = new WeakReference<Callback>(cb);
    }
    // 下边是Android7.0版本写法
    public final void setCallback(@Nullable Callback cb) {
        mCallback = cb != null ? new WeakReference<>(cb) : null;
    }
            由此可知在Android3.0之后Drawable在setCallback()方法中使用了软引用,把内存泄露的问题修复了。
           【注意:】在程序中尽量避免使用static关键字。
  • 非静态内部类造成的内存泄漏
           非静态内部类也常常会造成内存泄露,请看下边一个经典的例子:
    public class MainActivity extends Activity {
        
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            new Thread() {
                @Override
                public void run() {
                    while(true) {
                        try {
                            sleep(10000);
                            // 执行长时间的耗时任务
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                };
            }.start();
        }
    }
    
           在上述例子中当MainActivity执行destroy()后因为new出来的Thread还在执行,同时Thread为MainActivity的匿名内部类,会持有MainActivity对象的引用(详细可以通过查看字节码求证)造成MainActivity不会被垃圾回收器回收,从而造成内存泄露。解决办法可通过静态内部类的方式:
    public class MainActivity extends Activity {
        
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            new InnerTask().start();
        }
        
        private static class InnerTask extends Thread {
            @Override
            public void run() {
                while(true) {
                    try {
                        sleep(10000);
                        // 执行长时间的耗时任务
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
           由于静态内部类不持有外部类的引用,与独立类文件形式的类没有本质区别。上面的例子需要注意的是在Activity退出时如果没有必要应该将线程stop掉。
  • Handler造成的内存泄漏
           使用Handler不当往往也会造成内存泄露,先看下面的一个例子:
    public class MainActivity extends AppCompatActivity {
    
        private final Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                // do work
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    }
    
           以上例子咋一看并没有什么问题,但是Android Lint时会提示我们一个这样的warning:In Android,Handler classes should be static or leaks might occur。这个意思就是说在Android中,Handler类应该是静态的否则可能会发生内存泄露。
           为什么会是这样呢?这还要从Handler说起,我们都知道当Android应用程序启动的时候在ActivityThread的main()方法中通过调用Looper.prepare()方法为当前主线程创建了一个Looper对象,Looper的主要作用就是为当前线程提供消息队列以及不断的循环处理Message对象。程序启动后所有主要的事件(例如:屏幕上的点击事件,Activity生命周期的方法等等)都包含在Message对象中,然后添加到Looper的消息队列中一个个的处理,主线程的Looper存在整个应用程序的生命周期内。
           当在主线程创建一个Handler对象时,它会关联到主线程的Looper的Message Queue,当Message被添加到Message Queue的时候会持有Handler的引用,当Looper处理到当前Message的时候会调用Handler的handleMessage()方法。在Java中非静态内部类会隐式的持有外部类的一个引用,因此当通过Handler来send一个Message的时候,若Looper还没有处理到该Message,那该Message就会持有Handler的引用而Handler又持有了当前Activity的引用,最终会导致Activity的所有资源在这个消息处理之前都不能释放,所以会造成内存泄露。例子如下所示:
    public class MainActivity extends AppCompatActivity {
    
        private final Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                // do work
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    // do work
                }
            }, 600000);
    
            finish();
        }
    }
           在上面的代码中当MainActivity结束后,在Message Queue处理这个Message之前,MainActivity会持续存活。由于Message持有Handler的引用而Handler又持有MainActivity的引用,所以就导致MainActivity的资源在Message被处理之前都不能被回收,所以会引发内存泄露。解决方法有以下几种:
           解决办法一:
    public class MainActivity extends AppCompatActivity {
    
        private final InnerHandler mHandler = new InnerHandler(this);
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    // do work
                }
            }, 600000);
    
            finish();
        }
    
        private static class InnerHandler extends Handler {
            private final WeakReference<MainActivity> mainActivityWeakReference;
    
            public InnerHandler(MainActivity activity) {
                this.mainActivityWeakReference = null == activity ? null : new WeakReference<MainActivity>(activity);
            }
    
            @Override
            public void handleMessage(Message msg) {
                if (null != mainActivityWeakReference) {
                    MainActivity mainActivity = mainActivityWeakReference.get();
                    if (null != mainActivity) {
                        // do work
                    }
                }
            }
        }
    }
           解决方法二:
    public class MainActivity extends AppCompatActivity {
    
        private static final Runnable TASK = new Runnable() {
            @Override
            public void run() {
                // do work
            }
        };
        private final Handler mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // do work
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mHandler.postDelayed(TASK, 600000);
    
            finish();
        }
    }
           解决方式三:
    public class MainActivity extends AppCompatActivity {
    
        private final Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                // do work
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    // do work
                }
            }, 600000);
    
            finish();
        }
    
        @Override
        protected void onDestroy() {
            if (null != mHandler) {
                mHandler.removeCallbacksAndMessages(null);
            }
            super.onDestroy();
        }
    }
            方法一和方法二同样采用了内部静态类的方式,方法二虽然避免了MainActivity的内存泄露,但是它静态化一个Runnable静态对象,对于这个静态对象在MainAcitvity销毁后并没有什么用处,也属于一种小范围的内存泄露,所以建议采用第一种方式解决。解决方式三在MainActivity的onDestroy()方法中调用mHandler的removeCallbacksAndMessage()方法移除消息队列中那些还没有被执行的Message对象防止间接持有MainActivity的引用而造成内存泄露。综上所述建议采用方法一和方法三结合的方式,从根本上解决Handler造成的内存泄露。
  • HandlerThread造成的内存泄漏
           HandlerThread使用不当也会造成内存泄露,因为HandlerThread实现的run()方法是一个无限循环,它不会自己结束,因此ThreadHandler的生命周期超过了Activity的生命周期,应该在Activity的onDestroy()时将HandlerThread线程停止掉,方法是mHanderThread.getLooper().quit()。
           对于非HandlerThread的子线程也应该确保Activity在销毁前已经停止,例如在Activity的onDestroy()方法中调用mThread.join()方法。如果你对HandlerThread不是很了解,可以看我之前写的一篇文章:Android 源码系列之<七>从源码的角度深入理解IntentService及HandlerThread 。
  • 注册对象后未反注册造成的内存泄漏
           提起注册与反注册,最常见的就是注册广播接收器,注册观察者等等;例如当我们注册了一个Receiver后一定要调用unregisterReceiver()取消注册。
    registerReceiver(new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // ... 省略 ...
        }
    }, filter);
           上边注册了一个广播接收器,这种通过匿名内部类注册了广播接收器是个很严重的错误,它会导致注入的广播接收器不会被unregister而造成内存泄露。
  • 集合没有清理造成的内存泄漏
           集合没有清理往往也会造成内存泄露,我们通常会把一些对象的引用加入到集合中,当我们不需要该对象时,如果没有把它的引用从集合中清理掉,这样这个集合装载的对象引用就越来越多,占用内存也会越来越多……假如这个集合对象是static的话情况就会更严重。举个例子:
    public class MainActivity extends AppCompatActivity {
    
        private ValueAnimator mValueAnimator;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_login);
            mValueAnimator = ValueAnimator.ofInt(0, 1);
            mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    // do some work
                }
            });
            mValueAnimator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {
                    // do some work
                }
    
                @Override
                public void onAnimationEnd(Animator animation) {
                    // do some work
                }
    
                @Override
                public void onAnimationCancel(Animator animation) {
                    // do some work
                }
    
                @Override
                public void onAnimationRepeat(Animator animation) {
                    // do some work
                }
            });
            mValueAnimator.setInterpolator(new LinearInterpolator());
            mValueAnimator.setDuration(3000);
            mValueAnimator.start();
        }
    }
           上边的例子看起来也没啥问题,可能我们也都这样写过,但是这确实存在内存泄露的风险,mValueAnimator中设置了两个内部匿名监听类,这俩内部匿名类默认持有MainActivity的引用,但是在mValueAnimator中设置监听器是这样处理的:
    public void addUpdateListener(AnimatorUpdateListener listener) {
        if (mUpdateListeners == null) {
            mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
        }
        mUpdateListeners.add(listener);
    }
    
    public void addListener(AnimatorListener listener) {
        if (mListeners == null) {
            mListeners = new ArrayList<AnimatorListener>();
        }
        mListeners.add(listener);
    }
           以上是Animator设置监听器的源码,根据源码我们知道监听器的设置最终是存储在集合中的,那也就是说在MainActivity中的mValueAnimator间接持有了MainActivity的引用,会造成内存泄露的风险,应当在MainActivity的onDestroy()方法中清除监听器,如下所示:
    @Override
    protected void onDestroy() {
        if (null != mValueAnimator) {
            mValueAnimator.removeAllUpdateListeners();
            mValueAnimator.removeAllListeners();
            mValueAnimator = null;
        }
        super.onDestroy();
    }
            这样就避免了内存泄露的风险。
  • 资源对象没有关闭造成的内存泄漏
            资源对象比如Cursor,File等往往都有用一些缓冲操作,在我们不使用的时候应该及时关闭它们,以便它们的缓冲所占用的内存被及时回收,它们的缓冲不仅存在于Java虚拟机内,还会存在于Java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄漏。所以在用完有关资源的时候一定要手动关闭。
  • WebView造成的内存泄露
            对WebView打交道较多的童靴应该清楚WebView很容易造成内存泄露,下边是我遇见的其中一个例子:
    10-24 23:19:08.360: E/BufferQueue(228): [com.**.**] dequeueBuffer: can't dequeue multiple buffers without setting the buffer count
    10-24 23:19:08.360: E/Surface(14162): dequeueBuffer failed (Invalid argument)
    10-24 23:19:08.360: E/ViewRootImpl(14162): Could not lock surface
    10-24 23:19:08.360: E/ViewRootImpl(14162): java.lang.IllegalArgumentException
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.view.Surface.nativeLockCanvas(Native Method)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.view.Surface.lockCanvas(Surface.java:246)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.view.ViewRootImpl.drawSoftware(ViewRootImpl.java:2513)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.view.ViewRootImpl.draw(ViewRootImpl.java:2487)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2331)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1961)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1060)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5782)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.view.Choreographer$CallbackRecord.run(Choreographer.java:761)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.view.Choreographer.doCallbacks(Choreographer.java:574)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.view.Choreographer.doFrame(Choreographer.java:544)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:747)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.os.Handler.handleCallback(Handler.java:733)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.os.Handler.dispatchMessage(Handler.java:95)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.os.Looper.loop(Looper.java:136)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.app.ActivityThread.main(ActivityThread.java:5086)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at java.lang.reflect.Method.invokeNative(Native Method)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at java.lang.reflect.Method.invoke(Method.java:515)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:893)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:702)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at dalvik.system.NativeStart.main(Native Method)
            有关WebView造成内存泄露的问题请查看https://code.google.com/p/android/issues/detail?id=63738或者https://code.google.com/p/android/issues/detail?id=71742,所以要在Activity或者Fragment中合理的销毁WebView,如下所示:
    @Override
    protected void onDestroy() {
        if (null != webView) {
            ViewParent viewParent = webView.getParent();
            if (viewParent instanceof ViewGroup) {
                ViewGroup parent = (ViewGroup) viewParent;
                parent.removeView(webView);
                webView.destroy();
            }
        }
        super.onDestroy();
    }
    测试一下看看
  • 不良代码造成内存压力
    ①、Bitmap使用不当
           Bitmap比较消耗内存,虽然系统能够最终销毁为Bitmap分配的内存,但是在Bitmap使用完成后应该及时的recycle掉。recycle并不能确定立即会将Bitmap释放掉,但是会给虚拟机一个暗示:该图片可以释放了。
    public void recycleBitmap(Bitmap bitmap) {
        if (null != bitmap && !bitmap.isRecycled()) {
            bitmap.recycle();
            bitmap = null;
        }
    }
    ②、构造Adapter时没有使用缓存的ConvertView
           以构造ListView的BaseAdapter为例,在BaseAdapter中提供了方法public View getView(int position, View convertView, ViewGroup parent)来向ListView提供每一个item所需要的View对象,初始时ListView会从BaseAdapter中根据当前屏幕布局实例化一定数量的View对象,同时ListView会将这些View对象缓存起来。当向上滚动ListView时,原先位于最上面的ListView的item对象会被回收,然后用来构造新出现的最下面的ListView的item。这个构造过程就是由getView()方法完成的,getView()方法的第二个形参convertView就是被缓冲起来的View对象。
           由此可以看出如果我们不使用convertView而是每次都在getView()方法中重新实例化一个View对象的话,会造成不必要的内存开销,所以要使用缓存的conertView。
    ④、单例造成内存压力
           单例也可能造成内存压力,单例模式在开发中应该是设计模式中使用频率最多的了,单例对象的生命周期也是伴随着应用的生命周期的,如果我们在单例对象中使用集合来缓存一些对象的引用,那么在缓存对象使用之后应当从缓存中删除,否则单例对象会持续持有缓存对象的引用,造成那些使用过的缓存对象资源得不到释放,占用内存造成不必要的内存开销,从而造成内存压力。
        以上简单的介绍了Java内存分配机制以及Android开发中常见的内存泄露情形。我们在平时开发中要细心谨慎,尽量避免不恰当的代码造成程序出现内存泄露最终导致应用crash,由于篇幅原因,我将在下篇文章  Android 源码系列之<十三>从源码的角度深入理解LeakCanary的内存泄露检测机制(中)中向小伙伴们介绍如何使用LeakCanary来检查代码中出现的内存泄露,敬请期待!!!最后感谢收看(*^__^*) ……


       【参考文章:】
       http://blog.csdn.net/gemmem/article/details/13017999

       http://blog.csdn.net/gemmem/article/details/8920039






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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值