一、静态的Activity和View
静态的Activity,其实就是创建了一个static修饰的Context或者Activity,然后赋值Activity的this,而静态的View,比如动态创建一个静态的TextView对象,这个时候就需要传入Activity的上下文实例,因为静态变量的生命周期是大于Activity的,所以当Activity销毁的时候,静态变量还会存在,那么就会导致Activity依然被静态变量持有,而无法正常回收。比如下面的代码:
public class MainActivity extends AppCompatActivity {
private static Context context;
private static TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = this;
textView = new TextView(this);
}
}
二、单例造成的内存泄露
单例造成的内存泄露,其实也是因为静态变量持有了Activity实例,导致在Activity在销毁的时候,无法正常回收。比如:
public class TestManager {
private static TestManager instance;
private Context context;
private TestManager(Context context) {
this.context = context;
}
public static TestManager getInstance(Context context) {
if (instance != null) {
instance = new TestManager(context);
}
return instance;
}
}
如果单例中,需要使用Context来创建,则可以使用弱引用优化,将创建的单例对象保存在弱引用中
public class TestManager {
private static WeakReference<TestManager> WeakReferenceInstance;
private Context context;
public TestManager(Context context) {
this.context = context;
}
public static TestManager getInstance(Context context){
if (WeakReferenceInstance == null || WeakReferenceInstance.get() == null) {
WeakReferenceInstance = new WeakReference<TestManager>(new TestManager(context));
}
return WeakReferenceInstance.get();
}
}
单例模式中用到的Context除了使用弱引用持有Context以外,还可以根据需要改成使用ApplicationContext。
三、线程造成的内存泄露
线程造成的内存泄露,一种是创建匿名内部类对象,一种是自定义一个非静态内部类线程实现类,然后创建对象。
非静态内部类会默认持有外部类的引用,所以当非静态内部类线程被创建的时候,其实是持有了外部类Activity的引用。而使用匿名内部类的方式创建线程对象,那么也会持有外部对象的引用。所以当Activity销毁的时候,线程依然还在执行的话,这样就不能保证Activity能被正常回收,就会造成内存泄露。
private Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(10000);
}
}).start();
public class MainActivity extends AppCompatActivity {
private MyThread myThread1 = new MyThread();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
class MyThread extends Thread {
@Override
public void run() {
// 在这里执行耗时操作
System.out.print("内存泄露");
}
}
}
避免内存泄露的方式,可以将自定义线程内部类定义为静态内部类,而如果需要传入Activity实例时,使用弱引用持有Activity对象。
四、非静态内部类创建静态实例造成的内存泄露
在java中,因为非静态内部类会默认持有外部类的引用,而如果使用这个非静态内部类创建静态实例时,因为静态变量的生命周期会大于Activity的生命周期,这样就会导致Activity在销毁时,依然被静态变量所引用,静态变量一直持有该Activity的引用,导致Activity无法被正常的回收。
public class MainActivity extends AppCompatActivity {
private static TestResource mResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(mResource == null){
mResource = new TestResource();
//...
}
class TestResource {
//...
}
}
避免内存泄露的做法:可以将非静态内部类改成静态内部类或者封装成一个单例模式,如果需要传入Activity实例,根据需要看是否可以使用ApplicationContext,如果不能则使用WeakReference持有Activity实例。
五、Handler造成的内存泄露
当Handler创建成非静态的匿名内部类实例,这样就会隐性的引用外部类Activity实例,如果Handler中还有消息没有被处理,而Activity已经被销毁,那么Activity实例就没有办法被回收。想要改变这样的内存泄露,有两种方案:一个就是Handler自定义成静态内部类,并且使用弱引用持有Activity实例,或者使用Handler开源库WeakHandler;另一个方案就是在onDestroy的时候,调用Handler的removeCallbacksAndMessages(null)方法,移除MessageQueue中的消息。
(1)因为定义的匿名内部类,会持有外部类的引用this,可以直接调用外部类的方法和属性。
(2)生命周期的问题。因为Handler.sendMessageAtTime会调用enqueueMessage,然后msg.target=this,说明Message会持有Handler,而Message在传递过程中,会有延时的情况,比如设置20s执行、20分钟执行,这样就会出现Message持有了Handler,而Handler持有了Activity,但是当Activity销毁的时候,可能Message还没处理,还在延迟等待,这样就导致Activity根据JVM可达性分析算法得出Activity不能被GC回收。
前五个总结
总结以上五个情况:创建Handler或者Thread对象的时候,最好不要使用匿名内部类,可以自定义静态内部类避免持有外部引用,如果需要持有Activity实例,则使用WeakReference,并且内部类不要创建成非静态内部类,因为创建非静态内部类的静态实例的时候,也会因为非静态内部类默认持有外部引用导致内存泄露。
六、动画造成的内存泄露
在属性动画中有一类无限循环动画,如果在Activity中播放这类动画并且在onDestroy中没有去停止动画,那么这个动画将会一直播放下去,这时候Activity会被View所持有,从而导致Activity无法被释放。解决此类问题则是需要早Activity中onDestroy去调用objectAnimator.cancel()来停止动画。
七、匿名内部类造成的内存泄露
一般就是会造成内存泄漏的匿名内部类一般有:Runnable、Thread、AsyncTask、TimerTask
我们经常用到的AsyncTask、Runnable、Handler、Thread等类,在采用非静态内部类/匿名内部类的方式使用的话,都会隐式地持有外部类的引用。
但是OnClickListener这个类在使用匿名内部类的方式创建的时候并不会导致内存泄漏。
尽管OnClickListener是匿名内部类,肯定会持有外部类引用,但是new出来点的OnClickListener是被对应的View引用,当Activity销毁时,所包含的View也会释放鸽子所有引用的对象,这样View就被释放,View也会去释放OnClickListener,所以OnClickListener虽然引用了Activity,但是当Activity销毁时,OnClickListener并不会被其他对象引用,那么可达性分析算法分析,这个时候OnClickListener是不可达的,所以OnClickListener就可以被正常回收,那么OnClickListener持有的Activity也可以被正常回收。
八、BroadcastReceiver未取消注册
九、流未关闭
十、WebView
WebView会因为不同版本的兼容问题,导致内存泄露。解决办法就是使用WebView单独开一个进程,使用AIDL与应用的主线程进行通信。
十一、资源对象未关闭
比如Cursor、File等,往往都使用了缓冲,会造成内存泄露。
十二、集合中对象没清理
通常把一些对象添加到集合中,当不需要该对象的时候,如果没有把它的引用从集合中清理掉,这个集合就会越来越大,如果集合是static的,那么就会更大,就会造成内存泄露
十三、Bitmap对象
临时创建的某个相对比较大的Bitmap对象,在经过变换得到新的Bitmap对象之后,应该尽快回收原始的Bitmap,这样能够更快释放原始Bitmap占用的空间。避免静态变量持有较大的Bitmap对象或者其他大的数据对象
网友评论