android 常见的内存泄漏
好久没写博客了,今天提辞职了,希望以后还能一如既往的继续写博客,今天写下常见的内存泄漏的案例,可能在我们平时开发中经常这么写,但是并没发现有什么内存泄漏问题? 分为如下几种
第一种情况静态变量引起的内存泄露
我们在获取屏幕的宽和高时,都会写一个通用的工具类,方便使用:
package com.leak; import android.content.Context; import android.view.WindowManager; /** * Created by zhouguizhi on 2017/10/23. */ public class CommonUtils { private static CommonUtils INSTANCE = null; private Context mContext; private WindowManager windowManager; public CommonUtils(Context context) { this.mContext = context; windowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); } public static CommonUtils getInstance(Context context){ if(null==INSTANCE){ INSTANCE = new CommonUtils(context); } return INSTANCE; } public int getScreenWidth(){ if(windowManager==null){ return -1; } return windowManager.getDefaultDisplay().getWidth(); } public int getScreenHeight (){ if(windowManager==null){ return -1; } return windowManager.getDefaultDisplay().getHeight(); } }
在MainActivity调用:
package com.leak; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); CommonUtils.getInstance(this).getScreenHeight(); } }
然后通过Link检查会发现这段代码有啥问题:
解决方法:传Application就行
第二种情况非静态内部类引起内存泄露
package com.leak; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; @SuppressWarnings("unused") public class MainActivity extends AppCompatActivity { private final int b = 10; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); loadData(); } private void loadData() { new Thread(new Runnable() { @Override public void run() { while (true){ try { int a =b; Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } }
这个通过Link既然没检查出来,我就使用了LeakCanary,运行起来发现确实有内存泄漏的问题:
其实问题是出现在这段话:
int a =b;
因为new Thread()是一个匿名内部类,而a=b,其实是a=MainActivity.this.b,隐士的持有MainActivity实例,这样比如在你调用网络请求还没结束的时候,这个时候你退出这个界面的,但是这个线程还会在运行,这样就导致这个Activity无法销毁,
解决办法:
把loadData()方法改为静态方法即可!因为静态内部类不会隐士持有外部类实例.
这样虽然解决了内存泄漏的问题,但是无法使用外部类的变量了,而你又想使用外部类的变量怎么办?
我改成这样:
package com.leak; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; @SuppressWarnings("unused") public class MainActivity extends AppCompatActivity { private final int b = 10; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); loadData(); } private void loadData() { MyThread myThread = new MyThread(this); myThread.start(); } private class MyThread extends Thread{ private MainActivity activity; public MyThread(MainActivity activity){ this.activity = activity; } @Override public void run() { super.run(); while (true){ try { int a = activity.b; Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
其实问题还是一样的:
一般是这么解决的:
package com.leak; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import java.lang.ref.WeakReference; @SuppressWarnings("unused") public class MainActivity extends AppCompatActivity { private final int b = 10; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); loadData(); } private void loadData() { MyThread myThread = new MyThread(this); myThread.start(); } private class MyThread extends Thread{ private WeakReference<MainActivity> weakReference; public MyThread(MainActivity activity){ this.weakReference = new WeakReference<>(activity); } @Override public void run() { super.run(); while (true){ try { MainActivity activity = weakReference.get(); int a = activity.b; Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
使用软引用或者弱引用来解决的.
第三种情况Handler引起的内存泄漏
package com.leak; import android.os.Handler; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; @SuppressWarnings("unused") public class MainActivity extends AppCompatActivity { private final int b = 10; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); loadData(); } private void loadData() { new Handler().postDelayed(new Runnable() { @Override public void run() { int a = b; } },2000); } }
平时也有人这么写,之前我干过这种事,
这个和上面第二种情况是一样的,
发现内存泄漏了,那我改成这样:
package com.leak; import android.os.Handler; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; @SuppressWarnings("unused") public class MainActivity extends AppCompatActivity { private final int b = 10; private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); if(msg.what==100){ int a = b; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Message msg = new Message(); msg.what=100; mHandler.sendMessageDelayed(msg,20000); } }
其实还是会出现内存泄漏:因为mHandler是匿名内部类的实例,会引用外部对象MainActivity.this。如果Handler在MaiinActivity退出的时候,它可能还活着,这时候就会一直持有MainActivity
解决方法:
package com.leak; import android.os.Handler; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import java.lang.ref.WeakReference; @SuppressWarnings("unused") public class MainActivity extends AppCompatActivity { private final int b = 10; private static class MyHandler extends Handler{ private WeakReference<MainActivity> weakReference;//设置软引用保存,当内存一发生GC的时候就会回收。 public MyHandler(MainActivity mainActivity) { this.weakReference = new WeakReference<>(mainActivity); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); MainActivity main = weakReference.get(); if(main==null||main.isFinishing()){ return; } switch (msg.what){ case 100: int a = main.b; break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Message msg = new Message(); msg.what=100; MyHandler mHandler = new MyHandler(this); mHandler.sendMessageDelayed(msg,20000); } }
第四种情况 资源未关闭引起的内存泄露
比如:BroadCastReceiver、Cursor、Bitmap、IO流、自定义属性attribute,attr.recycle()回收。当不需要使用的时候,要记得及时释放资源。否则就会内存泄露
第五种情况无限循环动画引起的内存泄露
没有在onDestroy中停止动画,否则Activity就会变成泄露对象。比如:轮播图效果