什么是内存泄露?
内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
什么是内存溢出?
内存溢出(out of memory)通俗理解就是内存不够,通常在运行大型软件或游戏时,软件或游戏所需要的内存远远超出了你主机内安装的内存所承受大小,就叫内存溢出。
Android出现内存泄露的几种情况
1)单例设计模式导致内存泄露
单例设计模式的静态特性会使它的生命周期和应用的生命周期一样长,当单例对象不被使用时,还保持着单例对象对该对象的引用,导致GC无法回收,从而导致内存泄露。
- public class AppManager {
- private static AppManager instance;
- private Context context;
- private AppManager(Context context) {
- this.context = context;
- }
- public static AppManager getInstance(Context context) {
- if (instance != null) {
- instance = new AppManager(context);
- }
- return instance;
- }
- }
以上例子如果传入的context是Application就没有问题,因为Application的生命周期和应用的生命周期一样长。如果传入的是Activity,当Activity对象被销毁时,context也会被销毁,单例还是保持对该对象的引用,从而导致内存泄漏。
所以正确的单例模式的写法如下:
- public class AppManager {
- private static AppManager instance;
- private Context context;
- private AppManager(Context context) {
- this.context = context.getApplicationContext();
- }
- public static AppManager getInstance(Context context) {
- if (instance != null) {
- instance = new AppManager(context);
- }
- return instance;
- }
- }
2)资源未关闭导致内存泄漏
在使用完Cursor、IO流、BroadcastRecevier、ContentObserver、Bitmap等资源时,没有关闭就会导致内存泄漏。
3)线程导致内存泄漏
线程也是造成内存泄露的重要源头,下面结合以下例子来进行分析:
- //——————test1
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- SystemClock.sleep(10000);
- return null;
- }
- }.execute();
- //——————test2
- new Thread(new Runnable() {
- @Override
- public void run() {
- SystemClock.sleep(10000);
- }
- }).start();
在子线程里执行耗时操作,当开启线程后进行横竖屏切换操作时,一般会把老的Activity销毁,然后新建一个Activity,但是这里不会,
因为子线程是Activity的内部类,子线程还继续保存了对Activity的一个引用,子线程的还没有结束,导致Activity不会被销毁,从而导致内存泄漏。
正确的做法就是在线程内部采用弱引用保存Context引用或者使用静态内部类:
- static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
- private WeakReference<Context> weakReference;
- public MyAsyncTask(Context context) {
- weakReference = new WeakReference<>(context);
- }
- @Override
- protected Void doInBackground(Void... params) {
- SystemClock.sleep(10000);
- return null;
- }
- @Override
- protected void onPostExecute(Void aVoid) {
- super.onPostExecute(aVoid);
- MainActivity activity = (MainActivity) weakReference.get();
- if (activity != null) {
- //...
- }
- }
- }
- static class MyRunnable implements Runnable{
- @Override
- public void run() {
- SystemClock.sleep(10000);
- }
- }
- //——————
- new Thread(new MyRunnable()).start();
- new MyAsyncTask(this).execute();
当然在Activity销毁时也要记得在OnDestry中调用AsyncTask.cancal()方法来取消相应的任务。避免在后台运行浪费资源。
4)Handler 导致的内存泄漏
先看一下不规范的Handler写法:
- public class MainActivity extends AppCompatActivity {
- private Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- //...
- }
- };
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- loadData();
- }
- private void loadData(){
- //...request
- Message message = Message.obtain();
- mHandler.sendMessage(message);
- }
- }
这里的handler也是一个非静态匿名内部类,他跟上面的一样,也会持有Activity的引用,我们知道handler是运行在
一个Looper线程中的,而Looper线程是轮询来处理消息队列中的消息的,假设我们处理的消息有十条,而当他执行到
第6条的时候,用户点击了back返回键,销毁了当前的Activity,这个时候消息还没有处理完,handler还在持有
Activity的引用,这个时候就会导致无法被GC回收,造成了内存泄漏。正确的做法是:
- public class MainActivity extends AppCompatActivity {
- //new一个自定义的Handler
- private MyHandler mHandler = new MyHandler(this);
- private TextView mTextView ;
- //自定义静态内部类继承自Handler
- private static class MyHandler extends Handler {
- private WeakReference<Context> reference;
- //在构造函数中使用弱引用来引用context对象
- public MyHandler(Context context) {
- reference = new WeakReference<>(context);
- }
- @Override
- public void handleMessage(Message msg) {
- MainActivity activity = (MainActivity) reference.get();
- if(activity != null){
- activity.mTextView.setText("");
- }
- }
- }
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- mTextView = (TextView)findViewById(R.id.textview);
- loadData();
- }
- private void loadData() {
- //...request
- Message message = Message.obtain();
- mHandler.sendMessage(message);
- }
- @Override
- protected void onDestroy() {
- super.onDestroy();
- //移除队列中所有的Runable和消息
- //这里也可以使用mHandler.removeMessage和mHandler.removeCallBacks来移除指定的Message和Runable
- mHandler.removeCallbacksAndMessages(null);
- }
- }
5)非静态内部类创建的静态实例造成的内存泄漏
有时候因为需求我们会去频繁的启动一个Activity,这时为了避免频繁的创建相同的数据源,我们通常会做如下处理:
- public class MainActivity extends AppCompatActivity {
- private static TestResource mResource = null;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- if(mManager == null){
- mManager = new TestResource();
- }
- //...
- }
- class TestResource {
- //...
- }
- }
这样就在Activity中创建了非静态内部类,非静态内部类默认持有Activity类的引用,但是他的生命周期还是和应用程序一样长,所以当Activity销毁时,静态内部类的对象引用不会被GC回收,就会造成了内存溢出,解决办法:
1、将内部类改为静态内部类。
2、将这个内部类封装成一个单例,Context使用Application的Context