Android常见的内存泄漏

2018-06-12  本文已影响0人  HappyGhh
1、内部类导致内存泄漏

1、内部类实例会隐式持有外部类的引用。
例如在Activity中创建一个内部类实例,然后在内部类实例中执行耗时操作,在执行任务时,关掉Activity,这时Activity对象不会被释放,因为内部类还持有对Activity的引用,但此时Activity已经没用了,所以这时就出现了内存泄漏。

1.1 Thread线程
在Activity中创建一个内部类继承Thread,在该Thread中执行一些后台任务,未执行完时,关闭Activity,此时会出现内存泄漏。

如何解决:避免内部类隐式调用外部类Activity即可。
解决方案:
把这个内部类声明为静态类,这时内部类将不再隐式持有外部类Activity的引用。此时内部类也无法使用外部类中的方法、变量(只能引用静态的方法和变量)

问题:如何在防止内存泄漏的情况下,去访问外部类中的非静态方法、变量??
解决办法:
通过软引用或者弱引用的方式引用外部类Activity
Java强引用,软引用,弱引用,虚引用

1.2 Handler

public class MainActivity extends AppCompatActivity {

    private static final int MESSAGE_DELAY = 0;
    private Button mButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mButton = (Button) findViewById(R.id.button);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startDelayTask();
            }
        });
    }

    private void startDelayTask() {
        //发送一条消息,该消息会被延时10秒后才处理
        Message message = Message.obtain();
        message.obj = "按钮点击15秒后再弹出";
        message.what = MESSAGE_DELAY;
        mHandler.sendMessageDelayed(message, 15000);
    }

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_DELAY:
                    Toast.makeText(MainActivity.this, (String) msg.obj, Toast.LENGTH_SHORT).show();
                    mButton.setText("延时修改了按钮的文本");
                    break;
            }
        }
    };
}

在sendMessageDelayed()发送消息时,延时时间过长,在中途退出Activity,会出现内存泄漏,出现的原因也是因为内部类持有外部类的引用。

解决方案:
1)将MyHandler声明为静态的
2)将MyHandler作为一个独立的类,不作为内部类使用

在onDestory()中移除所有的消息

 mHandler.removeCallbacksAndMessages(null);
2、非静态内部类的静态实例

非静态的内部类创建了一个静态实例。非静态内部类会持有外部类Activity的引用,后来又创建了一个这个内部类的静态实例。

这个静态实例不会在Activity被关掉时一块被回收(静态实例的生命周期跟应用的生命周期一样长,Activity被销毁后,静态实例并不会被释放)。

非静态内部类持有外部引用,而该内部类的静态实例不会及时回收,所以才导致了内存泄露。

解决方案:
将内部类申明为静态的内部类。

3、Context导致内存泄露

例如我们经常使用的工具类,需要传入Context对象,如果传入当前的Activity对象的话容易导致内存泄漏,因为工具类一般都是静态的,并且对其调用时机也不能很好的把控,这个时候需要传入该应用的Context来避免内存泄漏

系统中的Context的具体实现子类有:Activity、Application、Service。
虽然Context能做很多事,但并不是随便拿到一个Context实例就可以为所欲为,它的使用还是有一些规则限制的。在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。

出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。

conext作用域.png

上图中Application和Service所不推荐的两种使用情况:

如果我们用ApplicationContext去启动一个LaunchMode为standard的Activity的时候会报错

javaandroid.util.AndroidRuntimeException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?

这是因为非Activity类型的Context并没有所谓的任务栈,所以待启动的Activity就找不到栈了。解决这个问题的方法就是为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动的时候就为它创建一个新的任务栈,而此时Activity是以singleTask模式启动的。所有这种用Application启动Activity的方式不推荐使用,Service的原因跟Application一致。

在Application和Service中去layout inflate也是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。所以这种方式也不推荐使用。一句话总结:凡是跟UI相关的,都建议使用Activity做为Context来处理;其他的一些操作,Service,Activity,Application等实例Context都可以,当然了,注意Context引用的持有,防止内存泄漏。

上一篇下一篇

猜你喜欢

热点阅读