Common memory leaks in Android

1 Activity object is not recycled

1.1 Static variables refer to Activity objects

When the Activty object is referenced through a static variable, the memory occupied by the Activty object will leak. The main reason is that static variables reside in the method area of ​​the JVM. Therefore, objects referenced by static variables will not be reclaimed by GC, because the objects they reference are themselves GC ROOTs. That is, the Activity object will not be recycled eventually, which will also cause a memory leak.

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

If you really want to use this awkward way of writing, remember to set mActivity to null in onDestroy().

1.2 Static View

Sometimes, when an Activity is started frequently, but the corresponding View is very time-consuming to read, we can maintain the rootView reference to the Activity through static View variables. In this way, you don't have to read and render the View every time you start the Activity. This is really a great way to increase the speed of Activity startup! But note that once the View is attached to our Window, it will hold a reference to the Context (ie Activity). And our View has a static variable, so the Activity is not recycled. Of course, it does not mean that static View cannot be used, but when using static View, you need to ensure that the static View is detached when resources are recycled.

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 Non-static inner classes

Non-static inner classes (including anonymous inner classes) will hold the outer class instance by default. If the life cycle of the inner class is longer than that of the outer class, it may cause memory leaks.

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

Consider changing to a static inner class, or create a new file and move the inner class over there.

1.4 Anonymous classes

Like inner classes, anonymous classes also hold references to outer classes.

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 泄漏了。

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


Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325193133&siteId=291194637