内存泄露发生的原因
一个不再被使用的对象,被另外一个使用着的对象持有该对象的引用,导致该对象无法被回收,而停留在堆内存当中。通常使用着的对象是生命周期更长的对象。
内存泄露的危害
1、轻则造成app使用卡顿,因为内存泄露会造成app使用时所占用的内存越来越多,在内存空间不足时,就会频繁的引起GC,jvm在进行垃圾回收时会暂停当前正在活动着的线程,等垃圾回收完毕之后线程再继续。所以频繁的GC会导致用户用起来一卡一卡的,用户体验会非常差。
2、严重的就造成app直接崩掉,在分配的内存耗尽时,引发OOM。
内存泄露的检测工具
1、使用android studio自带的profiler
2、使用mat工具
3、使用Android LeakCanary 工具,可参考我的另一篇文章对LeakCanary的介绍:Android内存泄露检测之LeakCanary的使用
内存泄露常见的原因
1、类的静态变量持有大数据对象。一般出现在单例模式、静态的view或者其他静态的变量申明;
2、非静态的内部类、匿名内部类。例如Handler;
3、监听器。例如各种需要注册的Listener,Watcher等;
4、资源对象没有关闭;
5、容器中的对象没清理造成的内存泄漏;
6、webView;
1、静态的变量:静态变量的生命周期和应用的生命周期一样长。如果静态变量持有某个Activity的context,则会引发对应Activity无法释放,导致内存泄漏。如果持有application的context,就没有问题。
这里列举自己的项目中曾经出现过的因为静态变量使用不当导致的问题:
public class ToastUtil {
private static Toast mToast;
public static void showToast(Context context, String text, int gravity){
if (StringUtil.isStringNull(text)){
return;
}
cancelToast();
if (context != null) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View layout = inflater.inflate(R.layout.toast_layout, null);
((TextView) layout.findViewById(R.id.tv_toast_text)).setText(text);
mToast = new Toast(context);
mToast.setView(layout);
mToast.setGravity(gravity, 0, 20);
mToast.setDuration(Toast.LENGTH_LONG);
mToast.show();
}
}
public static void cancelToast() {
if (mToast != null){
mToast.cancel();
}
}
}
这里写了一个ToastUtil来管理弹出框,类中申明了一个静态的Toast变量,然后在showToast()创建了这个静态的对象,这里每调一次showToast方法,就会创建一个静态的toast对象,而静态变量的生命周期是存在于整个应用期间的,所以在toast对象创建后,一直没办法回收,导致内存泄露,这里要想避免内存泄露需要在toast调用show方法之后,将toast对象置为null。当然不断的创建静态对象又销毁很影响性能,这里可以考虑用单例模式,但是单例模式如果使用不当,也很容易产生内存泄露,下面举两个容易产生内存泄露的例子:
private volatile static SingleInstance mSingleInstance = null;
private SingleInstance (Context context) {}
public static SingleInstance getInstance(Context context) {
if (mSingleInstance == null) {
synchronized (SingleInstance.class) {
if (mSingleInstance == null) {
mSingleInstance = new SingleInstance(context);
}
}
}
return mSingleInstance;
}
public class MyActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//这样就出问题了, 单例中持有了activity对象,导致activity无法被回收
SingleInstance singleInstance = SingleInstance.getInstance(this);
}
}
上面这段代码,我想有些初学者肯定这样写过,把当前的activity对象传入到单例中,一旦单例持有了activity,那么activity在整个应用期间将无法被释放。正确的做法应该是传入applicationContext。
当然在单例模式中,我们可能还需要设置View或者其他的对象。那么如果我们像下面这么写也是会出问题的:
private volatile static SingleInstance mSingleInstance = null;
private SingleInstance (Context context) {}
public static SingleInstance getInstance(Context context) {
if (mSingleInstance == null) {
synchronized (SingleInstance.class) {
if (mSingleInstance == null) {
mSingleInstance = new SingleInstance(context);
}
}
}
return mSingleInstance;
}
//单例模式中这样持有View的引用会导致内存泄漏
private View myView = null;
public void setMyView(View myView) {
this.myView = myView;
}
这里原因和上面的一样,那么我们应该怎么解决这种问题呢,我们可以采用弱引用(WeakReference)的方式:
private volatile static SingleInstance mSingleInstance = null;
private SingleInstance (Context context) {}
public static SingleInstance getInstance(Context context) {
if (mSingleInstance == null) {
synchronized (SingleInstance.class) {
if (mSingleInstance == null) {
mSingleInstance = new SingleInstance(context);
}
}
}
return mSingleInstance;
}
//用弱引用
private WeakReference<View> myView = null;
public void setMyView(View myView) {
this.myView = new WeakReference<View>(myView);
}
弱引用指向的对象,当gc运行的时候,无论是否被其他对象引用都会被回收。
2、非静态内部类和匿名内部类容易造成内存泄露的原因是因为这两者都会持有外部类的引用。下面这个例子中,handler延迟一秒执行,当界面从HandlerActivity跳转到SecondActivity时,HandlerActivity会进入到后台,此时如果内存紧张,有可能会触发gc,那么SecondActivity就将被回收,但是因为Handler持有了持有了外部类HandlerActivity,所以HandlerActivity无法被回收,造成内存泄露。解决办法是
public class HandlerActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
TextView tv_test = findViewById(R.id.tv_test);
tv_test.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setClass(HandlerActivity.this, SecondActivity.class);
startActivity(intent);
}
});
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mHandler.sendEmptyMessage(0);
}
}, 1000);
}
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0: {
}
}
}
};
@Override
protected void onDestroy() {
super.onDestroy();
}
}
上面问题的解决办法是将Handler声明为静态内部类,就不会持有外部类SecondActivity的引用,其生命周期就和外部类无关,如果Handler里面需要context的话,可以通过弱引用方式引用外部类。
public class HandlerActivity extends AppCompatActivity {
private Handler mHandler;
private Runnable mRunnable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
TextView tv_test = findViewById(R.id.tv_test);
tv_test.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setClass(HandlerActivity.this, SecondActivity.class);
startActivity(intent);
}
});
mHandler = new MyHandler(this);
mRunnable = new Runnable() {
@Override
public void run() {
mHandler.sendEmptyMessage(0);
}
};
mHandler.postDelayed(mRunnable, 1000);
}
private static final class MyHandler extends Handler {
private final WeakReference<HandlerActivity> mActivity;
private MyHandler(HandlerActivity mActivity) {
this.mActivity = new WeakReference<>(mActivity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// TODO
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// 页面销毁之后,移除消息队列中待处理的消息
mHandler.removeCallbacks(mRunnable);
}
}
3、当我们需要使用系统服务时,比如执行某些后台任务、为硬件访问提供接口等等系统服务。我们需要把自己注册到服务的监听器中。然而,这会让服务持有 activity 的引用,如果程序员忘记在 activity 销毁时取消注册,那就会导致 activity 泄漏了。
例如:EditText的一个addTextChangeListener,如果在回调方法里有耗时操作,可能会造成内存泄露。这种情况下, 我们需要在onDestory时,取消注册,editText.removeTextChangedListener。
public class MainActivity extends AppCompatActivity {
EditText et_test;
TextWatcher textWatcher;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test_layout);
TextView tv_test = findViewById(R.id.tv_test);
tv_test.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setClass(MainActivity.this, LeakActivity.class);
startActivity(intent);
}
});
et_test = findViewById(R.id.et_test);
textWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//
}
@Override
public void afterTextChanged(Editable s) {
}
};
et_test.addTextChangedListener(textWatcher);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 如果TextWatcher里面执行了耗时任务, 那么在onDestroy方法中需要把监听取消掉
et_test.removeTextChangedListener(textWatcher);
}
}
4、在android中,资源性对象比如Cursor、File、Bitmap、视频等,系统都用了一些缓冲技术,在使用这些资源的时候,如果我们确保自己不再使用这些资源了,要及时关闭,否则可能引起内存泄漏。因为有些操作不仅仅只是涉及到Dalvik虚拟机,还涉及到底层C/C++等的内存管理,不能完全寄希望虚拟机帮我们完成内存管理。
在这些资源不使用的时候,记得调用相应的类似close()、destroy()、recycler()、release()等函数,这些函数往往会通过jni调用底层C/C++的相应函数,完成相关的内存释放。
5、我们通常会把一些对象装入到集合中,当不使用的时候一定要记得及时清理集合,让相关对象不再被引用。如果集合是static、不断的往里面添加东西、又忘记去清理,肯定会引起内存泄漏。
6、webView内部的一些线程持有activity对象,导致activity无法释放,继而内存泄漏。网上多采用新开一个进程来解决。可参考:https://www.jianshu.com/p/aa5a99b565e7
网友评论