当前位置: 首页 > 科技观察

Android内存泄漏案例及分析

时间:2023-03-17 20:17:03 科技观察

Android编程中使用的Java是一种使用垃圾收集器(GC,garbagecollection)来自动管理内存的语言,这使得我们不再需要手动调用代码进行内存回收。那么它是如何判断的呢?简单地说,如果一个对象从它的根节点不可达,那么这个对象就没有引用,就会被垃圾收集器回收。所谓“根节点”往往就是一个线程,比如主线程。线。因此,如果一个对象从它的根节点开始可达并被引用,但实际上已经不再使用,没有用了,这样的对象就是内存泄漏对象,它会占用我们在内存中的应用程序没有很多内存,导致程序变慢,甚至内存溢出(OOM)程序崩溃。内存泄漏的原因并不难理解,但是即使我们知道它的存在,我们也常常在不知情的情况下编写导致内存泄漏的代码。在Android编程中,也有很多场景很容易导致内存泄露。下面将我所知道的一些内存泄漏案例一一列举。从这些例子中,应该可以更直观的了解到内存泄漏是如何造成的,从而在编程过程中尽量避免。.静态变量导致内存泄漏首先,一个比较简单的情况是静态变量导致内存泄漏。说到静态变量,我们至少要了解它们的生命周期才能完全理解。静态变量的生命周期从类的加载开始,到类的释放结束。对于Android,程序也是从一个main方法进入,开始主线程的工作。如果在主线程或侧分支中使用了一个类,它将被加载。反之,如果一个类存在于我们的项目中,但从来没有被我们使用过,它就是一个孤岛,此时并没有被加载。一旦加载,直到我们的Android应用程序进程结束,它才会被卸载。因此,当我们在Activity中声明一个引用Activity本身的静态变量时,会造成内存泄漏:@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_leak);这;}}这样的代码会导致当Activity结束时,sContext仍然持有它的引用,使得Activity无法被回收。解决方法是在这个Activity的onDestroy期间清空sContext的值,或者避免使用静态变量。同样,如果一个Activity的静态字段变量在内部获得了对当前Activity的引用,例如,我们通常会将其传递给View等对象。如果这个对象是静态的并且在Activity生命周期结束前不为空,也会导致同样的问题。非静态内部类和匿名内部类引起的内存泄漏也是很常见的情况。经常遇到的Handler问题就是这样一种情况。如果我们在字段中声明一个Handler变量:}};因为在Java中,非静态内部类(包括匿名内部类,比如这个Handler匿名内部类)会引用外部类对象(比如Activity),而静态内部类则不会引用外部类的对象。所以这里的Handler会引用Activity对象。当它使用postDelayed时,如果Activity已经结束,而handler仍然引用Activity,就会造成内存泄漏,因为handler会继续被主Looper持有一段时间,导致引用仍然存在。在此期间,如果内存紧到超过,那就很危险了。解决方法就是大家知道的使用静态内部类加WeakReference:privateStaticHandlermHandler=newStaticHandler(this);publicstaticclassStaticHandlerextendsHandler{privatefinalWeakReferencemActivity;publicStaticHandler(Activityactivity){mActivity=newWeakReference(activity);}@OverridepublicvoidhandleMessage(Messagemsg){super.handleMessage(msg);另外,结合以上两种情况,如果一个变量既是静态变量又是非静态内部类对象,也会造成内存泄漏:@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_leak);sHello=newHello();}publicclassHello{}}注意我们这里定义的Hello虽然是空的,但是它是一个非静态的内部类,所以必须持有外部类LeakActivity.this的引用,导致静态变量sHello一直持有Activity,所以结果和第一个例子一样,Activity不能被回收。看到这里应该可以看出,内存泄漏往往和静态变量有关。和静态变量相关,还有一种常见的情况,就是使用单例模式,不解绑定导致内存泄漏。单例模式的对象往往和我们的应用有相同的生命周期。如果我们使用EventBus或Otto并生成一个单例,注册了一个Activity但没有在页面末尾取消注册,那么单例将一直持有我们的Activity。这个Activity虽然不用,但是会一直占用内存。属性动画会导致内存泄漏。另外,我们在使用属性动画的时候,需要调用一些方法来停止动画,尤其是有循环的动画,否则也会造成内存泄漏。幸运的是,在使用View动画时不会出现内存泄漏。估计是View里面有release和stops。RxJava使用不当导致内存泄漏***说说RxJava使用不当导致的内存泄漏,RxJava是一个非常好用优雅的异步操作库。对于异步操作,如果没有及时取消订阅,会造成内存泄漏:Observable.interval(1,TimeUnit.SECONDS).subscribe(newAction1(){@Overridepublicvoidcall(LongaLong){//pass}});还有就是匿名内部类导致的引用无法释放,这样如果在Activity中使用,是不会被回收的,即使我们的Action1看起来什么都没做。解决方法是接收subscribe返回的Subscription对象,在ActivityonDestroy时取消订阅:@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity)mObservable.interval(1,TimeUnit.SECONDS).subscribe(newAction1(){@Overridepublicvoidcall(LongaLong){//pass}});}@OverrideprotectedvoidonDestroy(){super.onDestroy();mSubscription.unsubscribe();除了上述方案,还有一种方案就是通过RxJava的compose操作符来hookActivity的生命周期。我们可以使用一个非常方便的第三方库RxLifecycle来快速完成这件事。像这样使用它:publicclassMyActivityextendsRxActivity{@OverridepublicvoidonResume(){super.onResume();myObservable.compose(bindToLifecycle()).subscribe();另外,它还提供了与View的便捷绑定,具体可以点击我提供的链接进行了解,这里不多说。综上所述,还是上面说的内部类或者匿名内部类引用外部类导致内存泄漏,所以在实际的编程过程中,如果涉及到此类问题或者线程操作,就要非常小心,可能是不自觉的有内存泄漏的代码写在.内存泄漏的检测上面已经提到了很多内存泄漏的场景和对应的??解决方案,但是如果我们在不知不觉中写出了隐藏内存泄漏的代码怎么办呢?面对这个问题,其实直到现在,我们还是幸运的,因为有很多相关的检查方法或者组件可以选择,比如最简单的:观察MemoryMonitor内存趋势图,就可以大致了解内存情况,但是如果你想准确追踪到内存泄漏点,这里特别推荐大Square公司开源的LeakCanary解决方案。LeakCanary可以用一种非常简单、方便和低侵入的方式捕获内存泄漏代码。在很多情况下,您甚至可以捕获官方Android组件的内存泄漏代码。具体使用可以参考它的使用说明,由于这篇文章主要是想说说内存泄漏的原因和一些常见的场景,检测这里就不多说了