java(Android)——内存泄露的一些经典案例

来源:互联网 发布:pathfinder软件价格 编辑:程序博客网 时间:2024/04/18 15:35

匿名内部类,非静态内部类造成的内存泄露

经典例子1:

@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);    new Thread(new Runnable() {        @Override        public void run() {            while (true) {                SystemClock.sleep(1000);            }        }    }).start();}

Thread是一个匿名内部类,当我们finish的时候,该activity实例不会真正销毁,GC机制也不会进行该实例的垃圾回收,因为匿名内部类和非静态内部类持有外部类的强引用,也就是说Thread持有外部activity的强引用,而thread内部while(true)是死循环,线程不会停止,对外部activity的强引用也不会消失。这样就造成了 memory leak,内存溢出。

解决方法:
1、通常情况下我们可以设置一个flag,在activity,的生命周期ondestroy中改变flag的状态,但是!!!主线程和子线程的执行时有线程调度的,也就是说会造成竞争的现象,两个线程会争夺cup时间片的执行权,这又会造成我们虽然改变的flag的状态,但是,子线程中的flag并不是马上就能改变值,因为jvm memory model,原型和执行原理,所有的线程都是在自己的工作内存中工作的,对值得操作是先从主内存读取到工作线程的工作内存中,然后cpu对工作内存的值进行修改,最后再写回主内存,所以主线程对flag的操作可能恰好的发生在子线程已经读完主内存的值到工作内存,但是还没有执行的这段时间,所以,我们应该让flag的状态改变能够让子线程马上可见,应该在声明flag的时候加上volatile关键字,虽然该关键字不能保证操作的原子性,但是能够保证变量flag的可见性。
2、声明为一个静态类。

经典例子2:

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 (mManager == null) {            mManager = new TestResource();        }        //...    }    class TestResource {        //...    }}

这样就在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。

解决方法:
将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例。

还有一种情况,android开发经常会继承实现Activity/Fragment/View,此时如果使用了匿名类,并被异步线程持有了,此线程和此Acitivity生命周期不一致的时候,就造成了Activity的泄露。

静态类与静态变量造成的内存泄漏

比如当我们旋转屏幕,可能会造成activity的销毁与重建(虽然国内很多应用不允许旋转屏幕),不希望重新加载图片,所以,有些人会这样写:

private static Drawable background;...ImageView imageView = new ImageView(MainActivity.this);imageView.setBackground(background);

很聪明,这样无论旋转多少次屏幕,bitmap都不会重新加载了,省去了大量的时间,但是imageView 引用了MainActivity的context。

imageView.setBackground(background);

这句源码中(父类View中)background.setCallback(this);background又引用了imageView的回调强引用,这样旋转屏幕,就会造成之前的MainActivity不能被销毁和回收,他的生命甚至和static一样长,所以,内存泄露了。

Handler 造成的内存泄漏

们知道 Handler、Message 和 MessageQueue 都是相互关联在一起的,万一 Handler 发送的 Message 尚未被处理,则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有。
由于 Handler 属于 TLS(Thread Local Storage) 变量, 生命周期和 Activity 是不一致的。因此这种实现方式一般很难保证跟 View 或者 Activity 的生命周期保持一致,故很容易导致无法正确释放。

public class SampleActivity extends Activity {    private final Handler mHandler = new Handler() {        @Override        public void handleMessage(Message msg) {            // ...        }    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        mHandler.postDelayed(new Runnable() {            @Override            public void run() { /* ... */ }        }, 1000 * 60 * 10);        finish();    }}

在该 SampleActivity 中声明了一个延迟10分钟执行的消息 Message,mHandler将其 push 进了消息队列 MessageQueue 里。当该 Activity 被 finish() 掉时,延迟执行任务的 Message 还会继续存在于主线程中,它持有该 Activity 的 Handler 引用,所以此时 finish() 掉的 Activity 就不会被回收了从而造成内存泄漏(因 Handler 为非静态内部类,它会持有外部类SampleActivity的强引用)。

解决方法:
在 Activity 中避免使用非静态内部类,比如上面我们将 Handler 声明为静态的,则其存活期跟 Activity 的生命周期就无关了。同时通过弱引用的方式引入 Activity,避免直接将 Activity 作为 context 传进去,见下面代码:

    public class SampleActivity extends Activity {        private final MyHandler mHandler = new MyHandler(this);        private static class MyHandler extends Handler {            private final WeakReference<SampleActivity> mActivity;            public MyHandler(SampleActivity activity) {                mActivity = new WeakReference<SampleActivity>(activity);            }            @Override            public void handleMessage(Message msg) {                SampleActivity activity = mActivity.get();                if (activity != null) {                    // ...                }            }        }        private static final Runnable sRunnable = new Runnable() {            @Override            public void run() { /* ... */ }        };        @Override        protected void onCreate(Bundle savedInstanceState) {            super.onCreate(savedInstanceState);            mHandler.postDelayed(sRunnable, 1000 * 60 * 10);            finish();        }    }

或者封装一个WeakHandler,直接继承:

public abstract class WeakHandler<T> extends Handler {    private WeakReference<T> mOwner;    public WeakHandler(T owner) {        mOwner = new WeakReference<T>(owner);    }    public T getOwner() {        return mOwner.get();    }}

静态内部类 + WeakReference 这种方式虽然避免了 Activity 泄漏,不过 Looper 线程的消息队列中还是可能会有待处理的消息,所以我们在 Activity 的 Destroy 时或者 Stop 时应该移除消息队列 MessageQueue 中的消息。

public final void removeCallbacks(Runnable r);public final void removeCallbacks(Runnable r, Object token);public final void removeCallbacksAndMessages(Object token);public final void removeMessages(int what);public final void removeMessages(int what, Object object);

单例造成的内存泄漏

public class AppManager {    private static AppManager instance;    private Context context;    private AppManager(Context context) {        this.context = context;    }    public static AppManager getInstance(Context context) {        if (instance == null) {            instance = new AppManager(context);        }        return instance;    }}

这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:
1、如果此时传入的是 Application 的 Context,因为 Application 的生命周期就是整个应用的生命周期,所以这将没有任何问题。
2、如果此时传入的是 Activity 的 Context,当这个 Context 所对应的 Activity 退出时,由于该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以当前 Activity 退出时它的内存并不会被回收,这就造成泄漏了。

关于Context的使用场景请参考:http://blog.csdn.net/wuseyukui/article/details/51332159

正确的方式应该改为下面这种方式:

public class AppManager {    private static AppManager instance;    private Context context;    private AppManager(Context context) {        this.context = context.getApplicationContext();    }    // 或者这样写,连 Context 都不用传进来了    private AppManager() {        this.context = MyApplication.getContext();// MyApplication的context    }    public static AppManager getInstance(Context context) {        if (instance == null) {            instance = new AppManager(context);        }        return instance;    }}

临时Bitmap对象的及时回收

在某些时候,例如临时创建的某个相对比较大的bitmap对象,在经过变换得到新的bitmap对象之后,应该尽快回收原始的bitmap,这样能够更快释放原始bitmap所占用的空间。
需要特别留意的是Bitmap类里面提供的createBitmap()方法:

/*** Returns an immutable bitmap from subset of the source bitmap,* transformed by the optional matrix. The new bitmap may be the* same object as source, or a copy may have been made. It is* initialized with the same density as the original bitmap.* * If the source bitmap is immutable and the requested subset is the* same as the source bitmap itself, then the source bitmap is* returned and no new bitmap is created.**/// 如上:这个函数返回的bitmap有可能和source bitmap是同一个,在回收的时候,// 需要特别检查source bitmap与return bitmap的引用是否相同,只有在不等的情况下,// 才能够执行source bitmap的recycle方法。public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height,            Matrix m, boolean filter) {            ...}

资源未关闭造成的内存泄漏

register的监听器、BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

1 0