常见内存泄漏问题分析与解决:
什么是内存泄漏:
内存管理的目的就是在开发过程中能够有效的避免内存使用及内存泄漏问题,内存泄漏简单的可以总结为:
“没用的对象出现无法回收的现象就是内存泄漏”。
内存泄漏会造成哪些问题:
应用可使用内存逐步变小,增加了堆内存压力
降低了应用性能,比如频繁触发GC
严重的时候也会造成内存溢出,OOM
OOM 发生在,当我们尝试进行创建对象,但是堆内存无法通过 GC 释放足够的空间,堆内存也无法再继续增长,从而完成对象创建请求的时候,OOM 发生很有可能是内存泄露导致的,但并非所有的 OOM 都是由内存泄露引起的,内存泄露也并不一定引起 OOM。
平常开发中常见的内存泄漏案例:
单例模式造成内存泄漏:
public class SingleInstanceTest {
private static SingleInstanceTest sInstance;
private Context mContext;
private SingleInstanceTest(Context context){
this.mContext = context;
}
public static SingleInstanceTest newInstance(Context context){
if(sInstance == null){
sInstance = new SingleInstanceTest(context);
}
return sInstance;
}
}
单例模式中若单独持有View的Context的话,那么当该View层销毁时,单例任然存活且持有该View层的应用,这样就会导致View层不能被及时释放。
解决方法及建议:
1.明确单例模式使用场景,在合适的地方使用单例模式并且尽量避免传入View层Context。
2.单例模式中若必须使用Context,可将单例模式应用对象的生命周期依附于整个应用的生命周期。
public class SingleInstanceTest {
private static SingleInstanceTest sInstance;
private Context mContext;
private SingleInstanceTest(Context context){
this.mContext = context.getApplicationContext();
}
public static SingleInstanceTest newInstance(Context context){
if(sInstance == null){
sInstance = new SingleInstanceTest(context);
}
return sInstance;
}
}
部分类构造函数持有View的Context:
public class Sample {
private Context mContext;
public Sample(Context context){
this.mContext = context;
}
public Context getContext() {
return mContext;
}
}
// 外部调用
Sample sample = new Sample(MainActivity.this);
这种在构造函数中传入了context的例子,处理不当也会造成内存泄漏问题。
解决方案:
1.对于View层的Context持有可以改为弱引用
public class Sample {
private WeakReference<Context> mWeakReference;
public Sample(Context context){
this.mWeakReference = new WeakReference<>(context);
}
public Context getContext() {
if(mWeakReference.get() != null){
return mWeakReference.get();
}
return null;
}
}
// 外部调用
Sample sample = new Sample(MainActivity.this);
非静态内部类/匿名类:
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中的onCreate 中创建了MyAscnyTask对象,并启动了异步方法,这样当异步方法未执行完毕前退出了Activity,那么因为MyAscnyTask仍持有Activity的引用,就会导致Activity不能及时释放。
解决方法:将 MyAsyncTask 变成静态内部类 (Handler的使用也可以参考这里)
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 "";
}
}
}
List中填充了View层对象或控件:
在非View层持有一个List,里面填充了持有View层的对象。
List viewLists = new ArrayLists(){};
for(int i=0;i<10;i++){
ImageView iv = new ImageView(this);
viewLists.add(iv);
}
在使用完毕后,记得置空和清除List中的持有对象,避免内存泄漏。
点击事件及按键事件处理不当:
在单例模式中封装windowManager处理每一层View的点击/按键事件,但是在View层销毁的时候没有及时释放掉单例中持有的时间回调。
解决方式:单例模式中提供释放接口,在View层销毁的时候,及时销毁掉有关于自己的事件回调。单例模式中若持有对View层的引用,必须得提供释放方法,让View层去根据生命周期调用。
MVP模式不规范造成的内存泄漏:
MVP模式中,P层一般会持有View层的引用,所以P层需要提供UnBind方法去释放掉对View层的引用。或自己封装好BaseView,BasePresenter,BaseModel来进行基类封装。
部分Android源生API带来的内存泄漏问题:
在Android4.4中,TelephoneManager监听sim卡信号变化的API存在内存泄漏问题。解决无果后,后期便将该对象与应用生命周期绑定而不是在某个activity中进行注册。