Android: статические поля и утечки памяти

Я изучаю лучшие практики предотвращения утечек памяти Context/Activity при создании представлений, и я не могу найти определенного ответа на то, что есть или не разрешено, когда дело доходит до статических полей в классах.

Скажем, у меня есть код этой формы:

public class MyOuterClass extends Activity{
   private MyInnerClass;
   MyInnerClass = (MyInnerClass) findViewById(<XML call here>);
   MyInnerClass.myXInt = 3;

   // onCreate(), onResume(), etc.

   public static class MyInnerClass extends SurfaceView implements Runnable{
      // Safe variables?
      private static int myXInt, myYInt;
      private static boolean myBoolean;
      // Potentially safe?
      private static Canvas myCanvas;
      // Definitely bad.
      private static Context myContext;

      public MyInnerClass(Context context){
         myContext = context;        // This is bad.
      }
   }
}

Я немного смущен тем, что JVM фактически рассматривает ClassLoader для MyInnerClass. Технически, поскольку это объект SurfaceView, кажется, что статические переменные всегда должны существовать, как только приложение создало экземпляр MyInnerClass один раз (что происходит, когда представление сначала завышено), а затем остается там до тех пор, пока сам приложение не будет завершено. Если это так, то что препятствует тому, чтобы объекты Bitmaps и Canvas оставались открытыми и заполняли кучу?

Единственное утверждение, которое я когда-либо повторял снова и снова, заключается в том, что вы не можете пропустить статический контекст, как показано в конструкторе, но он никогда не выходит за рамки этого. Это действительно единственное, что вы не можете сделать?

Ответы

Ответ 1

В Java/Android переменная или константа static не будет собираться мусором. Он просто остается там, когда класс, который его удерживает, загружается через загрузчик классов. Класс-загрузчик afaik всегда одинаковый для всех классов внутри вашего приложения, а тот, который имеет статические ссылки на все ваши классы (например, MyInnerClass.class). Так как загрузчик классов не уходит, ваши классы не будут делать это ни с тех пор, как они ссылаются, а потому не собирают мусор.

Как в вашем примере

public class SomeClass extends SurfaceView {
  private static Context myContext;

  public MyInnerClass(Context context){
     myContext = context;        // This is bad.
  }
}

Это действительно плохо. Даже если не существует ссылки на SomeClass (например, Activity, который показал, что ваш пользовательский SurfaceView закончился), статическая ссылка на Context (и любая другая переменная/константа static в SomeClass останется. Вы можете считать, что все они просочились, поскольку сбор мусора невозможен, т.е. Context и т.д. Если у вас есть регулярная ссылка на переменную, то после того, как экземпляр, содержащий эту переменную, больше не ссылается на него, весь экземпляр, включая его ссылки на другие вещи могут и будут собираться мусором. Java может даже обрабатывать круговые ссылки в порядке.

Для констант, которые вы хотите, чтобы это произошло, и обычно это плохо, поскольку количество констант и объем памяти, которые они занимают, невелики. Также константы не должны (не должны) ссылаться на другие экземпляры, которые занимают большие объемы памяти, такие как Context или Bitmap.

Помимо возможности создавать утечки памяти через статические переменные, вы также можете создавать проблемы, если вы не хотите иметь только одну вещь для всех экземпляров одновременно. Например, если вы сохранили Bitmap вашего SurfaceView в переменной static, у вас не может быть двух разных изображений. Даже если два SurfaceView не отображаются одновременно, вы можете столкнуться с проблемами, поскольку каждый новый экземпляр, вероятно, перезапишет старое изображение, и если вы вернетесь к другому SurfaceView, вы неожиданно увидите неправильное изображение. Я почти уверен, что вы не хотите использовать static здесь.

Тот факт, что ваш внутренний класс является static class, не означает, что вам нужно использовать статические переменные - это просто означает, что он ведет себя как метод static, поскольку он не может использовать переменные экземпляра (те, которые не являются static) в вашем классе.

Чтобы избежать утечек памяти, вы просто не должны использовать статические переменные вообще. Нет необходимости использовать их, если вы не делаете специальные материалы (например, подсчет экземпляров класса). Константы в порядке.