[Android]Android内存泄漏你所要知道的一切(翻译)


如下内容为原创,欢迎转载,转载请注明
来自每天博客:http://www.cnblogs.com/tiantianbyconan/p/7235616.html
html

Android内存泄漏你所要知道的一切

原文:https://blog.aritraroy.in/everything-you-need-to-know-about-memory-leaks-in-android-apps-655f191ca859java

写一个Android app很简单,可是写一个超高品质的内存高效的Android app并不简单。从个人我的经验来讲,我曾经比较关注和感兴趣构建我app的新特性,功能和UI组件。android

我主要倾向于工做在具备更多视觉冲击力的东西,而不是花时间在没有人一眼会注意到的东西上面。我开始养成了避免或者给app优化等事情更低优先级的习惯(检测和修复内存泄漏就是其中的一个)。git

这天然而然致使我承担起了技术负债,从长远来看,它开始影响个人应用的性能和质量。我满满地改变了个人心态,比起去年我更多地以“性能为重点”。github

内存泄漏的概念对不少的开发者来讲是很是艰巨的。他们以为这是困难,耗时,无聊和没必要要的,但幸运的是,这些都不是真的。一旦你开始深刻,你将绝对会爱上它。数据库

在本文中,我将尝试让这个话题尽量地简单,这样即便是新的开发者也能从他们的职业生涯一开始就能构建高质量和高性能的Android apps。编程

垃圾收集器是你的朋友,但不一直是

Java是一个强大的语言。在Android中,咱们不会(有时候咱们会)像 C 或者 C++ 那样去写代码来让咱们本身去管理整个内存分配和释放。c#

如今浮如今我脑中的第一个问题就是,既然Java有了一个内置专用的内存管理系统,它会在咱们不须要时清理内存,那么为何咱们还须要关心这个呢。是垃圾收集器不够完善?api

不,固然不是。垃圾收集器的工做原理就是如此。可是咱们本身编程错误有时就会阻止垃圾收集器收集大量没必要要的内存。缓存

因此基本上,都是咱们本身的错误才致使了一切的混乱。垃圾收集器是Java最好的成就之一,它值得被尊重。

推荐阅读

垃圾收集器“更多的一点”

在进一步以前,你须要了解一点垃圾收集器的工做原理。它的原理很是简单,可是它的内部有时候很是复杂。可是不用担忧,咱们将主要关注简单的部分。

每个Android(或者Java)应用程序都有一个从对象开始获取实例化、方法被调用的起点。因此咱们能够认为这个起点是内存树的“root”。一些对象直接保持了一个对“root”的引用,而且从它们中实例化其它对象,保持这些对象的引用等等。

于是,造成了建立内存树的引用链。因此,垃圾收集器从GC roots开始,而后直接或间接遍历对象连接到根。在这个过程的最后,存在一些GC历来没有访问到的对象。

这些是你的垃圾(或者dead objects),这些对象就是咱们所钟爱的垃圾收集器有资格去收集的。

到目前为止,这彷佛是一个童话故事,但让咱们深刻了解一下开始真正的乐趣。

Bonus: 若是你但愿学习更多关于垃圾收集器,我强烈推荐你看下 这里这里

那么如今,什么是内存泄漏呢?

知道如今,你有了一个简单想法的垃圾收集器,那么,在Android apps中内存管理师怎么工做的。如今,让咱们关注于更详细的内存泄漏这个话题。

简单来讲,内存泄漏发生在当你长时间持有一个已经达到目的的对象。实际的概念就这么简单。

每一个对象都有它本身的生命,以后它须要说拜拜,而后释放内存。可是若是一些对象持有这个对象(直接或间接),那么垃圾收集器就没法收集它。这就是咱们的朋友,内存泄漏

可是有个好消息就是你不须要担忧你app中的每一处的内存泄漏。并非全部的内存泄漏都会伤害你的app。

有一些泄漏真的很是小(泄漏了几千字节的内存),有些存在于Android framework自己(是的,你没看错),这些你不能也不须要去修复。他们一般对于你的app影响很小而且你能够安全地忽略它们。

可是也存在其它的能够让你的应用程序崩溃,使它像地狱同样滞留,并将其逐字缩小。这些是你要关注的东西。

推荐阅读

为何你真的须要解决内存泄漏?

没有人但愿使用一个缓慢的、迟钝的、吃不少内存、每用几分钟就会crash的app。对于用户长时间使用,它真的会建立一个糟糕的体验,而后你就有永远失去用户的可能性。

随着用户继续使用你的app,堆内存也不断地增加,若是你的app中有内存泄漏,那么GC就没法释放你无用的内存。因此你app的堆内存就会常常增加,直到达到一个死亡的点,这时将没有更多的内存分配给你的app,从而致使可怕的 OutOfMemoryError 并最终让你的应用程序崩溃。

你还要必须记住一件事情,垃圾收集器是一个繁重的过程,垃圾收集器跑得越少,对你的app就越好。

App正在被使用,对内存保持着增加状态,一个小的GC将启动并尝试清除刚刚死亡的对象。如今这些小的GC同时运行着(在单独的线程上),而且不会减缓你的app(2-5ms的暂停)。

可是若是你的app内部有严重的内存泄漏的问题,那么这些小的GC没法去回收这些内存,而且堆还在持续增加,从而迫使更大的GC被启动,这一般是一个"中止世界 的GC",它会暂停整个app主线程(大约50-100ms),从而使你的app严重滞后,甚至有时几乎不可用

因此如今你知道这些内存泄漏可能对你的应用程序产生的影响,以及为何须要当即修复它们,为用户提供他们应得的最佳体验。

怎么去检测这些内存泄漏?

目前为止,你应该至关信服你须要修复这些隐藏在你app中的内存泄漏。可是怎么去实际检测它们呢?

不错的是,对于这点Android Studio提供了一个很是有用且强大的工具,Monitors。这个显示器不只展现了内存使用,一样还有网络、CPU、GPU使用(更多信息查看这里)。

当你在使用和调试你的app时,应该密切关注这个内存监视器。内存泄漏的第一症状是当你持续使用你的app时内存使用图表常常增加,而且从不降低,甚至在你切换到后台后。

Allocation Tracker能够派上用场,你可使用它来检查分配给应用程序中不一样类型对象的内存百分比。

可是这自己还不够,由于你如今须要使用Dump Java Heap选项来建立一个实际表示给定时间内存快照的heap dump。看起来是一个无聊和重复性的工做,对吧?对,它确实是。

咱们工程师每每是懒惰的,这点 LeakCanary 来救援了。这个库随着你的app一块儿运行。在须要时dump出内存,寻找潜在的内存泄漏而且经过一个清晰有用的stack trace来寻找泄漏的根源。

LeakCanary 让任何人在他们的app中检测泄漏变得超级简单。我不能再感谢 Py(来自 Square)写了如此惊人和拯救了生命的库了。奖励!

Bonus: 若是你想详细学习怎么充分使用这个库,看这里

推荐阅读

一些实际常见的内存泄漏状况并怎么去解决它们

从咱们经验来看,有几个最多见的可能会致使内存泄漏的场景,它们都很是类似,你会在平常的Android开发中遇到这些状况。

一旦你知道这些内存泄漏发生在何时,什么地方,怎么发生,你就能够更容易对此进行修复。

Unregistered Listeners

有不少场景,你在Activity(或者Fragment)中进行了一个监听器的注册,可是忘记把它反注册掉。若是运气很差,这个很容易致使一个巨大的内存泄漏。通常状况下,这些监听器是平衡的,因此若是你在某些地方注册了它,你也须要在那里反注册它。

如今咱们来看一个简单的例子。假设你要在你的app中接收到位置的更新,你要作的事就是拿到一个 LocationManager 系统服务,而后为位置更新注册一个listener。

private void registerLocationUpdates(){
mManager = (LocationManager) getSystemService(LOCATION_SERVICE);
mManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
            TimeUnit.MINUTES.toMillis(1), 
            100, 
            this);
}

你在Activity自己实现了listener接口,所以 LocationManager 持有了一个它的引用。如今你的Activity是时候要销毁了,Android Framework将会调用它的 onDestroy() 方法,可是垃圾收集器将不能从内存中把这个实例删除,由于 LocationManager 仍然持有了它的强引用。

解决方案很是简单。仅仅在 onDestroy() 方法中反注册掉listener,这个很好实现。这是咱们大多数人忘记甚至不知道的。

@Override
public void onDestroy() {
    super.onDestroy();
    if (mManager != null) {
        mManager.removeUpdates(this);
    }
}

内部类

内部类在Java中很是常见,因为它的简洁性,Android开发者常用在各类任务中。可是因为不恰当的使用,这些内部类也致使了潜在的内存泄漏。

让咱们再在一个简单例子的帮助下看看,

public class BadActivity extends Activity {
    private TextView mMessageView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_bad_activity);
        mMessageView = (TextView) findViewById(R.id.messageView);
        new LongRunningTask().execute();
    }
    private class LongRunningTask extends AsyncTask<Void, Void, String> {
        @Override
        protected String doInBackground(Void... params) {
            return "Am finally done!";
        }
        @Override
        protected void onPostExecute(String result) {
            mMessageView.setText(result);
        }
    }
}

这是一个很是简单的Activity,它在后台(也许是复杂的数据库查询或者一个缓慢的网络请求)启动了一个耗时任务。在Task完成时,结果被展现在 TextView。看起来一切都很好?

不,固然不是。问题在于非静态内部类持有一个外部类的隐式引用(也就是Activity自己)。如今若是咱们旋转了屏幕或者若是这个耗时的任务比Activity生命长,那么它不会让垃圾收集器把整个Activity实例从内存回收。一个简单的错误致使了一个巨大的内存泄漏

可是解决方案仍是很是简单,看了你就明白了,

public class GoodActivity extends Activity {
    private AsyncTask mLongRunningTask;
    private TextView mMessageView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_good_activity);
        mMessageView = (TextView) findViewById(R.id.messageView);
        mLongRunningTask = new LongRunningTask(mMessageView).execute();
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mLongRunningTask.cancel(true);
    }
    private static class LongRunningTask extends AsyncTask<Void, Void, String> {
        private final WeakReference<TextView> messageViewReference;
        public LongRunningTask(TextView messageView) {
            this.messageViewReference = new WeakReference<>(messageView);
        }
        @Override
        protected String doInBackground(Void... params) {
            String message = null;
            if (!isCancelled()) {
                message = "I am finally done!";
            }
            return message;
        }
        @Override
        protected void onPostExecute(String result) {
            TextView view = messageViewReference.get();
            if (view != null) {
                view.setText(result);
            }
        }
    }
}

如你所见,我把非静态内部类改为了静态内部类,这样静态内部类就不会持有任何外部类的隐式引用。可是咱们不能经过静态上下文去访问外部类的非静态变量(好比 TextView),因此咱们不得不经过构造方法传递咱们须要的对象引用到内部类。

我强烈推荐使用 WeakReference 包装这个对象引用来防止进一步的内存泄漏。你须要开始学习关于在Java中各个可用的引用类型

匿名类

匿名类是不少开发者最喜欢的,由于它们被定义的方式使得用它们编写代码很是容易和简洁。可是根据个人经验这些匿名类是内存泄漏最多见的缘由

匿名类没有什么,可是非静态内部类会因为前面我讲到过的一样的理由引起潜在的内存泄漏。你已经在app的一系列地方用到了它,可是你不知道若是错误的使用可能会对你app的性能有严重的影响

public class MoviesActivity extends Activity {
    private TextView mNoOfMoviesThisWeek;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_movies_activity);
        mNoOfMoviesThisWeek = (TextView) findViewById(R.id.no_of_movies_text_view);
        MoviesRepository repository = ((MoviesApp) getApplication()).getRepository();
        repository.getMoviesThisWeek()
                .enqueue(new Callback<List<Movie>>() {
                    
                    @Override
                    public void onResponse(Call<List<Movie>> call,
                                           Response<List<Movie>> response) {
                        int numberOfMovies = response.body().size();
                        mNoOfMoviesThisWeek.setText("No of movies this week: " + String.valueOf(numberOfMovies));
                    }
                    @Override
                    public void onFailure(Call<List<Movie>> call, Throwable t) {
                        // Oops.
                    }
                });
    }
}

这里,咱们使用了一个很是流行的库 Retrofit 来执行一个网络请求并把结果显示在 TextView 上。很明显,Callable 对象持有了一个外部Activity类的引用。

若是这个网络请求执行速度很是慢,而且在调用结束以前 Activity 由于某种状况被旋转了屏幕或者被销毁,那么整个Activity实例都会被泄漏

无论是否必须,使用静态内部类来代替匿名内部类一般是明智之举。不是我忽然告诉你彻底中止使用匿名类,而是你必需要懂得判断何时能用何时不能用。

推荐阅读

Bitmaps

在你app中看到的全部图片都不要紧,除了 Bitmap ,它包含了图像的整个像素数据。

这些 bitmaps 对象通常很是重,若是处理不当可能会引起明显的内存泄漏,并最终让你的app由于 OutOfMemoryError 而崩溃。你在app中使用的图片相关的 bitmap 内存 都会由Android Framework 自身自动管理,若是你手动处理 Bitmap,确保在使用后进行 recycle()

你还必须学会怎么去正确地管理这些bitmaps,加载大的Bitmap时经过压缩,以及使用bitmap缓存池来尽量减小内存的占用。这里 有一个理解 bitmap 处理的很好的资源。

Contexts

另外一个至关常见的内存泄漏是滥用 context 实例。Context 只是一个抽象类,它有不少类(好比 Activity,Application,Service 等等)继承它并提供它们本身的功能。

若是你要在 Android 中完成任务,那么 Context 对象就是你的老板。

可是这些 contexts 有一些不一样之处。很是重要的一点是理解 Activity级别的Context 和 Application级别的Context 之间的区别,分别用在什么状况下。

在错误的地方使用 Activity context 会持有整个 Activity 的引用并引起潜在的内存泄漏。这里有篇很好的文章做为开始。

总结

如今你确定知道垃圾收集器是怎么工做的,什么是内存泄漏,它们如何对你的app产生重大的影响。你也学习了怎样检测和修复这些内存泄漏。

没有任何借口,从如今开始让咱们开始构建一个高质量,高性能的 Android app。检测和修复内存泄漏不只会让你的app的用户体验更好,并且会慢慢地让你成为一个更好的开发者。

本文最初发表于 TechBeacon.

相关文章
相关标签/搜索