Android内存优化方案和内存泄露检测分析方法

常见内存泄露及优化方案

1、单例

错误写法:
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;
    }
}

sIntance作为静态单例(在应用程序的整个生命周期中存在)会继续持有这个Activity的引用,就算finish也不会销毁。

优化方案:

将context参数改为全局的上下文:

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

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的引用,当Activity退出后,sInfo仍然引用了Activity,Activity不能被回收

优化方案:
  • 尽量少地使用静态持有的变量

  • 在适当的时候讲静态量重置为null,使其不再持有引用,这样也可以避免内存泄露

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

错误写法:
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) {
                // 做相应逻辑
            }
        }
    };
}

这种写法太常见了!熟悉Handler消息机制的都知道,mHandler会作为成员变量保存在发送的消息msg中,而mHandler是Activity的非静态内部类实例,即mHandler持有Activity的引用。当msg被发送到MessageQueue中,还没有被处理完,即使Activity退出了,也无法被回收。

优化方案:

步骤1:

采用静态内部类+弱引用的方式:

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) {
                    // 做相应逻辑
                }
            }
        }
    }
}

Handler通过弱引用的方式持有Activity,当GC执行垃圾回收时,遇到Activity就会回收并释放所占据的内存单元。这样就不会发生内存泄露了。

步骤2:
上面的做法确实避免了Activity导致的内存泄露,发送的msg不再已经没有持有Activity的引用了,但是msg还是有可能存在消息队列MessageQueue中,所以更好的是在Activity销毁时就将mHandler的回调和发送的消息给移除掉。

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

4、使用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的引用

优化方案:

要避免内存泄露的话还是需要像上面Handler一样使用静态内部类+弱应用的方式(代码就不列了,参考上面Hanlder的正确写法)

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

错误写法:
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) {
            // 接收到广播需要做的逻辑
        }
    };
}

非静态内部类一样持有Activity引用,导致内存泄露。

优化方案:

此注册广播后在Activity销毁后一定要取消注册

  @Override
    protected void onDestroy() {
        super.onDestroy();
        this.unregisterReceiver(mReceiver);
    }

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

6、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);
        }
    }
}

当我们Activity销毁的时,有可能Timer还在继续等待执行TimerTask,它持有Activity的引用不能被回收

优化方案:

Activity销毁的时候要立即cancel掉Timer和TimerTask,以避免发生内存泄漏。

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();
    }

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

错误写法:

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

优化方案:

在使用集合时要及时将不用的对象从集合remove,或者clear集合,以避免内存泄漏。

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

错误写法:

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

优化方案:

在不需要使用它们的时候就及时关闭,以便缓冲能及时得到释放,从而避免内存泄露。

9、属性动画造成内存泄露

错误写法:

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

优化方案:

在Activity销毁的时候cancel掉属性动画,避免发生内存泄漏。

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

10、WebView造成内存泄露

错误写法:

WebView在加载网页后会长期占用内存而不能被释放

Webview下面的Callback持有Activity引用,造成Webview内存无法释放,即使是调用了Webview.destory()等方法都无法解决问题(Android5.1之后)。

优化方案:

在销毁WebView之前需要先将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内存优化——内存泄露检测分析方法》来监测哪里内存泄露

Lint代码检查

这里写图片描述

这里写图片描述

其实,只要我们在设置里面勾选了Lint代码检查(AnroidStudio默认是勾选了的),在写代码的时候就会自动提示可能发生内存泄露。

这里写图片描述

这里写图片描述

package com.bourne.another;

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void start(View view) {
        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) {

            }
        }
    };
}

这里写图片描述

提示说这个Handler类应该是静态的,不然可能会发生泄漏

Android Monitor

在AndroidStudio中,可以通过Monitors来监控Memory、CPU、Network、GPU等。在Monitors监控中,我们可以获取内存的各种信息来分析内存泄露。

首先运行工程后,打开控制台的Android Monitor:

这里写图片描述

Android Monitor
在运行设备中使用app(各个页面的跳转,使用相应的各种功能),就可以看到内存使用的不断变化:

这里写图片描述

内存使用变化

淡蓝色和浅灰色区域就是内存分配的变化过程,浅灰色表示空闲内存,淡蓝色表示使用内存。

通常,我们在打开一个新的页面后,使用的内存就会增加,相应的,关闭一个页面后,系统执行了GC,使用的内存应该下降。如果我们在退出界面并执行GC后,内存使用并未下降明显,或者使用内存没有下降初始的使用大小,那么有可能就发生了内存泄露。

运行工程,在设备上操作app,观察Monitor中内存的变化,点击 initiate GC 触发GC,然后点击Dump Java Heap转出堆信息,稍等片刻,生成hprof文件,生成后会在Studio中自动打开。

这里写图片描述

hprof文件
点击右侧的Analyzer Tasks,再点击Perform Analyzer,展开下面分析结果
中的 Leaked Activities 就可以看到发生内存泄露的Activity了。

这里写图片描述

Monitor内存泄露分析
可以根据左侧的引用树,来查找持有Activity引用的位置,从而判断出哪个地方导致了内存泄露。

总结:

  1. 平时在使用Handler、Thread、AsyncTask的时候要注意,因为是在Activity里面进行创建的,在执行未完成就算Activity退出了,内存也不会回收Activity,所以要采取静态内部类+弱引用的方式,Activity不用之时就会被GC回收,还又就算Activity退出时 在onCreate里面移除Handler、Thread、AsyncTask。

  2. 如果使用了静态单例或者静态变量,引用了Activity,用将Activity转化为ApplicationContext,或者及时将Activity置为null。

  3. 使用Timer和TimerTask时,要cancel并且置为null

  4. 使用集合时,对象不用的时候要及时remove或者clear

  5. 属性动画要记得cancel

  6. 资源的读取要及时关闭

  7. WebView也会造成内存泄露,要找到正确关闭的方法

  8. Android Studio 提供了Lint代码检查工具来检查有内存泄露隐患的代码

  9. Android Studio提供了Android Monitor工具,让我们在运行时可以看出哪里内存泄露了

参考

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值