什么是内存泄露
内存泄露:程序在向系统申请分配内存空间后(new),在使用完毕后未释放。通俗讲,没有用的对象一直无法回收的现象就是内存泄露,值得注意的是,我们App随着内存泄露的不断积累,最终会导致内存溢出OOM(Out Of Memory),更严重的导致程序崩溃
内存溢出:程序向系统申请的内存空间超出了系统能给的,比如内存只能分配一个int类型,我却要塞给他一个long类型,系统就出现oom。
在Android中造成内存泄露的原因
-
1 单例
public class TestManager
{
private static TestManager mTestManager;
private static Context mContext;
private TestManager(Context context){
this.mContext=context;
}
public static TestManager getmTestManager(Context context){
if(mTestManager !=null){
synchronized (TestManager.class){
if(mTestManager !=null){
mTestManager=new TestManager(context);
}
}
}
return mTestManager;
}
}
然后在MainActivity中使用
private void initData()
{
TestManager testManager = TestManager.getmTestManager(this);
}
需要外部传入一个 Context 来获取该类的实例,如果此时传入的 Context 是 Activity 的话,此时单例就有持有该 Activity 的强引用(直到整个应用生命周期结束)。这样的话,即使该 Activity 退出,该 Activity 的内存也不会被回收,这样就造了内存泄露,
解决办法——单例模式引用的对象的生命周期 = 应用生命周期。将 context.getApplicationContext() 赋值给 mContext,此时单例引用的对象是 Application,而 Application 的生命周期本来就跟应用程序是一样的,也就不存在内存泄露。
public class TestManager
{
private static TestManager mTestManager;
private static Context mContext;
private TestManager(Context context){
this.mContext=context.getApplicationContext();
}
public static TestManager getmTestManager(Context context){
if(mTestManager !=null){
synchronized (TestManager.class){
if(mTestManager !=null){
mTestManager=new TestManager(context);
}
}
}
return mTestManager;
}
}
-
2.匿名内部类/非静态内部类(匿名类和非静态内部类最大的共同点就是 都持有外部类的引用)
匿名内部类,在Java当中,非静态内部类默认将会有持有外部类的引用,当在内部类实例化一个静态的对象,那么,这个对象将会与App的生命周期一样长,又因为非静态内部类一直持有外部的MainActivity的引用,导致MainActivity无法被回收,内存泄露的代码如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MyAscnyTask().execute();
}
class MyAscnyTask extends AsyncTask<Void, Integer, String>{
@Override
protected String doInBackground(Void... params) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
}
}
可以看到我们在 Activity 中继承 AsyncTask 自定义了一个非静态内部类,在 doInbackground() 方法中做了耗时的操作,然后在 onCreate() 中启动 MyAsyncTask。如果在耗时操作结束之前,Activity 被销毁了,这时候因为 MyAsyncTask 持有 Activity 的强引用,便会导致 Activity 的内存无法被回收,这时候便会产生内存泄露。
- 解决办法 :将 MyAsyncTask 变成静态内部类
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MyAscnyTask().execute();
}
static class MyAscnyTask extends AsyncTask<Void, Integer, String>{
@Override
protected String doInBackground(Void... params) {
try {
Thread.sleep(50000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
}
}
匿名类和非静态内部类最大的共同点就是 都持有外部类的引用,因此,匿名类造成内存泄露的原因也跟静态内部类基本是一样的,下面举个几个比较常见的例子:
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// ① 匿名线程持有 Activity 的引用,进行耗时操作
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(50000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// ② 使用匿名 Handler 发送耗时消息
Message message = Message.obtain();
mHandler.sendMessageDelayed(message, 60000);
}
上面举出了两个比较常见的例子:
- new 出一个匿名的 Thread,进行耗时的操作,如果 MainActivity 被销毁而 Thread 中的耗时操作没有结束的话,便会产生内存泄露
- new 出一个匿名的 Handler,这里我采用了 sendMessageDelayed() 方法来发送消息,这时如果 MainActivity 被销毁,而 Handler 里面的消息还没发送完毕的话,Activity 的内存也不会被回收
解决办法:
- 继承 Thread 实现静态内部类
- 继承 Handler 实现静态内部类,以及在 Activity 的 onDestroy() 方法中,移除所有的消息 mHandler.removeCallbacksAndMessages(null);
private MyHandler mHandler = new MyHandler(this);
private static class MyHandler extends Handler{
private WeakReference<Context> reference;
public MyHandler(Context context){
reference = new WeakReference<Context>(context);
}
@Override
public void handleMessage(Message msg) {
MainActivity mainActivity = (MainActivity) reference.get();
if(mainActivity != null){
//业务处理逻辑
}
}
}
3.其他情况
除了上述 3 种常见情况外,还有其他的一些情况
1、需要手动关闭的对象没有关闭
网络、文件等流忘记关闭
手动注册广播时,退出时忘记 unregisterReceiver()
Service 执行完后忘记 stopSelf()
EventBus 等观察者模式的框架忘记手动解除注册
2、static 关键字修饰的成员变量
3、ListView 的 Item 泄露
网友评论