详解Android内存泄露及优化方案一

目录
  • 一、常见的内存泄露应用场景?
    • 1、单例的不恰当使用
    • 2、静态变量导致内存泄露
    • 3、非静态内部类导致内存泄露
    • 4、未取消注册或回调导致内存泄露
    • 5、定时器Timer 和 TimerTask 导致内存泄露
    • 6、集合中的对象未清理造成内存泄露
    • 7、资源未关闭或释放导致内存泄露
    • 8、动画造成内存泄露
    • 9、WebView 造成内存泄露
  • 总结

一、常见的内存泄露应用场景?

1、单例的不恰当使用

单例是我们开发中最常见和使用最频繁的设计模式之一,所以如果使用不当就会导致内存泄露。因为单例的静态特性使得它的生命周期同应用的生命周期一样长,如果一个对象已经没有用处了,但是单例还持有它的引用,那么在整个应用程序的生命周期这个对象都不能正常被回收,从而导致内存泄露。

如:

public class App {
    private static App sInstance;

    private Context mContext;

    private App(Context context) {
        this.mContext = context;
    }

    public static App getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new App(context);
        }
        return sInstance;
    }
}

调用getInstance(Context context)方法时传入的上下文如果为当前活动Activity或者当前服务的Service以及当前fragment的上下文,当他们销毁时,这个静态单例sIntance还会持用他们的引用,从而导致当前活动、服务、fragment等对象不能被回收释放,从而导致内存泄漏。这种上下文的使用很多时候处理不当就会导致内存泄漏,需要我们多注意编码规范。

2、静态变量导致内存泄露

静态变量存储在方法区,它的生命周期从类加载开始,到整个进程结束。一旦静态变量初始化后, 它所持有的引用只有等到进程结束才会释放。

如下代码:

public class MainActivity extends AppCompatActivity {
    private static Info sInfo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (sInfo != null) {
            sInfo = new Info(this);
        }
    }
}

class Info {
    public Info(Activity activity) {
    }
}

Info 作为 Activity 的静态成员,并且持有 Activity 的引用,但是 sInfo 作为静态变量,生命周期 肯定比 Activity 长。所以当 Activity 退出后,sInfo 仍然引用了 Activity,Activity 不能被回收, 这就导致了内存泄露。 在 Android 开发中,静态持有很多时候都有可能因为其使用的生命周期不一致而导致内存泄露, 所以我们在新建静态持有的变量的时候需要多考虑一下各个成员之间的引用关系,并且尽量少地 使用静态持有的变量,以避免发生内存泄露。当然,我们也可以在适当的时候讲静态量重置为 null, 使其不再持有引用,这样也可以避免内存泄露。

3、非静态内部类导致内存泄露

非静态内部类(包括匿名内部类)默认就会持有外部类的引用,当非静态内部类对象的生命周期 比外部类对象的生命周期长时,就会导致内存泄露。这类内存泄漏很典型的Handler的使用,这么一说大家应该就很熟悉了吧,大家都知道怎么处理这类内存泄漏。

Handler的使用示例:

    private void start() {
        Message msg = Message.obtain();
        msg.what = 1;
        mHandler.sendMessage(msg);
    }

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 1) {
                // ui更新
            }
        }
    };

Handler 消息机制,mHandler 会作为成员变量保存在发送的消息 msg 中,即 msg 持有 mHandler 的引用,而 mHandler 是 Activity 的非静态内部类实例,即 mHandler 持有 Activity 的引 用,那么我们就可以理解为 msg 间接持有 Activity 的引用。msg 被发送后先放到消息队列 MessageQueue 中,然后等待 Looper 的轮询处理(MessageQueue 和 Looper 都是与线程相关联的, MessageQueue 是 Looper 引用的成员变量,而 Looper 是保存在 ThreadLocal 中的)。那么当 Activity 退出后,msg 可能仍然存在于消息对列 MessageQueue 中未处理或者正在处理,那么这样就会导致 Activity 无法被回收,以致发生 Activity 的内存泄露。

如何避免:
1、采用静态内部类+弱引用的方式

 private static class MyHandler extends Handler {
        private WeakReference<MainActivity> activityWeakReference;

        public MyHandler(MainActivity activity) {
            activityWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = activityWeakReference.get();
            if (activity != null) {
                if (msg.what == 1) {
                    // 做相应逻辑
                }
            }
        }
    }

mHandler 通过弱引用的方式持有 Activity,当 GC 执行垃圾回收时,遇到 Activity 就会回收并释 放所占据的内存单元。这样就不会发生内存泄露了。但是 msg 还是有可能存在消息队列 MessageQueue 中。

2、Activity 销毁时就将 mHandler 的回调和发送的消息给移除掉。

  @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }

非静态内部类造成内存泄露还有一种情况就是使用 Thread 或者 AsyncTask异步调用:
如示例:
Thread :

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

AsyncTask:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... params) {
                // UI线程处理
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return null;
            }
        }.execute();
    }
}

以上新建的子线程 Thread 和 AsyncTask 都是匿名内部类对象,默认就隐式的持有外部 Activity 的引用, 导致 Activity 内存泄露。要避免内存泄露的话还是需要像上面 Handler 一样使用采用静态内部类+弱引用的方式(如上面Hanlder采用静态内部类+弱引用的方式)。

4、未取消注册或回调导致内存泄露

比如我们在 Activity 中注册广播,如果在 Activity 销毁后不取消注册,那么这个刚播会一直存在 系统中,同上面所说的非静态内部类一样持有 Activity 引用,导致内存泄露。因此注册广播后在 Activity 销毁后一定要取消注册。

this.unregisterReceiver(mReceiver);

在注册观察则模式的时候,如果不及时取消也会造成内存泄露。比如使用 Retrofit+RxJava 注册网络请求的观察者回调,同样作为匿名内部类持有外部引用,所以需要记得在不用或者销毁的时候 取消注册。

5、定时器Timer 和 TimerTask 导致内存泄露

当我们 Activity 销毁的时,有可能 Timer 还在继续等待执行 TimerTask,它持有 Activity 的引用不 能被回收,因此当我们 Activity 销毁的时候要立即 cancel 掉 Timer 和 TimerTask,以避免发生内存 泄漏。

6、集合中的对象未清理造成内存泄露

这个比较好理解,如果一个对象放入到 ArrayList、HashMap 等集合中,这个集合就会持有该对象 的引用。当我们不再需要这个对象时,也并没有将它从集合中移除,这样只要集合还在使用(而 此对象已经无用了),这个对象就造成了内存泄露。并且如果集合被静态引用的话,集合里面那 些没有用的对象更会造成内存泄露了。所以在使用集合时要及时将不用的对象从集合 remove,或 者 clear 集合,以避免内存泄漏。

7、资源未关闭或释放导致内存泄露

在使用 IO、File 流或者 Sqlite、Cursor 等资源时要及时关闭。这些资源在进行读写操作时通常都 使用了缓冲,如果及时不关闭,这些缓冲对象就会一直被占用而得不到释放,以致发生内存泄露。 因此我们在不需要使用它们的时候就及时关闭,以便缓冲能及时得到释放,从而避免内存泄露。

8、动画造成内存泄露

动画同样是一个耗时任务,比如在 Activity 中启动了属性动画(ObjectAnimator),但是在销毁 的时候,没有调用 cancle 方法,虽然我们看不到动画了,但是这个动画依然会不断地播放下去, 动画引用所在的控件,所在的控件引用 Activity,这就造成 Activity 无法正常释放。因此同样要 在 Activity 销毁的时候 cancel 掉属性动画,避免发生内存泄漏。

  @Override
    protected void onDestroy() {
        super.onDestroy();
        mAnimator.cancel();
    }

9、WebView 造成内存泄露

关于 WebView 的内存泄露,因为 WebView在加载网页后会长期占用内存而不能被释放,因此我 们在 Activity 销毁后要调用它的 destory()方法来销毁它以释放内存。
另外在查阅 WebView 内存泄露相关资料时看到这种情况: Webview 下面的 Callback 持有 Activity 引用,造成 Webview 内存无法释放,即使是调用了 Webview.destory()等方法都无法解决问题(Android5.1 之后)

最终的解决方案是:在销毁 WebView 之前需要先将 WebView 从父容器中移除,然后在销毁 WebView。

   @Override
    protected void onDestroy() {
        super.onDestroy();
        // 先从父控件中移除
        WebView mWebViewContainer.removeView(mWebView);
        mWebView.stopLoading();
        mWebView.getSettings().setJavaScriptEnabled(false);
        mWebView.clearHistory();
        mWebView.removeAllViews();
        mWebView.destroy();
    }

总结

构造单例的时候尽量别用 Activity 的引用;
静态引用时注意应用对象的置空或者少用静态引用;
使用静态内部类+软引用代替非静态内部类;
及时取消广播或者观察者注册;
耗时任务、属性动画在 Activity 销毁时记得 cancel;
文件流、Cursor 等资源及时关闭; Activity 销毁时 WebView 的移除和销毁。

下一篇继续:Android 内存优化(二)——内存优化策略

ps:内存泄漏是开发中的一个痛点,需要我们有很好的良好编码习惯。奥里给!!!!!!!!!

到此这篇关于详解Android内存泄露及优化方案一的文章就介绍到这了,更多相关Android内存优化内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android 优化Handler防止内存泄露

    Android 优化Handler防止内存泄露 Demo描述: Handler可能导致的内存泄露及其优化 1 关于常见的Handler的用法但是可能导致内存泄露 2 优化方式请参考BetterHandler和BetterRunnable的实现 package cc.cc; import java.lang.ref.WeakReference; import android.os.Bundle; import android.os.Handler; import android.os.Messag

  • 浅谈Android性能优化之内存优化

    1.Android内存管理机制 1.1 Java内存分配模型 先上一张JVM将内存划分区域的图 程序计数器:存储当前线程执行目标方法执行到第几行. 栈内存:Java栈中存放的是一个个栈帧,每个栈帧对应一个被调用的方法.栈帧包括局部标量表, 操作数栈. 本地方法栈:本地方法栈主要是为执行本地方法服务的.而Java栈是为执行Java方法服务的. 方法区:该区域被线程共享.主要存储每个类的信息(类名,方法信息,字段信息等).静态变量,常量,以及编译器编译后的代码等. 堆:Java中的堆是被线程共享的,

  • 详解Android的内存优化--LruCache

    概念: LruCache 什么是LruCache? LruCache实现原理是什么? 这两个问题其实可以作为一个问题来回答,知道了什么是 LruCache,就只然而然的知道 LruCache 的实现原理:Lru的全称是Least Recently Used ,近期最少使用的!所以我们可以推断出 LruCache 的实现原理:把近期最少使用的数据从缓存中移除,保留使用最频繁的数据,那具体代码要怎么实现呢,我们进入到源码中看看. LruCache源码分析 public class LruCache<

  • 详解Android使用Handler造成内存泄露的分析及解决方法

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

  • 分析Android常见的内存泄露和解决方案

    目录 一.前言 二.Android 内存泄露场景 2.1.非静态内部类的静态实例 2.2.多线程相关的匿名内部类/非静态内部类 2.3.Handler 内存泄露 2.4.静态 Activity 或 View 2.5.Eventbus 等注册监听造成的内存泄露 2.6.单例引起的内存泄露 2.7.资源对象没关闭造成内存泄漏 2.8.WebView 一.前言 目前 java 垃圾回收主流算法是虚拟机采用 GC Roots Tracing 算法.算法的基本思路是:通过一系列的名为 GC Roots (

  • 详解Android内存泄露及优化方案一

    目录 一.常见的内存泄露应用场景? 1.单例的不恰当使用 2.静态变量导致内存泄露 3.非静态内部类导致内存泄露 4.未取消注册或回调导致内存泄露 5.定时器Timer 和 TimerTask 导致内存泄露 6.集合中的对象未清理造成内存泄露 7.资源未关闭或释放导致内存泄露 8.动画造成内存泄露 9.WebView 造成内存泄露 总结 一.常见的内存泄露应用场景? 1.单例的不恰当使用 单例是我们开发中最常见和使用最频繁的设计模式之一,所以如果使用不当就会导致内存泄露.因为单例的静态特性使得它

  • 详解Android内存泄露及优化方案

    目录 一.常见的内存泄露应用场景? 1.单例的不恰当使用 2.静态变量导致内存泄露 3.非静态内部类导致内存泄露 4.未取消注册或回调导致内存泄露 5.定时器Timer 和 TimerTask 导致内存泄露 6.集合中的对象未清理造成内存泄露 7.资源未关闭或释放导致内存泄露 8.动画造成内存泄露 9.WebView 造成内存泄露 总结 一.常见的内存泄露应用场景? 1.单例的不恰当使用 单例是我们开发中最常见和使用最频繁的设计模式之一,所以如果使用不当就会导致内存泄露.因为单例的静态特性使得它

  • 详解Android内存优化策略

    目录 前言 一.内存优化策略 二.具体优化的点 1.避免内存泄漏 2.Bitmap等大对象的优化策略 (1) 优化Bitmap分辨率 (2) 优化单个像素点内存 (3) Bitmap的缓存策略 (4) drawable资源选择合适的drawable文件夹存放 (5) 其他大对象的优化 (6) 避免内存抖动 3.原生API回调释放内存 4.内存排查工具 (1)LeakCanary监测内存泄漏 (2)通过Proflier监控内存 (3)通过MAT工具排查内存泄漏 总结 前言 在开始之前需要先搞明白一

  • 详解Java内存泄露的示例代码

    在定位JVM性能问题时可能会遇到内存泄露导致JVM OutOfMemory的情况,在使用Tomcat容器时如果设置了reloadable="true"这个参数,在频繁热部署应用时也有可能会遇到内存溢出的情况.Tomcat的热部署原理是检测到WEB-INF/classes或者WEB-INF/lib目录下的文件发生了变更后会把应用先停止然后再启动,由于Tomcat默认给每个应用分配一个WebAppClassLoader,热替换的原理就是创建一个新的ClassLoader来加载类,由于JVM

  • 详解Android内存泄漏检测与MAT使用

    内存泄漏基本概念 内存检测这部分,相关的知识有JVM虚拟机垃圾收集机制,类加载机制,内存模型等.编写没有内存泄漏的程序,对提高程序稳定性,提高用户体验具有重要的意义.因此,学习Java利用java编写程序的时候,要特别注意内存泄漏相关的问题.虽然JVM提供了自动垃圾回收机制,但是还是有很多情况会导致内存泄漏. 内存泄漏主要原因就是一个生命周期长的对象,持有了一个生命周期短的对象的引用.这样,会导致短的对象在该回收时候无法被回收.Android中比较典型的有:1.静态变量持有Activity的co

  • 详解Android性能优化之启动优化

    1.为什么要进行启动优化 网上流行一种说法,就是8秒定律,意思是说,如果用户在打开一个页面,在8秒的时间内还没有打开,那么用户大概的会放弃掉,意味着一个用户的流失.从这里就可以看出,启动优化的重要性了. 2.启动的分类 2.1 冷启动 先来看看冷启动的流程图 从图中可以看出,APP启动的过程是:ActivityManagerProxy 通过IPC来调用AMS(ActivityManagerService),AMS通过IPC启动一个APP进程,ApplicationThread通过反射来创建App

  • 详解android是如何管理内存的

    目录 前言 Java Heap 进程内存分配 内存不足管理 GC 垃圾回收 内核交换守护进程 低内存终止守护进程 最后 前言 很高兴遇见你~ 内存优化一直是 Android 开发中的一个非常重要的话题,他直接影响着我们 app 的性能表现.但这个话题涉及到的内容很广且都偏向底层,让很多开发者望而却步.同时,内存优化更加偏向于"经验知识",需要在实际项目中去应用来学习. 因而本文并不想深入到底层去讲内存优化的原理,而是着眼于宏观,聊聊 android 是如何分配和管理内存.在内存不足的时

  • 详解Android 语音播报实现方案(无SDK)

    本文介绍了详解Android 语音播报实现方案(无SDK),分享给大家,具体如下: 功能描述 类似支付宝收款时候的语音播报功能:当别人扫描你的收款码,你收到钱之后,就会听到"支付宝到账12.55元"的语音播报. 要解决的问题 1.播放单个语音文件 2.播放完单个语音文件之后立即播放下一条,这样才能连续 3.当多个完整的语音序列都需要播报时的处理(比如支付宝短时间内收到多条收款推送) 实现思路 1.播放单个文件选择MediaPlayer 首先创建一个MediaPlayer实例 Media

  • 详解python 内存优化

    写在之前 围绕类的话题,说是说不完的,仅在特殊方法,除了我们在前面遇到过的 __init__(),__new__(),__str__() 等之外还有很多.虽然它们只是在某些特殊的场景中才会用到,但是学会它们却可以成为你熟悉这门语言路上的铺路石. 所以我会在试图介绍一些「黑魔法」,让大家多多感受一下 Python 的魅力所在,俗话说「艺多不压身」就是这个道理了. 内存优化 首先先让我们从复习前面的类属性和实例属性的知识来引出另一个特殊方法: >>> class Sample: ... na

  • 详解Android Ashmem匿名共享内存

    目录 1. 简述 2. 创建 MemoryFile 和 数据写入 3. 将文件描述符传递到其他进程 4. 在其他进程接收 FileDescriptor 并读取数据 1. 简述 Android 的 匿名共享内存(Ashmem) 基于 Linux 的共享内存,都是在临时文件系统(tmpfs)上创建虚拟文件,再映射到不同的进程.它可以让多个进程操作同一块内存区域,并且除了物理内存限制,没有其他大小限制.相对于 Linux 的共享内存,Ashmem 对内存的管理更加精细化,并且添加了互斥锁.Java 层

随机推荐