记住几种出现内存泄漏的点

公众号:码农乐园 等级 363 0 0

Android 内存优化——常见内存泄露及优化方案 如果一个无用对象(不需要再使用的对象)仍然被其他对象持有引用,造成该对象无法被系统回 收,以致该对象在堆中所占用的内存单元无法被释放而造成内存空间浪费,这中情况就是内存泄 露。 在Android 开发中,一些不好的编程习惯会导致我们的开发的app 存在内存泄露的情况。下面介 绍一些在Android 开发中常见的内存泄露场景及优化方案。 单例导致内存泄露 单例模式在Android 开发中会经常用到,但是如果使用不当就会导致内存泄露。因为单例的静态 特性使得它的生命周期同应用的生命周期一样长,如果一个对象已经没有用处了,但是单例还持 有它的引用,那么在整个应用程序的生命周期它都不能正常被回收,从而导致内存泄露。

  public class AppSettings {
private static AppSettings sInstance;
private Context mContext;
private AppSettings(Context context) {
this.mContext = context;
}
public static AppSettings getInstance(Context context) {
if (sInstance == null) {
sInstance = new AppSettings(context);
}
return sInstance;
}
}

像上面代码中这样的单例,如果我们在调用getInstance(Context context)方法的时候传入的 context 参数是Activity、Service 等上下文,就会导致内存泄露。 以Activity 为例,当我们启动一个Activity,并调用getInstance(Context context)方法去获取 AppSettings 的单例,传入Activity.this 作为context,这样AppSettings 类的单例sInstance 就 持有了Activity 的引用,当我们退出Activity 时,该Activity 就没有用了,但是因为sIntance 作为静态单例(在应用程序的整个生命周期中存在)会继续持有这个Activity 的引用,导致这个 Activity 对象无法被回收释放,这就造成了内存泄露。 为了避免这样单例导致内存泄露,我们可以将context 参数改为全局的上下文: private AppSettings(Context context) { this.mContext = context.getApplicationContext(); } 全局的上下文Application Context 就是应用程序的上下文,和单例的生命周期一样长,这样就避 免了内存泄漏。 单例模式对应应用程序的生命周期,所以我们在构造单例的时候尽量避免使用Activity 的上下文, 而是使用Application 的上下文。 静态变量导致内存泄露 静态变量存储在方法区,它的生命周期从类加载开始,到整个进程结束。一旦静态变量初始化后, 它所持有的引用只有等到进程结束才会释放。 比如下面这样的情况,在Activity 中为了避免重复的创建info,将sInfo 作为静态变量:

  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, 使其不再持有引用,这样也可以避免内存泄露。 非静态内部类导致内存泄露 非静态内部类(包括匿名内部类)默认就会持有外部类的引用,当非静态内部类对象的生命周期 比外部类对象的生命周期长时,就会导致内存泄露。 非静态内部类导致的内存泄露在Android 开发中有一种典型的场景就是使用Handler,很多开发 者在使用Handler 是这样写的:

  public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
start();
}
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) {
// 做相应逻辑
}
}
};
}

也许有人会说,mHandler 并未作为静态变量持有Activity 引用,生命周期可能不会比Activity 长, 应该不一定会导致内存泄露呢,显然不是这样的! 熟悉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 的内存泄露。 通常在Android 开发中如果要使用内部类,但又要规避内存泄露,一般都会采用静态内部类+弱 引用的方式。

  public class MainActivity extends AppCompatActivity {
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new MyHandler(this);
start();
}
private void start() {
Message msg = Message.obtain();
msg.what = 1;
mHandler.sendMessage(msg);
}
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 就会回收并释
放所占据的内存单元。这样就不会发生内存泄露了。
上面的做法确实避免了Activity 导致的内存泄露,发送的msg 不再已经没有持有Activity 的引用
了,但是msg 还是有可能存在消息队列MessageQueue 中,所以更好的是在Activity 销毁时就将
mHandler 的回调和发送的消息给移除掉。
@Overrideprotected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
非静态内部类造成内存泄露还有一种情况就是使用Thread 或者AsyncTask。
比如在Activity 中直接new 一个子线程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) {
// 模拟相应耗时逻辑
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}.execute();
}
}

很多初学者都会像上面这样新建线程和异步任务,殊不知这样的写法非常地不友好,这种方式新 建的子线程Thread 和AsyncTask 都是匿名内部类对象,默认就隐式的持有外部Activity 的引用, 导致Activity 内存泄露。要避免内存泄露的话还是需要像上面Handler 一样使用静态内部类+弱 应用的方式(代码就不列了,参考上面Hanlder 的正确写法)。 未取消注册或回调导致内存泄露 比如我们在Activity 中注册广播,如果在Activity 销毁后不取消注册,那么这个刚播会一直存在 系统中,同上面所说的非静态内部类一样持有Activity 引用,导致内存泄露。因此注册广播后在 Activity 销毁后一定要取消注册。

  public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.registerReceiver(mReceiver, new IntentFilter());
}
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// 接收到广播需要做的逻辑
}
};
@Override
protected void onDestroy() {
super.onDestroy();
this.unregisterReceiver(mReceiver);
}
}

在注册观察则模式的时候,如果不及时取消也会造成内存泄露。比如使用Retrofit+RxJava 注册网 络请求的观察者回调,同样作为匿名内部类持有外部引用,所以需要记得在不用或者销毁的时候 取消注册。 Timer 和TimerTask 导致内存泄露 Timer 和TimerTask 在Android 中通常会被用来做一些计时或循环任务,比如实现无限轮播的 ViewPager:

  public class MainActivity extends AppCompatActivity {
private ViewPager mViewPager;
private PagerAdapter mAdapter;
private Timer mTimer;
private TimerTask mTimerTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
mTimer.schedule(mTimerTask, 3000, 3000);
}
private void init() {
mViewPager = (ViewPager) findViewById(R.id.view_pager);
mAdapter = new ViewPagerAdapter();
mViewPager.setAdapter(mAdapter);
mTimer = new Timer();
mTimerTask = new TimerTask() {
@Override
public void run() {
MainActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
loopViewpager();
}
});
}
};
}
private void loopViewpager() {
if (mAdapter.getCount() > 0) {
int curPos = mViewPager.getCurrentItem();
curPos = (++curPos) % mAdapter.getCount();
mViewPager.setCurrentItem(curPos);
}
}
private void stopLoopViewPager() {
if (mTimer != null) {
mTimer.cancel();
mTimer.purge();
mTimer = null;
}
if (mTimerTask != null) {
mTimerTask.cancel();
mTimerTask = null;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
stopLoopViewPager();
}
}

当我们Activity 销毁的时,有可能Timer 还在继续等待执行TimerTask,它持有Activity 的引用不 能被回收,因此当我们Activity 销毁的时候要立即cancel 掉Timer 和TimerTask,以避免发生内存 泄漏。 集合中的对象未清理造成内存泄露 这个比较好理解,如果一个对象放入到ArrayList、HashMap 等集合中,这个集合就会持有该对象 的引用。当我们不再需要这个对象时,也并没有将它从集合中移除,这样只要集合还在使用(而 此对象已经无用了),这个对象就造成了内存泄露。并且如果集合被静态引用的话,集合里面那 些没有用的对象更会造成内存泄露了。所以在使用集合时要及时将不用的对象从集合remove,或 者clear 集合,以避免内存泄漏。 资源未关闭或释放导致内存泄露 在使用IO、File 流或者Sqlite、Cursor 等资源时要及时关闭。这些资源在进行读写操作时通常都 使用了缓冲,如果及时不关闭,这些缓冲对象就会一直被占用而得不到释放,以致发生内存泄露。 因此我们在不需要使用它们的时候就及时关闭,以便缓冲能及时得到释放,从而避免内存泄露。 属性动画造成内存泄露 动画同样是一个耗时任务,比如在Activity 中启动了属性动画(ObjectAnimator),但是在销毁 的时候,没有调用cancle 方法,虽然我们看不到动画了,但是这个动画依然会不断地播放下去, 动画引用所在的控件,所在的控件引用Activity,这就造成Activity 无法正常释放。因此同样要 在Activity 销毁的时候cancel 掉属性动画,避免发生内存泄漏。 @Overrideprotected void onDestroy() { super.onDestroy(); mAnimator.cancel(); } WebView 造成内存泄露 关于WebView 的内存泄露,因为WebView 在加载网页后会长期占用内存而不能被释放,因此我 们在Activity 销毁后要调用它的destory()方法来销毁它以释放内存。 另外在查阅WebView 内存泄露相关资料时看到这种情况: Webview 下面的Callback 持有Activity 引用,造成Webview 内存无法释放,即使是调用了 Webview.destory()等方法都无法解决问题(Android5.1 之后)。 最终的解决方案是:在销毁WebView 之前需要先将WebView 从父容器中移除,然后在销毁WebView。 详细分析过程请参考这篇文章: [](http://blog.csdn.net/xygy8860/article/details/53334476?utm_source=itdadao&utm_medium =referral)(http://blog.csdn.net/xygy8860/article/details/53334476)[WebView 内存泄漏解决方 法]。

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

总结 内存泄露在Android 内存优化是一个比较重要的一个方面,很多时候程序中发生了内存泄露我们 不一定就能注意到,所有在编码的过程要养成良好的习惯。总结下来只要做到以下这几点就能避 免大多数情况的内存泄漏: 构造单例的时候尽量别用Activity 的引用; 静态引用时注意应用对象的置空或者少用静态引用; 使用静态内部类+软引用代替非静态内部类; 及时取消广播或者观察者注册; 耗时任务、属性动画在Activity 销毁时记得cancel; 文件流、Cursor 等资源及时关闭; Activity 销毁时WebView 的移除和销毁。

收藏
评论区

相关推荐

Android WebView加载优化
1.前言 最近几年关于原生WebView与H5混合开发的项目越来越多,这种开发带来了很多便利,但也会有一些缺点,比如说通过WebView加载H5会有一定的卡顿现象,会影响用户体验。下面本文就此问题一一展开讨论。 2. 场景 根据日常需求一般是通过webView.loadUrl()方法加载指定的网页,其大概流程如下: (https://i
Android webview 与 js(Vue) 交互
js 与原生交互分为两种情况:js 调用原生方法,原生调用 js 方法。 本文将对这两种情况分别讲解,H5 端用 vue 实现。 一、前期准备(Vue项目准备) 本文的 H5 端用Vue 实现,所以在正式开始前先把 Vue 项目环境准备好。 项目写好后,执行 npm run serve 命令启动项目,启动成功后会在命令
Android Activity生命周期,启动模式,启动过程详解
前言 接触过Android开发的同学都知道Activity,Activity作为Android四大组件之一,使用频率高。简单来说Activity提供了一个显示界面,让用户进行各种操作,本文主要分为以下三个部分:Activity的生命周期,启动模式,以及Activity的工作过程。文中大部分篇幅来自《Android开发艺术探索》一书,尽管想多以流程或图
Android中一个Activity关闭另一个Activity或者在一个Activity中关闭多个Activity
前言 最近项目中涉及需要在一个Activity中关闭另一个Activity或者在一个Activity中关闭多个Activity的需求,不涉及到应用的退出。自己首先想了一些方案,同时也查了一些方案,就各个方案比较下优劣。 方案一 广播的方式 这个是最容易想到的,同时也是网上提供最多的。 由于多个Activity要使用,关闭页面
Android开发你必须了解的几个原理
随着互联网的迅速发展,Android技术也是发生很大的变化,要求也是越来高了,在11,12年只要会基本的Android组件,会listview,分享就感觉很牛了,智能手机的发展,及用户普通追求高效率,用户体验的提升,要求开发人员必须会懂实现原理及优化APP程序;不管是面试他人还是被面试目前都经常问到原理性的问题,handler实现原理,activity启动原
Android通过URL打开Activity
关注公众号 QXF069 为每个 Activity 绑定一个 url 可以方便的让第三方 app 直接打开这些 Activity。也可以方便在 app 内部进行页面跳转,解耦。 背景 举一个常见的案例,假设我们有个产品 A,产品 A 包含 h5 网页端和客户端,当用户在手机打开我们的 h5 网页端的时候,我们会期望如果用户手机安装了我们的客户端,则直接打
Android Fragment生命周期
Fragment 是在 Android 3.0 中引入,用于解决不同屏幕分辨率的设备上 UI 显示、交互的问题。Fragment 有自己的布局,有自己的生命周期,有自己的事件响应。 但 Fragment 又是依赖于 Activity 存在的,你可以把多个 Fragment 嵌入到一个 Activity 中或者多个 Activity 重用一个 Fragmen
记住几种出现内存泄漏的点
Android 内存优化——常见内存泄露及优化方案如果一个无用对象(不需要再使用的对象)仍然被其他对象持有引用,造成该对象无法被系统回收,以致该对象在堆中所占用的内存单元无法被释放而造成内存空间浪费,这中情况就是内存泄露。在Android 开发中,一些不好的编程习惯会导致我们的开发的app 存在内存泄露的情况。下面介绍一些在Android 开发中常见的内存泄
检测xposed框架实现
1.检测安装包名java private static int l(Context context) { int i 0; PackageManager packageManager context.getPackageManager(); try { packageMan
Android深入理解Context(一)Context关联类和Application Context创建过程
Android框架层 Android深入理解Contextcategories: Android框架层本文首发于微信公众号「刘望舒」 前言Context也就是上下文对象,是Android较为常用的类,但是对于Context,很多人都停留在会用的阶段,这个系列会带大家从源码角度来分析Context,从而更加深入的理解它。<!more 1.Context概述Co
Android深入理解Context(二)Activity和Service的Context创建过程
Android框架层 Android深入理解Contextcategories: Android框架层本文首发于微信公众号「刘望舒」 前言上一篇文章我们学习了Context关联类和Application Context的创建过程,这一篇我们接着来学习Activity和Service的Context创建过程。需要注意的是,本篇的知识点会和深入理解四大组件系列的
Android深入四大组件(一)应用程序启动过程(前篇)
Android框架层 Android深入四大组件categories: Android框架层本文首发于微信公众号「后厂技术官」 前言在此前的文章中,我讲过了Android系统启动流程和Android应用进程启动过程,这一篇顺理成章来学习Android 7.0的应用程序的启动过程。分析应用程序的启动过程其实就是分析根Activity的启动过程。<!more 1
Android深入四大组件(三)Service的绑定过程
Android框架层 Android深入四大组件categories: Android框架层本文首发于微信公众号「刘望舒」 前言我们可以通过调用Context的startService来启动Service,也可以通过Context的bindService来绑定Service,建议阅读此篇文章前请阅读这篇文章,知识点重叠的部分,本篇文章将不再赘述。<!more
Android深入四大组件(六)Android8.0 根Activity启动过程(前篇)
Android框架层 Android深入四大组件categories: Android框架层本文首发于微信公众号「刘望舒」 前言在几个月前我写了和这两篇文章,它们都是基于Android 7.0,当我开始阅读Android 8.0源码时发现应用程序(根Activity)启动过程照Android 7.0有了一些变化,因此又写下了本篇文章,本篇文章照此前的文章不仅
这是一份用心整理的Android面试总结,涨姿势!
Android Jetpack组件的作用是什么? Navigation:一个用于管理Fragment切换的工具类,可视化、可绑定控件、支持动画等是其优点。 Data Binding:不用说,都知道,加速MVVM的创建。 Lifecycle:他是我们能够处理Activity和Fragment的生命周期的重要原因,在AndroidX的Fragment和Activ