内存泄漏(Memory Leak)
:是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
比如:当Activity的onDestroy()
方法被调用后,Activity
以及它涉及到的View和相关的Bitmap
都应该被回收掉。但是,如果有一个后台线程做耗时操作,导致生命周期比Activity长,造成GC
无法回收Activity,就造成内存泄漏。
它是造成应用程序OOM
的主要原因之一。由于android
系统为每个应用程序分配的内存有限,当一个应用中产生的内存泄漏比较多时,就难免会导致应用所需要的内存超过这个系统分配的内存限额,这就会导致程序崩溃等严重后果。
最常见的是:Leakcanary
leakCanary
是Square
开源框架,是一个Android和Java的内存泄露检测库,如果检测到某个 activity 有内存泄露,LeakCanary 就是自动地显示一个通知,所以可以把它理解为傻瓜式的内存泄露检测工具。通过它可以大幅度减少开发中遇到的oom问题,大大提高APP的质量。
单例在Android中经常使用,如果使用不当会造成内存泄漏,因为单例的静态特性使得他的生命周期与应用的生命周期一样长,这就造成当前对象的生命周期比单例短,单例又持有该对象的引用,GC无法回收该对象,就这导致内存泄漏,比如Context
使用不当: 这里的Context如果使用的是activity的Context,造成单例持有activity的引用,它的生命周期又是整个应用的生命周期,导致activity无法销毁。则有可能造成内存泄漏,比如:
public class LoginManager {
private static LoginManager mInstance;
private Context mContext;
//问题代码
private LoginManager(Context context) {
this.mContext = context;
//修改代码:this.mContext = context.getApplicationContext();
}
public static LoginManager getInstance(Context context) {
if (mInstance == null) {
synchronized (LoginManager.class) {
if (mInstance == null) {
mInstance = new LoginManager(context);
}
}
}
return mInstance;
}
public void get() {}
}
场景: 在一个Activity
中调用的,然后关闭该Activity则会出现内存泄漏。 LoginManager.getInstance(this).get();
解决方案: 可以将Context
修改为AppLication的Context
,代码如下: **this.mContext = context.getApplicationContext();**
此时传入的是 Application
的 Context
,因为 Application
的生命周期就是整个应用的生命周期,此时Context就和整个应用的生命周期一样,与单例的生命周期一样,单例持有的是整个application
的引用,与activity无关,此时activity就正常可以销毁了,所以这将没有任何问题。
比如:
public class TestLeak extends AppCompatActivity {
private static InnerClass mInnerClass = null;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_test);
if (mInnerClass == null) {
mInnerClass = new InnerClass();
}
}
class InnerClass {
}
}
代码中有一个非静态内部类InnerClass
,有一个静态变量mInnerClass
,在onCreate中进行了初始化操作,这个内部类实例就持有activity的强引用,而静态变量的生命周期与应用的生命周期一样长,而activity的生命周期比它短,想要销毁时,被持有引用,无法回收,继而造成内存泄漏。 解决方案:
Application
的Context。 public class InnerClass { private volatile static InnerClass instance; private InnerClass(Context context) { } public static InnerClass getInstance(Context context) { if (instance == null) { synchronized (InnerClass.class) { if (instance == null) { instance = new InnerClass(context.getApplicationContext()); } } } return instance; } } public class TestLeak extends AppCompatActivity { private static InnerClass mInnerClass = null; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.act_test); if (mInnerClass == null) { mInnerClass = InnerClass.getInstance(this); } } }activity
销毁时,将静态内部类设置为空 @Override protected void onDestroy() { mInnerClass = null; super.onDestroy(); }比如:
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler();
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.text); //模拟内存泄露
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mTextView.setText("yangchong");
}
}, 2000);
}
}
从上面问题代码,可以看出这里通过内部类方式创建handler
,而在java中,非静态内部类会持有外部类的引用,这里的postDelayed
是一个延迟处理消息,将一个handler装入到message中,将消息放进消息队列messageQueue
由Looper
进行取消息进行处理。如果此时activity要退出了,想要调用**destroy**
销毁,但是此时Looper正在处理消息,**Looper**
的生命周期明显比activity长,这将使得activity无法被**GC**
回收,最终造成内存泄漏。并且此时handler还持有activity的引用,也是造成内存泄漏的一个原因(不是根本原因)。
解决方案:
WeakReference
弱引用持有Activity
实例 原因:弱引用的对象拥有短暂的生命周期。而垃圾回收器不管内存是否充足都会回收弱引用对象。public class HandlerActivity extends AppCompatActivity {
private static class MyHandler extends Handler {
private final WeakReference<HandlerActivity> mActivity;
public MyHandler(HandlerActivity activity) {
mActivity = new WeakReference<HandlerActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
HandlerActivity activity = mActivity.get();
if (activity != null) {
}
}
private final MyHandler mHandler = new MyHandler(this);
private static final Runnable mRunnable = new Runnable() {
@Override
public void run() { }
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler.postDelayed(mRunnable, 1000 * 60 * 1);
finish();
}
}
Activity
的onDestroy()
方法中调用mHandler.removeCallbacksAndMessages(null)
;就行了。 @Override protected void onDestroy() { super.onDestroy(); if(handler!=null){ handler.removeCallbacksAndMessages(null); handler = null; } }线程造成的内部泄漏以AsyncTask
为例 比如:
public class MainActivity extends AppCompatActivity {
private AsyncTask<Void, Void, Integer> asyncTask;
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.text);
testAsyncTask();
finish();
}
private void testAsyncTask() {
asyncTask = new AsyncTask<Void, Void, Integer>() {
@Override
protected Integer doInBackground(Void... params) {
int i = 0;
//模拟耗时操作
while (!isCancelled()) {
i++;
if (i > 1000) {
break;
}
}
return i;
}
@Override
protected void onPostExecute(Integer integer) {
super.onPostExecute(integer);
mTextView.setText(String.valueOf(integer));
}
};
asyncTask.execute();
}
}
造成内存泄漏原因分析 在处理一个比较耗时的操作时,可能还没处理结束MainActivity就执行了退出操作,这时候线程的生命周期比activity长,又AsyncTask
依然持有对MainActivity的引用,最后导致MainActivity无法被GC回收引发内存泄漏。 解决方案: 在Activity销毁时候也应该取消相应的任务AsyncTask.cancel()方法,避免任务在后台执行浪费资源,进而避免内存泄漏的发生
private void destroyAsyncTask() {
if (asyncTask != null && !asyncTask.isCancelled()) {
asyncTask.cancel(true);
}
asyncTask = null;
}
@Override
protected void onDestroy() {
super.onDestroy();
destroyAsyncTask();
}
比如: add监听,放到集合里面
tv.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
@Override
public void onWindowFocusChanged(boolean b) {
//监听view的加载,view加载出来的时候,计算他的宽高等。
}
});
解决方案 计算完后,一定要移除这个监听
tv.getViewTreeObserver().removeOnWindowFocusChangeListener(this);
比如:BroadcastReceiver,ContentObserver,FileObserver,Cursor,Callback,EventBus
等在 Activity onDestroy
或者某类生命周期结束之后一定要 unregister
或者 close 掉,否则这个 Activity 类会被系统强引用,不会被内存回收。值得注意的是,关闭的语句必须在finally中进行关闭,否则有可能因为异常未关闭资源,致使activity泄漏。
比如:
public class LeakActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_leak);
textView = (TextView)findViewById(R.id.text_view);
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(textView,"rotation",0,360);
objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
objectAnimator.start();
}
}
解决方案: 在属性动画中有一类无限循环动画,如果在Activity中播放这类动画并且在onDestroy中去停止动画,那么这个动画将会一直播放下去,这时候Activity会被View所持有,从而导致Activity无法被释放。解决此类问题则是需要早Activity中onDestroy去去调用objectAnimator.cancel()来停止动画。
@Override
protected void onDestroy() {
super.onDestroy();
mAnimator.cancel();
}
每次从MainActivity退出程序时总会报InputMethodManager
内存泄漏,原因系统中的InputMethodManager
持有当前MainActivity的引用,导致了MainActivity不能被系统回收,从而导致了MainActivity
的内存泄漏。查了很多资料,发现这是 Android SDK
中输入法的一个Bug,在15<=API<=23中都存在,目前Google还没有解决这个Bug。 解决方案如下:
public class MemoryLeakUtil {
public static void fixInputMethodMemoryLeak(Context context) {
if (context == null)
return;
InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
if (inputMethodManager == null)
return;
String[] viewArr = new String[]{"mCurRootView", "mServedView", "mNextServedView"};
Field field;
Object fieldObj;
for (String view : viewArr) {
try {
field = inputMethodManager.getClass().getDeclaredField(view);
if (!field.isAccessible()) {
field.setAccessible(true);
}
fieldObj = field.get(inputMethodManager);
if (fieldObj != null && fieldObj instanceof View) {
View fieldView = (View) fieldObj;
if (fieldView.getContext() == context) {
//注意需要判断View关联的Context是不是当前Activity,否则有可能造成正常的输入框输入失效
field.set(inputMethodManager, null);
} else {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
最后在泄漏的Activity,调用工具类
@Override
protected void onDestroy() {
//手动切断InputMethodManager里的View引用链
MemoryLeakUtil.fixInputMethodMemoryLeak(this);
super.onDestroy();
}