Android 开发者必会的内存泄漏指南( 二 )


文章插图
如果你的应用中存在内存泄漏,垃圾回收器不能回收不使用的内存,随着用户使用时间的增长,内存的占用会越来越多 。如此下去,当系统不能在给它分配更多内存的时候,就会导致 OutOfMemoryError,然后应用程序会崩溃掉 。
垃圾回收有利有弊,垃圾回收是一庞大的系统,在应用中,尽可能少的让垃圾回收器运行,这样对应用体验会更好 。
随着你的应用使用的堆内存逐渐增加,Short GC 就会触发,来保证立即清理无用对象 。现在这些快速清理内存的 GC 运行在不同的线程中,这些 GC 不会导致你的应用变慢 。
但是如果你的应用中存在严重的内存泄漏,Short GC 没有办法回收内存,并且占用内存持续增加,这将会导致 Larger GC 被触发 。它会将整个应用程序挂起,阻塞大概 50~100ms,这会导致应用程序变慢并且有可能不能使用 。
修复内存泄漏,减少对 App 的影响,给用户提供更好的体验 。
5.如何发现内存泄漏?现在,你已经认识到,你需要修复隐藏在你 App 中的内存泄漏 。但是,我们如何才能找到它们呢?
Android Studio 为我们提供了一个非常强大的工具:Monitors 。
通过它,你能看到网络、CPU、GPU、内存的使用情况 。

Android 开发者必会的内存泄漏指南

文章插图
在调试运行 App 的时候,要密切关注内存监视器 。内存泄漏的第一个现象就是,在使用的过程中,内存一直增加,不能减少,即使你把 APP 退到后台也不能释放 。内存分配监视器能够清楚的看到不同对象所占用的内存,可以清楚的知道哪个对象占用内存较多,需要处理 。
但是,它本身还不够,它需要你指定时间,然后转存出对应的内存堆 。这是一个很无趣的工作 。
幸运的是,我们现在已经有更好的方式来实现 。LeakCanary,一个和 App 一起运行的库,它会在内存泄漏的时候,转存出内存信息,然后给我们发送一个通知并给我们一个有用的栈信息 。
6.常见的内存泄漏从我的经验来看,有很多相似且经常出现内存泄漏的问题,你在你每天的开发中,都有可能会遇到它们 。一但你清楚了它们发生的时间、地点、原因,你就可以很轻松的修复它们 。
  • 未取消的 Listener
很多时候,你在 Activity/Fragment 中注册了一个 Listener, 但是忘记取消注册了 。如果你的运气不好,它很可能会引起一个严重的内存泄漏问题 。一般来说,这些 Listener 的 注册与取消注册是同步出现的,在你使用的时候需要注册,在不使用的时候需要取消注册 。
举个例子,当我们的应用程序需要获取定位的时候,需要使用 LocationManager,你会从系统服务中拿到它,并且给其设置一个地理位置更新的回调:
private void registerLocationUpdats {mManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);mManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,TimeUnit.MINUTES.toMillis(1),100,this);}在代码中,可以看出来,使用了 Activity 自己来实现了地理位置更新的回调 。LocationManager 会持有这个回调的引用 。当你退出了这个页面,Android 系统会调用 onDestory,但是垃圾回收器并不能清理掉它,因为 LocationManager 持有它的强引用 。
当然,解决方案也很简单,就是在 onDestory 方法中,取消注册就可以了 。
@Overridepublic voidonDestroy {super.onDestroy;if (mManager != ) {mManager.removeUpdates(this);}}
  • 内部类
内部类在 Java 和 Android 开发中经常用到,非常简单,但是如果使用不当,也会造成严重的内存泄漏 。让我们先来看一个简单的例子:
public class BadActivity extends Activity {private TextView mMessageView;@Overrideprotected voidonCreate(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> {@Overrideprotected String doInBackground(Void... params) {// 做一些耗时操作return "Am finally done!";}@Overrideprotected voidonPostExecute(String result) {mMessageView.setText(result);}}}这是一个很简单的 Activity 页面,在页面启动的时候,在后台启动了一个耗时的任务(比如说,复杂的数据库查询或者是很慢的网络) 。等到任务执行结束,把拿到的结果显示到页面上 。看起来,这样做并没有问题 。事实上,非静态的内部类会隐式的持有外部类的引用(在这里,就是 Activity) 。如果在耗时任务执行完之前,你旋转屏幕或者退出这个页面,垃圾回收器就不能从内存中清理掉 Activity 的实例 。这个简单的问题会导致很严重的内存泄漏问题 。


推荐阅读