Android内存泄漏

作者: 寒_蝉 | 来源:发表于2019-05-04 17:53 被阅读3次

    一、单例造成的内存泄露

    单例模式是非常常用的设计模式,使用单例模式的类,只会产生一个对象,这个对象看起来像是一直占用着内存,但这并不意味着就是浪费了内存,内存本来就是拿来装东西的,只要这个对象一直都被高效的利用就不能叫做泄露。
    但是过多的单例会让内存占用过多,而且单例模式由于其 静态特性,其生命周期 = 应用程序的生命周期,不正确地使用单例模式也会造成内存泄露。

    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;
        }
    }
    

    上面是一个比较简单的单例模式用法,需要外部传入一个 Context 来获取该类的实例,如果此时传入的 Context 是 Activity 的话,此时单例就有持有该 Activity 的强引用(直到整个应用生命周期结束)。这样的话,即使该 Activity 退出,该 Activity 的内存也不会被回收,这样就造成了内存泄露,特别是一些比较大的 Activity,甚至还会导致 OOM(Out Of Memory)。

    解决方法:

    1. 单例模式引用的对象的生命周期 = 应用生命周期(即使用Application Context);
    2. 使用弱引用(WeakReference)。

    二、非静态内部类 / 匿名类

    class 对比 non static inner class static inner class
    与外部 class 引用关系 自动获得强引用 如果没有传入参数,就没有引用关系
    被调用时需要外部实例 需要 不需要
    能否调用外部 class 中的变量和方法 不能
    生命周期 依赖于外部类,甚至比外部类更长 自主的生命周期

    (一)非静态内部类

    可以看到非静态内部类自动获得外部类的强引用,而且它的生命周期甚至比外部类更长,这便埋下了内存泄露的隐患。如果一个 Activity 的非静态内部类的生命周期比 Activity 更长,那么 Activity 的内存便无法被回收,也就是发生了内存泄露,而且还有可能发生难以预防的空指针。

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            new MyAsyncTask().execute();
        }
    
        class MyAsyncTask extends AsyncTask<Void, Integer, String>{
            @Override
            protected String doInBackground(Void... params) {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return "";
            }
        }
    }
    

    可以看到我们在 Activity 中继承 AsyncTask 自定义了一个非静态内部类,在 doInbackground() 方法中做了耗时的操作,然后在 onCreate() 中启动 MyAsyncTask。如果在耗时操作结束之前,Activity 被销毁了,这时候因为 MyAsyncTask 持有 Activity 的强引用,便会导致 Activity 的内存无法被回收,这时候便会产生内存泄露。

    解决方法:将 MyAsyncTask 变成静态内部类。

    (二)匿名类

    匿名类和非静态内部类最大的共同点就是 都持有外部类的引用,因此,匿名类造成内存泄露的原因也跟静态内部类基本是一样的。

    public class MainActivity extends AppCompatActivity {
        private Handler mHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            // ① 匿名线程持有 Activity 的引用,进行耗时操作
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(50000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
    
            // ② 使用匿名 Handler 发送耗时消息
            Message message = Message.obtain();
            mHandler.sendMessageDelayed(message, 60000);
    }
    

    上面举出了两个比较常见的例子:

    1. new 出一个匿名的 Thread,进行耗时的操作,如果 MainActivity 被销毁而 Thread 中的耗时操作没有结束的话,便会产生内存泄露。
    2. new 出一个匿名的 Handler,这里我采用了 sendMessageDelayed() 方法来发送消息,这时如果 MainActivity 被销毁,而 Handler 里面的消息还没发送完毕的话,Activity 的内存也不会被回收。

    解决方法:

    1. 继承 Thread 实现静态内部类
    2. 继承 Handler 实现静态内部类,以及在 Activity 的 onDestroy() 方法中,移除所有的消息 mHandler.removeCallbacksAndMessages(null);

    三、集合类

    集合类添加元素后,仍引用着集合元素对象,导致该集合中的元素对象无法被回收,从而导致内存泄露。

    static List<Object> objectList = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
       Object obj = new Object();
       objectList.add(obj);
       obj = null;
    }
    

    在这个例子中,循环多次将 new 出来的对象放入一个静态的集合中,因为静态变量的生命周期和应用程序一致,而且他们所引用的对象 Object 也不能释放,这样便造成了内存泄露。

    解决方法:在集合元素使用之后从集合中删除,等所有元素都使用完之后,将集合置空。

    objectList.clear();
    objectList = null;
    

    四、其他情况

    (一)需要手动关闭的对象没有关闭
    1. 网络、文件等流忘记关闭
    2. 手动注册广播时,退出时忘记 unregisterReceiver()
    3. Service 执行完后忘记 stopSelf()
    4. EventBus、RxJava等观察者模式的框架忘记手动解除注册
    (二)static 关键字修饰的成员变量
    (三)ListView 的 Item 泄露

    相关文章

      网友评论

        本文标题:Android内存泄漏

        本文链接:https://www.haomeiwen.com/subject/gikfoqtx.html