Android中常见的内存泄漏

1 Activity对象未被回收

1.1 静态变量引用Activity对象

通过静态变量引用Activty对象时,会导致Activty对象所占内存内漏。主要是因为,静态变量是驻扎在JVM的方法区,因此,静态变量引用的对象是不会被GC回收的,因为它们所引用的对象本身就是GC ROOT。即最终导致Activity对象不被回收,从而也就造成内存泄漏。

public class TopicDetailActivity extends AppCompatActivity {
    private static Activity mActivity;

    public static void enterActivity(Activity activity) {
        mActivity = activity;
        Intent intent = new Intent(activity, TopicDetailActivity.class);
        activity.startActivity(intent);
    }
}

如果实在要用这样别扭的写法,记得在onDestroy()里把mActivity置为null。

1.2 静态View

有时,当一个Activity经常启动,但是对应的View读取非常耗时,我们可以通过静态View变量来保持对该Activity的rootView引用。这样就可以不用每次启动Activity都去读取并渲染View了。这确实是一个提高Activity启动速度的好方法!但是要注意,一旦View attach到我们的Window上,就会持有一个Context(即Activity)的引用。而我们的View有事一个静态变量,所以导致Activity不被回收。当然了,也不是说不能使用静态View,但是在使用静态View时,需要确保在资源回收时,将静态View detach掉。

public class MainActivity extends AppCompatActivity {

private static TextView tv;

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

    tv = (TextView) findViewById(R.id.tv);
    tv.setText("Hello world");
}
}

1.3 非静态内部类

非静态内部类(包括匿名内部类)默认会持有外部类实例,如果内部类的生命周期长于外部类,则可能导致内存泄漏。

public class MainActivity extends AppCompatActivity {  
@Override  
protected void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    setContentView(R.layout.activity_main);  
    MyThread myThread = new MyThread();  
    myThread.start();  
}  

class MyThread extends Thread {  
    @Override  
    public void run() {  
        while (true) {  
            try {  
                Thread.sleep(65536);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
}  
}

可以考虑改成静态内部类,或者新创建一个文件,把内部类挪过去。

1.4 匿名类

与内部类一样,匿名类也会持有外部类的引用。

1.5 Handler

我们知道,主线程的Looper对象不断从消息队列中取出消息,然后再交给Handler处理。如果在Activity中定义Handler对象,那么Handler肯定是持有Activty的引用。而每个Message对象是持有Handler的引用的(Message对象的target属性持有Handler引用),从而导致Message间接引用到了Activity。如果在Activty destroy之后,消息队列中还有Message对象,Activty是不会被回收的。当然了,如果消息正在准备(处于延时入队期间)放入到消息队列中也是一样的。

private final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {

}
};


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


handler.postDelayed(new Runnable() {
    @Override
    public void run() { /* ... */ }
}, Integer.MAX_VALUE); 
}

解决办法就是,将Handler放入单独的类或者将Handler放入到静态内部类中(静态内部类不会持有外部类的引用)。如果想要在handler内部去调用所在的外部类Activity,可以在handler内部使用弱引用的方式指向所在Activity,这样不会导致内存泄漏。还可以在onDestroy()时,removeCallbacks或removeCallBacks来清除MessageQueue残留的Message,已保证Activity不会被Message hold住。

1.6 Threads和TimerTask

Threads和Timer导致内存泄漏的原因跟内部类一样。虽然在新的线程中创建匿名类,但是只要是匿名类/内部类,它都会持有外部类引用。

void spawnThread() {
new Thread() {
    @Override public void run() {
        while(true);
    }
}.start();
}

void scheduleTimer() {
new Timer().schedule(new TimerTask() {
    @Override
    public void run() {
        while(true);
    }
}, Long.MAX_VALUE >> 1);
}

1.7 监听器

当我们需要使用系统服务时,比如执行某些后台任务、为硬件访问提供接口等等系统服务。我们需要把自己注册到服务的监听器中。然而,这会让服务持有 activity 的引用,如果程序员忘记在 activity 销毁时取消注册,那就会导致 activity 泄漏了。

扫描二维码关注公众号,回复: 101148 查看本文章

2 集合对象造成的泄漏

当我们定义一个静态的集合类时,请注意,这可能会导致内存泄漏!前面我们提到过,静态变量所引用的对象是不会被回收掉的。而我的静态集合类中,包含有大量的对象,这些对象不会被回收。另外,如果集合中保存的对象又引用到了其他的大对象,如超长字符串、Bitmap、大数组等,很容易造成OOM。

3 资源对象没关闭造成内存泄漏

当我们打开资源时,一般都会使用缓存。比如读写文件资源、打开数据库资源、使用Bitmap资源等等。当我们不再使用时,应该关闭它们,使得缓存内存区域及时回收。虽然有些对象,如果我们不去关闭,它自己在finalize()函数中会自行关闭。但是这得等到GC回收时才关闭,这样会导致缓存驻留一段时间。如果我们频繁的打开资源,内存泄漏带来的影响就比较明显了。

4 使用对象池避免频繁创建对象

在我们需要频繁创建使用某个类时,或者是在for循环里面创建新的对象时,导致JVM不断创建同一个类。我们知道,在使用Message对象时,不是直接new出来的,而是通过obtain方法获取,以及recycle方法回收。这是典型的享元模式(不熟悉的同学参考《从Android代码中来记忆23种设计模式 》【https://link.jianshu.com/?t=http://blog.csdn.net/huachao1001/article/details/51536074】)。我们可以通过使用对象池来实现.

import android.support.v4.util.Pools;

public class MyObject {
private static final Pools.SynchronizedPool<MyObject> MY_POOLS = new Pools.SynchronizedPool<>(10);

public static MyObject obtain() {
    MyObject object = MY_POOLS.acquire();
    if (object == null)
        object = new MyObject();
    return object;
}

public void recycle() {
    MY_POOLS.release(this);
}
}

参考:https://www.jianshu.com/users/0a7e42698e4b/latest_articles


猜你喜欢

转载自blog.csdn.net/yq5277/article/details/80170552