Android性能优化之内存泄漏及解决方案
内存泄漏的本质
申请了的内存在不再使用时无法回收。
Android应用程序内存泄漏的含义
Android系统为每个应用程序都分配了相应限额的内存。当应用程序中产生的内存泄漏较多时,将会导致应用程序运行所需要的内存超过系统为其分配的限额,这时应用程序就会Crash(崩溃)。
常见引发内存泄漏的情况
- 集合类
- static关键字修饰的成员变量
- 非静态内部类 / 匿名类
- 资源对象使用后未关闭
引发内存泄漏的具体情况及解决方案
(1)集合类
具体表现:
集合List仍然引用该对象,所以垃圾回收器GC依然不可回收该对象
// 通过循环申请Object对象和将申请的对象逐个放入到集合List List<Object> objectList = new ArrayList<>(); for (int i = 0; i < 10; i++) { Object o = new Object(); objectList.add(o); o = null; } // 虽释放了集合元素引用的本身:o=null) // 但集合List仍然引用该对象,故垃圾回收器GC依然不可回收该对象
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
解决方案:
集合类添加集合元素对象后,在使用后必须从集合中删除。
// 集合类添加集合元素对象后,在使用后必须从集合中删除 // 释放objectList objectList.clear(); objectList=null;
- 1
- 2
- 3
- 4
完整例子:
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 集合引发的内存溢出 // 通过循环申请Object对象和将申请的对象逐个放入到集合List List<Object> objectList = new ArrayList<>(); for (int i = 0; i < 10; i++) { Object o = new Object(); objectList.add(o); o = null; } // 虽释放了集合元素引用的本身:o=null) // 但集合List仍然引用该对象,故垃圾回收器GC依然不可回收该对象 // 解决方案 // 集合类添加集合元素对象后,在使用后必须从集合中删除 // 释放objectList objectList.clear(); objectList=null; }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
(2)static关键字修饰的成员变量
具体表现:
被static关键字修饰的成员变量的生命周期等于应用程序的生命周期。如果static关键字修饰的成员变量引用耗费资源过多的实例(如Context),则容易出现该成员变量的生命周期大于引用实例生命周期的情况,当引用实例需结束生命周期销毁时,会因静态变量的持有而无法被回收,从而出现内存泄漏,如:
public class StaticReferenceClass { // 定义1个静态变量 private static Context mContext; public StaticReferenceClass(Context context) { // 引用的是Activity的context mContext = context; } // 当Activity需销毁时,由于mContext的生命周期等于应用程序的生命周期,所以Activity无法被回收,从而出现内存泄露。
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
解决方案:
方法一:尽量避免static成员变量引用资源耗费过多的实例(如 Context),若需引用 Context,则尽量使用Application的Context。
// 方法一:尽量避免static成员变量引用资源耗费过多的实例(如 Context),若需引用 Context,则尽量使用Application的Context
StaticReferenceClass staticReferenceClass = new StaticReferenceClass(getApplication());
- 1
- 2
方法二: 使用弱引用(WeakReference)代替强引用持有实例
public class StaticReferenceClass { // 定义1个静态变量 // 使用弱引用(WeakReference) 代替 强引用 持有实例 private static WeakReference<Context> mContext; public StaticReferenceClass(Context context) { // 引用的是Activity的context mContext = new WeakReference<>(context); }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
特殊例子——单例: 单例的生命周期等于应用程序的生命周期。
如果一个对象已不需再使用,而单例对象却还继续持有该对象的引用时,那么该对象将不能被正常回收,从而导致内存泄漏。如:
import android.content.Context;
public class MySingleClass {
// 创建单例时,需传入一个Context
// 若传入的是Activity的Context,此时单例则持有该Activity的引用
// 由于单例一直持有该Activity的引用(直到整个应用生命周期结束),即使该Activity退出,该Activity的内存也不会被回收
// 特别是一些庞大的Activity,此处非常容易导致OOM private static MySingleClass ourInstance; private Context mContext; public static MySingleClass getInstance(Context context) { if (ourInstance == null) { ourInstance = new MySingleClass(context); } return ourInstance; } private MySingleClass(Context context) { this.mContext = context; // 传递的是Activity的context }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
单例的解决办法:
因为单例模式引用的对象的生命周期等于应用的生命周期。所以应传递Application的Context,因Application的生命周期等于整个应用的生命周期,解决后的代码如下:
import android.content.Context;
public class MySingleClass {
// 创建单例时,需传入一个Context
// 若传入的是Activity的Context,此时单例则持有该Activity的引用
// 由于单例一直持有该Activity的引用(直到整个应用生命周期结束),即使该Activity退出,该Activity的内存也不会被回收
// 特别是一些庞大的Activity,此处非常容易导致OOM private static MySingleClass ourInstance; private Context mContext; public static MySingleClass getInstance(Context context) { if (ourInstance == null) { ourInstance = new MySingleClass(context); } return ourInstance; } private MySingleClass(Context context) { this.mContext = context.getApplicationContext(); // 传递的是Activity的context }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
(3)非静态内部类 / 匿名类
非静态内部类 / 匿名类默认持有外部类的引用;而静态内部类则不会。有三类情况:
- 非静态内部类的实例是静态引起的
- 多线程以非静态内部类或匿名类使用引起的
- 资源没有主动释放或注销引起的
1.当非静态内部类的实例是静态时
如果非静态内部类所创建的实例是静态的,那么它的生命周期等于应用的生命周期,又因为非静态内部类默认持有外部类的引用,所以会导致外部类无法释放,最终造成内存泄漏,如:
具体表现:
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity { // 非静态内部类的实例的引用 // 注:设置为静态实例 public static InnerClass innerClass = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 保证非静态内部类的实例只有1个 if (innerClass == null) { innerClass = new InnerClass(); } } // 非静态内部类的定义 private class InnerClass { //... }
}
// 造成内存泄露的原因:
// a. 当MainActivity销毁时,因非静态内部类单例的引用(innerClass)的生命周期等于应用App的生命周期,持有外部类MainActivity的引用
// b. 所以MainActivity无法被GC回收,从而导致内存泄漏
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
解决方案:
- 1.将非静态内部类设置为:静态内部类(静态内部类默认不持有外部类的引用)
public class MainActivity extends AppCompatActivity { // 非静态内部类的实例的引用 // 注:设置为静态 public static InnerClass innerClass = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 保证非静态内部类的实例只有1个 if (innerClass == null) { innerClass = new InnerClass(); } } // 非静态内部类的定义 private static class InnerClass { //... }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 2.将该内部类抽取出来封装成一个单例
public class InnerClass { private static final InnerClass ourInstance = new InnerClass(); public static InnerClass getInstance() { return ourInstance; } private InnerClass() {}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 3.尽量避免非静态内部类所创建的实例是静态的。若需使用Context,建议使用Application的 Context。
import android.content.Context;
public class InnerClass { private static InnerClass ourInstance = null; private Context mContext; public static InnerClass getInstance() { return ourInstance; } public static InnerClass getInstance(Context context) { if (ourInstance == null) { ourInstance = new InnerClass(context); } return ourInstance; } private InnerClass(Context context) { this.mContext = context.getApplicationContext(); // 传递的是Activity的context }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
2.多线程:AsyncTask、实现Runnable接口、继承Thread类
当AsyncTask、实现Runnable接口、 继承Thread类等多线程以非静态内部类 / 匿名类(即线程类属于非静态内部类/匿名类)方式来使用时,当工作线程正在处理任务,而外部类需销毁时,由于工作线程实例持有外部类引用,将使得外部类无法被垃圾回收器(GC)回收,从而造成内存泄漏。以Thread类来举例,其他依次类推。
具体表现:
// 非静态内部类方式
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 通过创建的内部类 实现多线程 new InnerClassThread().start(); } // 自定义的Thread子类 private class InnerClassThread extends Thread{ @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } }
}
// 匿名内部类方式
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(5000); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
分析
工作线程Thread类属于非静态内部类 / 匿名内部类,运行时默认持有外部类的引用,当工作线程运行时,如果外部类MainActivity需销毁时,将会因为此时工作线程类实例持有外部类的引用,而使得外部类无法被垃圾回收器(GC)回收,从而造成内存泄漏。
解决方案:
a.使用静态内部类:静态内部类默认不持有外部类的引用,将Thread的子类设置成静态内部类,如:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 通过创建的内部类,实现多线程 new InnerClassThread().start(); } // 自定义的Thread子类 private static class InnerClassThread extends Thread{ @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
b.当外部类结束生命周期时,强制结束线程
使得工作线程实例的生命周期与外部类的生命周期同步,具体做法是当外部类结束生命周期时,强制结束线程。但是Thread.stop这个方法官方已标识为弃用状态,所以比较好的办法就是让线程自己停止自己。有两种处理方法:1、让线程任务执行完成,顺利结束退出。2、设置终止标志位,在循环的时候进行终止标志位检测,如果设置为终止状态则return结束线程,如:
public class MainActivity extends AppCompatActivity { private boolean isStop = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 执行多线程 new Thread(new Runnable() { @Override public void run() { while(true){ if(isStop){//当需要结束线程的时候终止线程 //doSomething 进行一些收尾工作 return; } System.out.println(Thread.currentThread().getName()); } } }).start(); } @Override protected void onDestroy() { super.onDestroy(); isStop = true; }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
这个标识位的放置的位置是很值得考虑的,你应该将它放到一个可以保证原子操作的地方。
(4)消息传递机制:Handler引发的内存溢出
请参考《Android性能优化之Handler内存溢出》
(5)资源对象使用后未关闭
对于资源如广播BraodcastReceiver、文件流File、数据库游标Cursor、图片资源Bitmap等的使用,如果在Activity销毁时,没有及时关闭或注销它们,那么这些资源将不会被回收,从而造成内存泄漏。
解决方案
在Activity销毁时 及时关闭或注销这些资源,如:
@Override protected void onDestroy() { super.onDestroy(); // 对于广播BraodcastReceiver:注销注册 unregisterReceiver() // 对于文件流File:关闭流 InputStream / OutputStream.close() // 对于数据库游标cursor:使用后关闭游标 cursor.close() // 对于图片资源Bitmap:Android分配给图片的内存只有8M,如果1个Bitmap对象占内存较多,当它不再被使用时,应调用recycle()回收此对象的像素所占用的内存;最后再赋为null Bitmap.recycle(); Bitmap = null; // 对于动画(属性动画) // 将动画设置成无限循环播放repeatCount = “infinite”后 // 在Activity退出时记得停止动画 }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
其他导致内存溢出的情况
- 不再使用的WebView对象,如果不销毁,将一直占用着内存,无法回收,将导致内存溢出。
解决方法:在不使用时,要主动销毁WebView对象。
谢谢阅读。
文章来源: blog.csdn.net,作者:WongKyunban,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/weixin_40763897/article/details/96289318
- 点赞
- 收藏
- 关注作者
评论(0)