OOM、ANR问题总结

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Leonidas_Li/article/details/84061357

ANR(Application Not Response)

ANR原因及解决方案:

  1. 主线程进行了耗时操作
  • 在主线程进行网络操作
  • 在主线程进行大量的I/O操作、数据库操作

解决办法:耗时操作放在子线程。

  1. 主线程被阻塞时间过长或死锁(调用thread的join()方法、sleep()方法、wait()方法或者等待线程锁的时候)
    参考:https://blog.csdn.net/zhangqunshuai/article/details/82455503

解决办法:注意代码逻辑,避免死锁和阻塞的情况。

  1. 其他进程(就是其他程序)占用CPU导致本进程得不到CPU时间片,比如其他进程的频繁读写操作可能会导致这个问题。

解决办法:注意代码逻辑,避免死锁和阻塞的情况。

OOM(Out Of Memory)

OOM原因及解決方案

(参考:https://blog.csdn.net/lc352778616/article/details/77131975

  1. 加载大图片内存泄漏
    在Android应用中加载Bitmap的操作是需要特别小心处理的,因为Bitmap会消耗很多内存。比如,Galaxy Nexus的照相机能够拍摄2592x1936 pixels (5 MB)的图片。 如果bitmap的图像配置是使用ARGB_8888 (从Android 2.3开始的默认配置) ,那么加载这张照片到内存大约需要19MB(259219364 bytes) 的空间,从而迅速消耗掉该应用的剩余内存空间。
    对于分辨率比我们手机屏幕的分辨率高得多的图片,我们应该加载一个缩小版本的图片,从而避免超出程序的内存限制。下面我们就来看一看,如何对一张大图片进行适当的压缩,让它能够以最佳大小显示的同时,还能防止OOM的出现。
    https://github.com/LRH1993/android_interview/blob/master/android/basis/bitmap.md

  2. 单利模式内存泄漏
    不正确使用单例模式是引起内存泄漏的一个常见问题,单例对象在被初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部对象的引用,那么这个外部对象将不能被JVM正常回收,导致内存泄漏。
    如果需要Context,尽量引用Application,而不用Activity
    注意代码逻辑

  3. Handler内存泄漏
    Handler的生命周期与Activity不一致
    由于Handler属于TLS(Thread Local Storage)变量,生命周期和Activity是不一致的。
    当Android应用启动的时候,会先创建一个UI主线程的Looper对象,Looper实现了一个简单的消息队列,一个一个的处理里面的Message对象。主线程Looper对象在整个应用生命周期中存在。
    当在主线程中初始化Handler时,该Handler和Looper的消息队列关联(没有关联会报错的)。发送到消息队列的Message会引用发送该消息的Handler对象,这样系统可以调用 Handler#handleMessage(Message) 来分发处理该消息。
    只要Handler发送的Message尚未被处理,则该Message及发送它的Handler对象将被线程MessageQueue一直持有。因此这种实现方式一般很难保证跟View或者Activity的生命周期保持一致,故很容易导致无法正确释放。
    handler引用Activity阻止了GC对Acivity的回收
    在Java中,非静态(匿名)内部类会默认隐性引用外部类对象。而静态内部类不会引用外部类对象。
    如果外部类是Activity,则会引起Activity泄露。
    当Activity finish后,延时消息会继续存在主线程消息队列中1分钟,然后处理消息。而该消息引用了Activity的Handler对象,然后这个Handler又引用了这个Activity。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致了上面说的 Activity泄露。
    错误示例:

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

解决办法:
方法一:通过程序逻辑来进行保护。
1.在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。
2.如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。

方法二:将Handler声明为静态类
PS:在Java 中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用,静态的内部类不会持有外部类的引用。
静态类不持有外部类的对象,所以你的Activity可以随意被回收。由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference)

正确示例:

static class MyHandler extends Handler
    {
        WeakReference<Activity> mWeakReference;
        public MyHandler(Activity activity) 
        {
            mWeakReference=new WeakReference<Activity>(activity);
        }
        @Override
        public void handleMessage(Message msg)
        {
            final Activity activity=mWeakReference.get();
            if(activity!=null)
            {
                if (msg.what == 1)
                {
                    noteBookAdapter.notifyDataSetChanged();
                }
            }
        }
    }

  1. 非静态内部类导致的内存泄漏
    非静态的内部类会持有外部类的一个引用,所以和前面context说到的一样,如果该内部类生命周期超过外部类的生命周期,就可能引起内存泄露了,如AsyncTask和Handler。因为在Activity中我们可能会用到匿名内部类,所以要小心管理其生命周期。 如果明确生命周期较外部类长的话,那么应该使用静态内部类

  2. Context导致内存泄漏
    android 中很多地方都要用到context,连基本的Activty 和 Service都是从Context派生出来的,我们利用Context主要用来加载资源或者初始化组件,在Activity中有些地方需要用到Context的时候,我们经常会把context给传递过去了,将context传递出去就有可能延长了context的生命周期,最终导致了内存泄漏。例如 我们将activty context对象传递给一个后台线程去执行某些操作,如果在这个过程中因为屏幕旋转而导致activity重建,那么原先的activity对象不会被回收,因为它还被后台线程引用着,如果这个activity消耗了比较多的内存,那么新建activity或者后续操作可能因为旧的activity没有被回收而导致内存泄漏。所以,遇到需要用到context的时候,我们要合理选择不同的context,对于android应用来说还有一个单例的Application Context对象,该对象生命周期和应用的生命周期是绑定的。选择context应该考虑到它的生命周期,如果使用该context的组件的生命周期超过该context对象,那么我们就要考虑是否可以用application context。如果真的需要用到该context对象,可以考虑用弱引用来WeakReference来避免内存泄漏

猜你喜欢

转载自blog.csdn.net/Leonidas_Li/article/details/84061357