美文网首页
何止学习Andoid内存泄漏优化

何止学习Andoid内存泄漏优化

作者: 何止搬砖工 | 来源:发表于2020-07-15 00:54 被阅读0次

一、Android内存泄露及优化

1、单例模式造成内存泄漏

单例的静态特性使得它的生命周期同应用的生命周期一样长,在整个应用程序的生命周期它都不能正常被回收,从而导致内存泄露。

举例:

public class MySingleton {

    private Context context;

    private static MySingleton ourInstance ;

    public static synchronized MySingleton getInstance(Context context) {

        if (null == ourInstance){

            ourInstance = new MySingleton(context);
        }
        return ourInstance;
    }

    private MySingleton(Context context) {
        
        this.context = context;

    }
    
}

像上面代码中这样的单例,如果我们在调用getInstance(Context context)方法的时候传入的context参数是Activity、Service等上下文,就会导致内存泄露。

优化方案

(1)、使用全局的上下文Application Context就是应用程序的上下文,和单例的生命周期一样长,这样就避免了内存泄漏。
(2)、不将context作为成员变量,而在用到的地方,用形参传入。

2、静态变量导致内存泄露

举例

public class MainActivity extends AppCompatActivity {

    public static final String TAG = "MainActivity";
    private static Info sInfo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (sInfo != null) {
            sInfo = new Info(this);
        }
    }
}

class Info {
    public Info(Activity activity) {
    }
}

Info作为Activity的静态成员,并且持有Activity的引用,但是sInfo作为静态变量,生命周期肯定比Activity长。所以当Activity退出后,sInfo仍然引用了Activity,Activity不能被回收,这就导致了内存泄露。

优化

在Activity销毁之前置空。

  protected void onDestroy() {
        super.onDestroy();
        if(sInfo != null){
            sInfo = null;
        }
    }

3、非静态内部类导致内存泄露(Handler,Thread,AsyncTask)

非静态内部类(包括匿名内部类)默认就会持有外部类的引用,当非静态内部类对象的生命周期比外部类对象的生命周期长时,就会导致内存泄露。

举例

public class MainActivity extends AppCompatActivity {

    public static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        sendMessage();
    }

    private void sendMessage() {
        Message msg = Message.obtain();
        msg.what = 1;
        mHandler.sendMessage(msg);
    }

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 1) {
                // 做相应逻辑
            }
        }
    };
}

mHandler会作为成员变量保存在发送的消息msg中,即msg持有mHandler的引用,而mHandler是Activity的非静态内部类实例,即mHandler持有Activity的引用,那么我们就可以理解为msg间接持有Activity的引用。msg被发送后先放到消息队列MessageQueue中,然后等待Looper的轮询处理(MessageQueue和Looper都是与线程相关联的,MessageQueue是Looper引用的成员变量,而Looper是保存在ThreadLocal中的)。那么当Activity退出后,msg可能仍然存在于消息对列MessageQueue中未处理或者正在处理,那么这样就会导致Activity无法被回收,以致发生Activity的内存泄露。

优化

通常在Android开发中如果要使用内部类,但又要规避内存泄露,一般都会采用静态内部类+弱引用的方式。

public class MainActivity extends AppCompatActivity {

    public static final String TAG = "MainActivity";
    private Handler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler = new MyHandler(this);
        sendMessage();
    }

    private void sendMessage() {
        Message msg = Message.obtain();
        msg.what = 1;
        mHandler.sendMessage(msg);
    }

    private static class MyHandler extends Handler {

        private WeakReference<MainActivity> activityWeakReference;

        public MyHandler(MainActivity activity) {
            activityWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = activityWeakReference.get();
            if (activity != null) {
                if (msg.what == 1) {
                    // 做相应逻辑
                }
            }
        }
    }
}

上面的做法确实避免了Activity导致的内存泄露,发送的msg不再已经没有持有Activity的引用了,但是msg还是有可能存在消息队列MessageQueue中,所以更好的是在Activity销毁时就将mHandler的回调和发送的消息给移除掉。

@Override
protected void onDestroy() {
    super.onDestroy();
    mHandler.removeCallbacksAndMessages(null);
}

非静态内部类造成内存泄露还有一种情况就是使用或者。在Activity中直接new Thread或AsyncTask
这样的写法非常地不友好,这种方式新建的子线程和都是匿名内部类对象,默认就隐式的持有外部的引用,导致内存泄露。要避免内存泄露的话还是需要像上面一样使用静态内部类+弱应用的方式。

4、未取消注册或回调导致内存泄露

比如我们在Activity中注册广播,如果在Activity销毁后不取消注册,那么这个刚播会一直存在系统中,同上面所说的非静态内部类一样持有Activity引用,导致内存泄露。因此注册广播后在Activity销毁后一定要取消注册。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        this.registerReceiver(mReceiver, new IntentFilter());
    }

    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // 接收到广播需要做的逻辑
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        this.unregisterReceiver(mReceiver);
    }
}

5、集合中的对象未清理造成内存泄露

在循环中把引用o释放了,但是它被添加到了objectList中,所以objectList也持有对象的引用,此时该对象是无法被GC的。因此对象如果添加到集合中,还必须从中删除,最简单的方法,置空集合对象即可

6、资源未关闭或释放导致内存泄露

一些IO/File流、cursor没及时关闭,这些资源在进行读写操作时通常都使用了缓冲,如果及时不关闭,这些缓冲对象就会一直被占用而得不到释放,以致发生内存泄露。因此我们在不需要使用它们的时候就及时关闭,以便缓冲能及时得到释放,从而避免内存泄露。

7、WebView造成内存泄露

因为在加载网页后会长期占用内存而不能被释放,因此我们在销毁后要调用它的方法来销毁它以释放内存。
下面的持有Activity引用,造成Webview内存无法释放,即使是调用了等方法都无法解决问题(Android5.1之后)。
最终的解决方案是:在销毁之前需要先将从父容器中移除,然后在销毁。

相关文章

网友评论

      本文标题:何止学习Andoid内存泄漏优化

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