详解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内存优化内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2021-09-08

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

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

浅谈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防止内存泄露

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常见的内存泄露和解决方案

目录 一.前言 二.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 App调试内存泄露之Cursor篇

最近在工作中处理了一些内存泄露的问题,在这个过程中我尤其发现了一些基本的问题反而忽略导致内存泄露,比如静态变量,cursor关闭,线程,定时器,反注册,bitmap等等,我稍微统计并总结了一下,当然了,这些问题这么说起来比较笼统,接下来我会根据问题,把一些实例代码贴出来,一步一步分析,在具体的场景下,用行之有效的方法,找出泄露的根本原因,并给出解决方案. 现在,就从cursor关闭的问题开始把,谁都知道cursor要关闭,但是往往相反,人们却常常忘记关闭,因为真正的应用场景可能并非理想化的简单.

使用Android Studio检测内存泄露(LeakCanary)

内存泄露,是Android开发者最头疼的事.可能一处小小的内存泄露,都可能是毁千里之堤的蚁穴. 怎么才能检测内存泄露呢? AndroidStudio 中Memory控件台(显示器)提供了一个内存监视器.我们可以通过它方便地查看应用程序的性能和内存使用情况,从而也就可以找到需要释放对象,查找内存泄漏等. 熟悉Memory界面 打开日志控制台,有一个标签Memory ,我们可以在这个界面分析当前程序使用的内存情况. 运行要监控的程序(APP)后,打开Android Monitor控制台窗口,可以看到

android的GC内存泄露问题

1. android内存泄露概念 不少人认为JAVA程序,因为有垃圾回收机制,应该没有内存泄露.其实如果我们一个程序中,已经不再使用某个对象,但是因为仍然有引用指向它,垃圾回收器就无法回收它,当然该对象占用的内存就无法被使用,这就造成了内存泄露.如果我们的java运行很久,而这种内存泄露不断的发生,最后就没内存可用了.当然java的,内存泄漏和C/C++是不一样的.如果java程序完全结束后,它所有的对象就都不可达了,系统就可以对他们进行垃圾回收,它的内存泄露仅仅限于它本身,而不会影响整个系统的

Android编程中避免内存泄露的方法总结

Android的应用被限制为最多占用16m的内存,至少在T-Mobile G1上是这样的(当然现在已经有几百兆的内存可以用了--译者注).它包括电话本身占用的和开发者可以使用的两部分.即使你没有占用全部内存的打算,你也应该尽量少的使用内存,以免别的应用在运行的时候关闭你的应用.Android能在内存中保持的应用越多,用户在切换应用的时候就越快.作为我的一项工作,我仔细研究了Android应用的内存泄露问题,大多数情况下它们是由同一个错误引起的,那就是对一个上下文(Context)保持了长时间的引

一个Vue页面的内存泄露分析详解

什么是内存泄露?内存泄露是指new了一块内存,但无法被释放或者被垃圾回收.new了一个对象之后,它申请占用了一块堆内存,当把这个对象指针置为null时或者离开作用域导致被销毁,那么这块内存没有人引用它了在JS里面就会被自动垃圾回收.但是如果这个对象指针没有被置为null,且代码里面没办法再获取到这个对象指针了,就会导致无法释放掉它指向的内存,也就是说发生了内存泄露.为什么代码里面会拿不到这个对象指针了呢,举一个例子: // module date.js let date = null; expo

JS闭包、作用域链、垃圾回收、内存泄露相关知识小结

补充: 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现. 闭包的特性 闭包有三个特性: 1.函数嵌套函数 2.函数内部可以引用外部的参数和变量 3.参数和变量不会被垃圾回收机制回收 闭包的定义及其优缺点 闭包 是指有权访问另一个函数作用域中的变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量 闭包的缺点就是常驻内存,会增大内存使用量,使用不当很容易造成内存泄露. 闭包是javascript

容易造成JavaScript内存泄露几个方面

发表于谷歌WebPerf(伦敦WebPerf集团),​​2014年8月26日. 高效的JavaScript Web应用必须流畅,快速.与用户交互的任何应用程序,都需要考虑如何确保内存有效使用,因为如果消耗过多,页面就会崩溃,迫使用户重新加载.而你只能躲在角落哭泣. 自动垃圾收集是不能代替有效的内存管理的,特别是在大型,长时间运行的Web应用程序中.在这次讲座中,我们将演示如何通过Chrome的DevTools对内存进行有效的管理. 并了解如何解决性能问题,如内存泄漏,频繁的垃圾收集暂停,和整体内

JavaScript避免内存泄露及内存管理技巧

本文实例讲述了JavaScript避免内存泄露及内存管理技巧,非常实用.分享给大家供大家参考之用.具体方法如下: 本文内容源自谷歌WebPerf(伦敦WebPerf集团),2014年8月26日. 一般来说,高效的JavaScript Web应用必须流畅,快速.与用户交互的任何应用程序,都需要考虑如何确保内存有效使用,因为如果消耗过多,页面就会崩溃,迫使用户重新加载.而你只能躲在角落哭泣. 自动垃圾收集是不能代替有效的内存管理的,特别是在大型,长时间运行的Web应用程序中.本文中,我们将演示如何通

C# 定时器保活机制引起的内存泄露问题解决

C# 中有三种定时器,System.Windows.Forms 中的定时器和 System.Timers.Timer 的工作方式是完全一样的,所以,这里我们仅讨论 System.Timers.Timer 和 System.Threading.Timer 1.定时器保活 先来看一个例子: class Program { static void Main(string[] args) { Start(); GC.Collect(); Read(); } static void Start() { F