美文网首页
Android内存泄漏原因及解决案例

Android内存泄漏原因及解决案例

作者: 石头1314 | 来源:发表于2017-11-15 10:56 被阅读10次

造成 Android 内存泄漏的最根本原因就是生命周期较长的对象持有生命周期较短的对象的引用。

1.单例造成的内存泄漏

如果一个 Activity 被一个单例对象所引用,那么当退出这个 Activity 时,由于单例的对象依然存在(单例对象的生命周期跟整个 App 的生命周期一致),而单例对象又持有 Activity 的引用,这就导致了此 Activity 无法被回收,从而造成内存泄漏。

public class SingleTon { 

    private static SingleTon singleTon; 
    private Context context; 
    private SingleTon(Context context) { 
        this.context = context; 
    } 

    public static SingleTon getInstance(Context context) { 
        if (singleTon == null) { 
            synchronized (SingleTon.class) { 
                if (singleTon == null) { 
                    singleTon = new SingleTon(context); 
                } 
            } 
        } 
        return singleTon; 
    } 
} 

这是单例模式饿汉式的双重校验锁的写法,这里的 singleTon 持有 Context 对象,如果 Activity 中调用 getInstance 方法并传入 this 时,singleTon 就持有了此 Activity 的引用,当退出 Activity 时,Activity 就无法回收,造成内存泄漏。

解决方法:
private SingleTon(Context context) { 
    this.context = context.getApplicationContext(); 
}

通过 getApplicationContext 来获取 Application 的 Context,让它被单例持有,这样退出 Activity 时,Activity 对象就能正常被回收了,而 Application 的 Context 的生命周期和单例的生命周期是一致的,所有再整个 App 运行过程中都不会造成内存泄漏。

2.Handler 或 Runnable 作为非静态内部类

handler 和 runnable 都有定时器的功能,当它们作为非静态内部类的时候,同样会持有外部类的引用,如果它们的内部有延迟操作,在延迟操作还没有发生的时候,销毁了外部类,那么外部类对象无法回收,从而造成内存泄漏。

public class MainActivity extends AppCompatActivity {  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  

        new Handler().postDelayed(new Runnable() {  
            @Override  
            public void run() {  
            }  
        }, 10 * 1000);  
    }  
}

上面的代码中,Handler 和 Runnable 作为匿名内部类,都会持有 MainActivity 的引用,而它们内部有一个 10 秒钟的定时器,如果在打开 MainActivity 的 10 秒内关闭了 MainActivity,那么由于 Handler 和 Runnable 的生命周期比 MainActivity 长,会导致 MainActivity 无法被回收,从而造成内存泄漏。

解决方法:

那么应该如何避免内存泄漏呢?这里的一般套路就是把 Handler 和 Runnable 定义为静态内部类,这样它们就不再持有 MainActivity 的引用了,从而避免了内存泄漏:

public class MainActivity extends AppCompatActivity {  

    private Handler handler;  
    private Runnable runnable;  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  

        handler = new TestHandler();  
        runnable = new TestRunnable();  
        handler.postDelayed(runnable, 10 * 1000);  
    }  

    private static class TestHandler extends Handler {  
    }  

    private static class TestRunnable implements Runnable {  
        @Override  
        public void run() {  
        }  
    }  
}

最好再在 onDestory 调用 handler 的 removeCallbacks 方法来移除 Message,这样不但能避免内存泄漏,而且在退出 Activity 时取消了定时器,保证 10 秒以后也不会执行 run 方法:

@Override  
protected void onDestroy() {  
    super.onDestroy();  
    handler.removeCallbacks(runnable);  
}

还有一种特殊情况,如果 Handler 或者 Runnable 中持有 Context 对象,那么即使使用静态内部类,还是会发生内存泄漏:

public class MainActivity extends AppCompatActivity { 

    private Handler handler; 
    private Runnable runnable; 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main); 

        handler = new TestHandler(this); 
        runnable = new TestRunnable(); 
        handler.postDelayed(runnable, 10 * 1000); 
    } 

    private static class TestHandler extends Handler { 
        private Context context; 
        private TestHandler(Context context) { 
            this.context = context; 
        } 
    } 

    private static class TestRunnable implements Runnable { 
        @Override 
        public void run() { 
        } 
    } 
}

上面的代码,使用 leakcanary 工具会发现依然会发生内存泄漏,而且造成内存泄漏的原因和之前用非静态内部类是一样的,那么为什么会出现这种情况呢?

解决方法:

这是由于在 Handler 中持有 Context 对象,而这个 Context 对象是通过 TestHandler 的构造方法传入的,它是一个 MainActivity 对象,也就是说,虽然 TestHandler 作为静态内部类不会持有外部类 MainActivity 的引用,但是我们在调用它的构造方法时,自己传入了 MainActivity 的对象,从而 handler 对象持有了 MainActivity 的引用,handler 的生命周期比 MainActivity 的生命周期长,因此会造成内存泄漏,这种情况可以使用弱引用的方式来引用 Context 来避免内存泄漏,代码如下:

public class MainActivity extends AppCompatActivity { 

    private Handler handler; 
    private Runnable runnable; 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main); 

        handler = new TestHandler(new WeakReference<Context>(this)); 
        runnable = new TestRunnable(); 
        handler.postDelayed(runnable, 10 * 1000); 
    } 

    private static class TestHandler extends Handler { 
        private Context context; 
        private TestHandler(WeakReference<Context> weakContext) { 
            context = weakContext.get(); 
        } 
    } 

    private static class TestRunnable implements Runnable { 
        @Override 
        public void run() { 
        } 
    } 
} 
3.外部类中持有非静态内部类的静态对象
public class MainActivity extends AppCompatActivity { 

    private static Test test; 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main); 
        if (test == null) { 
            test = new Test(); 
        } 
    } 

    private class Test { 
    } 
}
解决方法:

这个其实和单例的原理是一样的,由于静态对象 test 的生命周期和整个应用的生命周期一致,而非静态内部类 Test 持有外部类 MainActivity 的引用,导致 MainActivity 退出的时候不能被回收,从而造成内存泄漏,解决的方法也很简单,把 test 改成非静态,这样 test 的生命周期和 MainActivity 是一样的了,就避免了内存泄漏。或者也可以把 Test 改成静态内部类,让 test 不持有 MainActivity 的引用,不过一般没有这种操作。

4.非静态内部类造成的内存泄漏

非静态内部类会持有外部类的引用,如果这个非静态的内部类的生命周期比它的外部类的生命周期长,那么当销毁外部类的时候,它无法被回收,就会造成内存泄漏。

解决方法:

1.通过静态内部类来去除隐式引用
2.修改静态内部类的构造方式,手动引入其外部类引用
3.当内存不可用时,不执行不可控代码(Android可以结合智能指针,WeakReference包裹外部类实例)
(注意:并不是所有的内部类只能使用静态内部类,只有在该内部类中的生命周期不可控制的情况下,我们要采用静态内部类,其它时候大家可以照旧。)

5.其他内存泄漏情况

还有一些其他的会导致内存泄漏的情况,比如 BraodcastReceiver 未取消注册,InputStream 未关闭等,平时写代码时多多注意即可避免。

相关文章

网友评论

      本文标题:Android内存泄漏原因及解决案例

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