Android内存优化-内存泄漏的几个场景以及解决方式

转自:http://blog.csdn.net/a910626/article/details/50849760

一.什么是内存泄漏

在Java程序中,如果一个对象没有利用价值了,正常情况下gc是会对其进行回收的,但是此时仍然有其他引用指向这个活在堆内存中的对象,那么gc就不会认为这个对象是一个垃圾,那么就不会对其进行回收,所以它会一直活在堆内存中占用内存,这就导致了内存泄漏。

总结一下,导致内存泄漏的原因就是有一些我们永远不会使用的对象,仍然有引用指向它(当然这是在强引用的情况下),那么就不满足gc回收的条件,从而一直活在堆内存中导致内存泄漏,这样的对象多了占用大量内存就会导致App发生oom。

举几个例子:比如使用EventBus,肯定是要执行register(),那么在Fragment或Activity finish的时候,一定不要忘记执行unregister()方法。

二.内存泄漏的常见场景以及解决方式

1.Activity中的Handler长期持有activity引用导致activity泄漏
public class MainActivity extends AppCompatActivity {

    private final Handler myHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            //doSomething
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        myHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                //doSomething
            }
        },60*10*1000);
    }
}

由于myHandler延时10分钟就会发送一条消息,当activity finish之后,延时发送的消息会在主线程的消息队列中存活10分钟直到被looper拿到然后给到handler处理。此消息(new Runnable)隐式持有其外部类handler的引用,myHandler又隐式的持有其外部类Activity的引用,直到消息被处理完之后,这个引用都不会被释放。因此Activity即使finish,但仍然不会被gc回收。
引用的顺序MessageQueue->Message->Runnable->Handler->Activity,从这个引用链得到Activity与MessageQueue关联,所以Activity对象不能被gc回收,从而导致内存泄漏。

解决方式:
为了解决Handler隐式的持有外部类引用,我们应当将Handler定义在一个新文件或在Activity中使用静态内部类。因为静态内部类不会持有外部类的引用,这样当Activity finish时,Handler不会持有Activity的引用就不会导致Activity内存泄漏。如果需要在Handler内部调用外部Activity的方法,正确的做法是让Handler持有一个Activity的弱引用(WeakReference),这样当gc扫描的时候,这个弱引用的对象就会被回收。
解决了Handler隐式持有外部类Activity引用,Runnable在之前的代码中作为匿名内部类隐式持有Handler引用,所以我们在Activity内部定义一个静态变量引用Runnable类,这是因为匿名类的静态实例不会隐式持有他们外部类的引用。

public class MainActivity extends AppCompatActivity {

    private final MyHandler mHandler = new MyHandler(this);

    private static Runnable sRunnable = new Runnable() {
        @Override
        public void run() {
            //doSomething
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mHandler.postDelayed(sRunnable, 60 * 10);

        this.finish();
    }

    private static class MyHandler extends Handler {

        private final WeakReference<MainActivity> mActivity;

        public MyHandler(MainActivity activity) {
            this.mActivity = new WeakReference<MainActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = mActivity.get();

            if (activity != null) {
                //doSomething
            }

        }
    }
}
或者

我们也可以这样做:
在Activity的onDestroy方法中干掉handler中所有的callback和message:

@Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
2.非静态匿名内部类造成内存泄漏

在Android中最常见的操作就是当有耗时操作的时候我们不能在主线程执行这些操作,否则有可能造成ANR,主线程主要是UI操作的主战场。
比如网络请求或者数据库查询这些耗时操作我们需要自己另外开启线程,在子线程中执行这些耗时操作。当我们需要开启的子线程比较少的时候,直接new Thread(Runnable)就可以了。如果你经常这样做的话就说明你没有注意到有可能会产生内存泄漏的问题。
如果Activity结束了,而Thread还在跑,同样会导致Activity内存泄漏,这是因为new Thread作为非静态内部类对象都会隐式持有一个外部类对象的引用,我们所创建的线程就是Activity中的一个内部类,持有Activity对象的引用,所以当Activity 结束了,而子线程还在跑就会导致Activity内存泄漏。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        testThread();
    }

    private void testThread() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    SystemClock.sleep(1000);
                }
            }
        }).start();
    }

}

new Thread()是匿名内部类,且非静态。所以会隐式持有外部类的一个引用,只要非静态匿名类对象没有被回收,Activity就不会被回收。

解决方式:
同样把Thread定义为静态的内部类,这样就不会持有外部类的引用。

3.单例+依赖注入

LeakActivity.java

public class LeakActivity extends AppCompatActivity {

    private TestManager testManager = TestManager.getInstance();
    private MyListener listener=new MyListener() {
        @Override
        public void doSomeThing() {}
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        testManager.registerListener(listener);
    }

}

TestManager.java

public class TestManager {

    private static final TestManager INSTANCE = new TestManager();
    private MyListener listener;

    public static TestManager getInstance() {
        return INSTANCE;
    }

    public void registerListener(MyListener listener) {
        this.listener = listener;
    }
    public void unregisterListener() {
        listener = null;
    }
}

interface MyListener {
    void doSomeThing();
}

在LeakActivity中TestManager.getInstance()创建对象实例,TestManager中采用单例模式返回一个Testmanager实例变量。
引用链:TestManager->listener->Activity

TestManager中的实例变量是static静态变量,静态变量和类的生命周期是一样的。类加载的时候,静态变量就被加载,类销毁时,静态变量也会随之销毁。
因为INSTANCE是一个单例,所以和app的生命周期是一样的。当app进程销毁时,堆内存中的INSTANCE对象才会被释放,INSTANCE的生命周期非常的长。
而又可以看到代码中Activity里面创建了listener非静态内部类,所以listener就持有外部类Activity的引用。随着testManager.registerListener(listener)执行,TestManager中的listener就持有Activity中listener对象,由此形成了一个引用链。关键在于INSTANCE是一个静态变量,往往Activity finish的时候,INSTANCE还活着,而INSTANCE依然持有Activity的引用,所以造成了Activity内存泄漏。

所以,解决方式:要在Activity的onDestroy()方法中注销注册的listener

@Override
    protected void onDestroy() {
        testManager.unregisterListener();
        super.onDestroy();
    }

将TestManager中listener与Activity中的listener关联断开。

三.总结

出现内存泄露的主要原因是生命周期的不一致造成的:在Android中,长时间运行的任务和Acyivity生命周期进行协调会有点困难,如果你不加以小心的话会导致内存泄漏。
内存泄漏的主要原因在于一个生命周期长的东西间接引用了一个生命周期短的东西,会造成生命周期短的东西无法被回收。反过来,如果是一个生命周期短的东西引用了一个生命周期长的东西,是不会影响生命周期短的东西被回收的。
  对象都是有生命周期的,对象的生命周期有的是进程级别的,有的是Activity所在的生命周期,随Activity消亡;有的是Service所在的生命周期,随Service消亡。很多情况下判断对象是否合理存在的一个很重要的理由就是它实际的生命周期是否符合它本来的生命周期。很多Memory Leak的发生,很大程度上都是生命周期的错配,本来在随Activity销毁的对象变成了进程级别的对象,Memory Leak就无法避免了。

四.避免内存泄漏的一些技巧

  1. 使用静态内部类/匿名类,不要使用非静态内部类/匿名类.非静态内部类/匿名类会隐式的持有外部类的引用,外部类就有可能发生泄漏。而静态内部类/匿名类不会隐式的持有外部类引用,外部类会以正常的方式回收,如果你想在静态内部类/匿名类中使用外部类的属性或方法时,可以显示的持有一个弱引用。
  2. 不要以为Java永远会帮你清理回收正在运行的threads.在上面的代码中,我们很容易误以为当Activity结束销毁时会帮我们把正在运行的thread也结束回收掉,但事情永远不是这样的!Java threads会一直存在,只有当线程运行完成或被杀死掉,线程才会被回收。所以我们应该养成为thread设置退出逻辑条件的习惯。
  3. 适当的考虑下是否应该使用线程.Android应用框架设计了许多的类来简化执行后台任务,我们可以使用与Activity生命周期相关联的Loaders来执行简短的后台查询任务。如果一个线程不依赖与Activity,我们还可以使用Service来执行后台任务,然后用BroadcastReceiver来向Activity报告结果。另外需要注意的是本文讨论的thread同样使用于AsyncTasks,AsyncTask同样也是由线程来实现,只不过使用了Java5.0新增并发包中的功能,但同时需要注意的是根据官方文档所说,AsyncTask适用于执行一些简短的后台任务。
  4. 频繁的使用static关键字修饰
    很多初学者非常喜欢用static类static变量,声明赋值调用都简单方便。由于static声明变量的生命周期其实是和APP的生命周期一样的(进程级别)。大量的使用的话,就会占据内存空间不释放,积少成多也会造成内存的不断开销,直至挂掉。static的合理使用一般用来修饰基本数据类型或者轻量级对象,尽量避免修复集合或者大对象,常用作修饰全局配置项、工具类方法、内部类。
  5. BitMap隐患
    Bitmap的不当处理极可能造成OOM,绝大多数情况应用程序OOM都是因这个原因出现的。Bitamp位图是Android中当之无愧的胖子,所以在操作的时候必须小心。
    及时释放recycle。由于Dalivk并不会主动的去回收,需要开发者在Bitmap不被使用的时候recycle掉。
    设置一定的压缩率。需求允许的话,应该去对BItmap进行一定的缩放,通过BitmapFactory.Options的inSampleSize属性进行控制。如果仅仅只想获得Bitmap的属性,其实并不需要根据BItmap的像素去分配内存,只需在解析读取Bmp的时候使用BitmapFactory.Options的inJustDecodeBounds属性。
    最后建议大家在加载网络图片的时候,使用软引用或者弱引用并进行本地缓存,推荐使用android-universal-imageloader或者xUtils。
  6. 引用地狱
    Activity中生成的对象原则上是应该在Activity生命周期结束之后就释放的。Activity对象本身也是,所以应该尽量避免有appliction进程级别的对象来引用Activity级别的对象,如果有的话也应该在Activity结束的时候解引用。如不应用applicationContext在Activity中获取资源。Service也一样。
    有的时候我们也会为了程序的效率性能把本来是Activity级里才用的资源提升到进程级别,比如ImageCache,或者其它DataManager等。
    我只能说,空间和时间是相对的,有的时候需要牺牲时间换取空间,有的时候需要牺牲空间换取时间。内存是空间的存在,性能是时间的存在。完美的程序是在一定条件下的完美。
  7. BroadCastReceiver、Service 解绑
    绑定广播和服务,一定要记得在不需要的时候给解绑。
  8. handler 清理
    在Activity的onDestroy方法中调用
    handler.removeCallbacksAndMessages(null);
    取消所有的消息的处理,包括待处理的消息;
  9. Cursor及时关闭
    在查询SQLite数据库时,会返回一个Cursor,当查询完毕后,及时关闭,这样就可以把查询的结果集及时给回收掉。
  10. I/O流
    I/O流操作完毕,读写结束,记得关闭。
  11. 线程
    线程不再需要继续执行的时候要记得及时关闭,开启线程数量不易过多,一般和自己机器内核数一样最好,推荐开启线程的时候,使用线程池。线程生命周期要跟activity同步。
  12. 网络请求也是线程操作的,也应该与activity生命周期同步,在onDestroy的时候cancle掉请求。
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: Android 性能优化实战篇 PDF 是一本介绍如何优化 Android 应用性能的电子书。该书通过实践经验和案例分析,向读者提供了一系列提高应用性能的实用技巧和法。 该书首先介绍了性能优化的基本概念,例如应用启动时间、内存管理、网络请求和绘制性能等面。之后,通过对真实应用情景的分析,详细介绍了一些常见的性能问题,并给出了相应的解决案。 对于应用启动时间的优化,该书提供了一些切实可行的建议。例如合理使用启动模、使用冷启动的启动动画、减少启动时的初始化操作等。这些法可以明显减少应用的启动时间,提升用户体验。 在内存管理面,该书介绍了如何减少内存泄漏优化内存占用。通过使用合适的数据结构、避免频繁的对象创建和销毁、注意资源的主动释放等法,可以有效地减少内存开销,提高应用的稳定性和响应速度。 对于网络请求的优化,该书提供了一些网络请求的最佳实践。例如使用合适的网络请求库、优化请求的并发性、合理使用缓存等。这些法可以减少网络请求的时间,提高数据加载速度,提升用户体验。 对于绘制性能的优化,该书介绍了如何优化布局和渲染过程,提高界面的流畅度。例如使用合适的布局容器、避免过度绘制、使用硬件加速等。这些法可以减少界面卡顿和掉帧现象,提升应用的视觉效果。 总而言之,Android 性能优化实战篇 PDF 提供了一些宝贵的经验和法,帮助开发者优化应用的性能,提升用户体验。不仅可以帮助开发者理解性能优化的基本原理,还给出了一些具体的解决案,对于 Android 应用的开发和优化都非常有帮助。 ### 回答2: 《Android性能优化实战篇》是一本针对Android开发者的重要参考书籍,旨在帮助开发者提升应用性能,提高用户体验。该书内容包含以下几个面: 1. 性能分析工具:介绍了常用的性能分析工具,如Android Profiler、TraceView等。通过使用这些工具,开发者可以监测应用在不同场景下的性能情况,例如CPU占用、内存使用、网络请求等,从而快速定位并解决性能问题。 2. UI性能优化:详细介绍了Android UI渲染原理以及优化法。通过合理使用View的绘制流程、减少UI层级和View的复杂度、适当使用缓存等法,可以提升UI渲染的速度,提高应用的响应性。 3. 内存优化:介绍了内存泄漏的原因及排查法,提供了避免内存泄漏的最佳实践。此外,还介绍了内存管理机制以及如何合理地使用内存,包括优化Bitmap的加载、使用SparseArray替代HashMap等。 4. 网络优化:讨论了网络请求中存在的性能问题,并提供了相应的解决法。例如,合理使用缓存技术、减少网络请求的频率、使用多线程进行并发请求等。 通过学习《Android性能优化实战篇》,开发者可以更全面地了解和掌握Android性能优化法与技巧,并能够针对具体场景中的性能问题进行准确的分析和解决。这对于提高应用的性能和用户体验具有重要意义。 ### 回答3: 《Android性能优化实战篇》是一本介绍如何提升Android应用性能的实战指南,旨在帮助开发人员通过一系列优化技巧和法,使应用在运行时更加稳定和高效。 该书首先介绍了性能优化的基本概念和原则,包括优化目标、优化思路等。然后详细讲解了Android系统的性能优化核心内容,如内存优化、CPU优化、网络优化等。其中涉及到了一些基本的工具和技术,如内存泄漏的检测与优化、多线程优化、数据缓存等。同时,该书还提供了一些优化实例,通过示例展示了具体的优化法和效果。 对于开发人员来说,优化应用的性能是一个非常重要的任务,能够提高用户的体验度和应用的竞争力。《Android性能优化实战篇》通过详细的指导和实例,帮助读者快速理解和掌握性能优化的基本法和技巧,同时提供了大量实战经验和案例,让读者能够更加深入地了解优化的过程和效果。 总而言之,该书是一本非常实用的Android性能优化指南,对于开发人员来说是一本必备的参考书籍。通过阅读该书,开发人员能够系统地学习和掌握Android应用的性能优化法,提高应用的性能和质量。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值