Вопрос по android, java – Когда именно утечка безопасна для использования (анонимных) внутренних классов?

294

Я читал некоторые статьи об утечках памяти в Android и смотрел это интересное видео из Google I / Oна предмет.

Тем не менее, я не полностью понимаю эту концепцию, особенно когда она безопасна или опасна для пользователя.inner classes inside an Activity.

Вот что я понял:

Утечка памяти произойдет, если экземпляр внутреннего класса выживет дольше, чем его внешний класс (Activity). - & GT;In which situations can this happen?

В этом примере я предполагаю, что нет риска утечки, потому что нет никакого способа расширения анонимного классаOnClickListener будет жить дольше, чем активность, верно?

    final Dialog dialog = new Dialog(this);
    dialog.setContentView(R.layout.dialog_generic);
    Button okButton = (Button) dialog.findViewById(R.id.dialog_button_ok);
    TextView titleTv = (TextView) dialog.findViewById(R.id.dialog_generic_title);

    // *** Handle button click
    okButton.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
            dialog.dismiss();
        }
    });

    titleTv.setText("dialog title");
    dialog.show();

Опасен ли этот пример и почему?

// We are still inside an Activity
_handlerToDelayDroidMove = new Handler();
_handlerToDelayDroidMove.postDelayed(_droidPlayRunnable, 10000);

private Runnable _droidPlayRunnable = new Runnable() { 
    public void run() {
        _someFieldOfTheActivity.performLongCalculation();
    }
};

У меня есть сомнения относительно того, что понимание этой темы связано с пониманием в деталях того, что сохраняется, когда действие уничтожается и воссоздается.

Это?

Допустим, я просто изменил ориентацию устройства (что является наиболее распространенной причиной утечек). когдаsuper.onCreate(savedInstanceState) будет называться в моемonCreate()Это восстановит значения полей (какими они были до изменения ориентации)? Восстановит ли это также состояния внутренних классов?

Я понимаю, что мой вопрос не очень точный, но я действительно ценю любое объяснение, которое могло бы прояснить ситуацию.

This blog post а такжеthis blog post иметь хорошую информацию об утечках памяти и внутренних классах. :) Alex Lockwood

Ваш Ответ

1   ответ
603

То, что вы спрашиваете, довольно сложный вопрос. Хотя вы можете думать, что это всего лишь один вопрос, вы на самом деле задаете несколько вопросов одновременно. Я сделаю все возможное, зная, что я должен покрыть это, и, надеюсь, некоторые другие присоединятся, чтобы покрыть то, что я могу пропустить.

Nested Classes: Introduction

Поскольку я не уверен, насколько вам удобно с ООП в Java, это затронет несколько основ. Вложенный класс - это когда определение класса содержится в другом классе. Существует два основных типа: статические вложенные классы и внутренние классы. Реальная разница между ними:

  • Static Nested Classes:
    • Are considered "top-level".
    • Do not require an instance of the containing class to be constructed.
    • May not reference the containing class members without an explicit reference.
    • Have their own lifetime.
  • Inner Nested Classes:
    • Always require an instance of the containing class to be constructed.
    • Automatically have an implicit reference to the containing instance.
    • May access the container's class members without the reference.
    • Lifetime is supposed to be no longer than that of the container.

Garbage Collection and Inner Classes

Сборка мусора происходит автоматически, но пытается удалить объекты в зависимости от того, считает ли она, что они используются. Сборщик мусора довольно умен, но не безупречен. Он может только определить, используется ли что-то, существует ли активная ссылка на объект.

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

Это может привести к ситуации, когда внутренний объект жив (посредством ссылки), но ссылки на содержащий объект уже удалены из всех других объектов. Внутренний объект, следовательно, поддерживает вмещающий объект, потому что он будетalways иметь ссылку на это. Проблема в том, что, если он не запрограммирован, невозможно вернуться к содержащемуся объекту, чтобы проверить, жив ли он.

Наиболее важным аспектом этой реализации является то, что не имеет значения, находится ли она в Деятельности или является доступной для рисования. Ты сможешьalways должны быть методичными при использовании внутренних классов и следить за тем, чтобы они никогда не переживали объекты контейнера. К счастью, если это не основной объект вашего кода, утечки могут быть небольшими по сравнению. К сожалению, это некоторые из самых трудных утечек, которые можно найти, потому что они, вероятно, останутся незамеченными, пока многие из них не протекут.

Solutions: Inner Classes

  • Gain temporary references from the containing object.
  • Allow the containing object to be the only one to keep long-lived references to the inner objects.
  • Use established patterns such as the Factory.
  • If the inner class does not require access to the containing class members, consider turning it into a static class.
  • Use with caution, regardless of whether it is in an Activity or not.

Activities and Views: Introduction

Действия содержат много информации, чтобы иметь возможность запускать и отображать. Действия определяются характеристикой, которая должна иметь представление. У них также есть определенные автоматические обработчики. Независимо от того, указали вы это или нет, у Activity есть неявная ссылка на содержащееся в ней представление.

Чтобы создать представление, оно должно знать, где его создать и есть ли у него дочерние элементы, чтобы его можно было отобразить. Это означает, что каждое представление имеет ссылку на действие (черезgetContext()). Более того, каждый View хранит ссылки на своих потомков (т.е.getChildAt()). Наконец, каждое представление хранит ссылку на визуализированное растровое изображение, которое представляет его отображение.

Всякий раз, когда у вас есть ссылка на действие (или контекст действия), это означает, что вы можете следовать по всей цепочке вниз по иерархии макета. Вот почему утечки памяти относительно действий или представлений так важны. Это может бытьton о потере памяти все сразу.

Activities, Views and Inner Classes

Учитывая приведенную выше информацию о внутренних классах, это наиболее распространенные утечки памяти, но их также чаще всего избегают. Хотя желательно, чтобы внутренний класс имел прямой доступ к членам класса «Деятельности», многие хотят просто сделать их статичными, чтобы избежать потенциальных проблем. Проблема с деятельностью и представлениями гораздо глубже.

Leaked Activities, Views and Activity Contexts

Все сводится к контексту и жизненному циклу. Существуют определенные события (например, ориентация), которые убивают контекст действия. Так как очень многим классам и методам требуется контекст, разработчики иногда пытаются сохранить некоторый код, захватывая ссылку на контекст и удерживая его. Просто так получилось, что многие объекты, которые мы должны создать, чтобы запустить наше действие, должны существовать вне жизненного цикла действия, чтобы позволить действию делать то, что ему нужно. Если какой-либо из ваших объектов, когда он уничтожен, имеет ссылку на Activity, ее Context или любой из его Views, вы только что слили эту Activity и все дерево View.

Solutions: Activities and Views

  • Avoid, at all costs, making a Static reference to a View or Activity.
  • All references to Activity Contexts should be short lived (the duration of the function)
  • If you need a long-lived Context, use the Application Context (getBaseContext() or getApplicationContext()). These do not keep references implicitly.
  • Alternatively, you may limit the destruction of an Activity by overriding Configuration Changes. However, this does not stop other potential events from destroying the Activity. While you can do this, you may still want to refer to the above practices.

Runnables: Introduction

Runnables на самом деле не так уж и плохо. Я имею в виду, ониcould Да, но на самом деле мы уже поразили большинство опасных зон. Runnable - это асинхронная операция, которая выполняет задачу независимо от потока, в котором она была создана. Большинство исполняемых объектов создаются из потока пользовательского интерфейса. По сути, использование Runnable создает другой поток, чуть более управляемый. Если вы классифицируете Runnable как стандартный класс и следуете приведенным выше рекомендациям, у вас должно быть несколько проблем. Реальность такова, что многие разработчики не делают этого.

Из-за простоты, читабельности и логического выполнения программы многие разработчики используют анонимные внутренние классы для определения своих Runnables, как, например, в примере, который вы создали выше. Это приводит к примеру, подобному тому, который вы напечатали выше. Анонимный внутренний класс - это в основном дискретный внутренний класс. Вам просто не нужно создавать совершенно новое определение и просто переопределять соответствующие методы. Во всех других отношениях это внутренний класс, что означает, что он хранит неявную ссылку на свой контейнер.

Runnables and Activities/Views

Ура! Этот раздел может быть коротким! Из-за того, что Runnables работают вне текущего потока, опасность с ними связана с длительными асинхронными операциями. Если выполняемый объект определен в Activity или View как анонимный внутренний класс ИЛИ вложенный внутренний класс, существуют некоторые очень серьезные опасности. Это потому, что, как уже говорилось ранее, этоhas знать, кто его контейнер. Введите изменение ориентации (или уничтожение системы). Теперь просто вернитесь к предыдущим разделам, чтобы понять, что только что произошло. Да, твой пример довольно опасен.

Solutions: Runnables

  • Try and extend Runnable, if it doesn't break the logic of your code.
  • Do your best to make extended Runnables static, if they must be nested classes.
  • If you must use Anonymous Runnables, avoid creating them in any object that has a long-lived reference to an Activity or View that is in use.
  • Many Runnables could just as easily have been AsyncTasks. Consider using AsyncTask as those are VM Managed by default.

Answering the Final Question Теперь, чтобы ответить на вопросы, которые не былиdirectly адресованные другими разделами этого поста. Вы спросили: «Когда объект внутреннего класса может выжить дольше, чем его внешний класс?» Прежде чем мы перейдем к этому, позвольте мне еще раз подчеркнуть: хотя вы и вправе беспокоиться об этом в разделе «Действия», это может вызвать утечку в любом месте. Я приведу простой пример (без использования Activity) просто для демонстрации.

Ниже приведен типичный пример базовой фабрики (отсутствует код).

public class LeakFactory
{//Just so that we have some data to leak
    int myID = 0;
// Necessary because our Leak class is an Inner class
    public Leak createLeak()
    {
        return new Leak();
    }

// Mass Manufactured Leak class
    public class Leak
    {//Again for a little data.
       int size = 1;
    }
}

Это не обычный пример, но достаточно простой для демонстрации. Ключ здесь - конструктор ...

public class SwissCheese
{//Can't have swiss cheese without some holes
    public Leak[] myHoles;

    public SwissCheese()
    {//Gotta have a Factory to make my holes
        LeakFactory _holeDriller = new LeakFactory()
    // Now, let's get the holes and store them.
        myHoles = new Leak[1000];

        for (int i = 0; i++; i<1000)
        {//Store them in the class member
            myHoles[i] = _holeDriller.createLeak();
        }

    // Yay! We're done! 

    // Buh-bye LeakFactory. I don't need you anymore...
    }
}

Теперь у нас есть утечки, но нет фабрики. Несмотря на то, что мы выпустили Фабрику, она останется в памяти, потому что каждая утечка имеет ссылку на нее. Это даже не имеет значения, что внешний класс не имеет данных. Это происходит гораздо чаще, чем можно подумать. Нам не нужен создатель, только его творения. Таким образом, мы создаем его временно, но используем творения бесконечно.

Представьте, что происходит, когда мы слегка меняем конструктор.

public class SwissCheese
{//Can't have swiss cheese without some holes
    public Leak[] myHoles;

    public SwissCheese()
    {//Now, let's get the holes and store them.
        myHoles = new Leak[1000];

        for (int i = 0; i++; i<1000)
        {//WOW! I don't even have to create a Factory... 
        // This is SOOOO much prettier....
            myHoles[i] = new LeakFactory().createLeak();
        }
    }
}

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

Conclusion

В них перечислены наиболее известные опасности использования этих объектов ненадлежащим образом. В целом, этот пост должен был охватить большинство ваших вопросов, но я понимаю, что это был недолговечный пост, поэтому, если вам нужны пояснения, просто дайте мне знать. Пока вы будете следовать вышеизложенным методам, вы будете очень мало беспокоиться о утечке.

Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceededstatic inner classError: User Rate Limit ExceededDocsError: User Rate Limit ExceededstaticError: User Rate Limit ExceededinnerError: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Большое спасибо за этот четкий и подробный ответ. Я просто не понимаю, что вы подразумеваете под "многими разработчиками, использующими замыкания для определения своих Runnables". Sébastien

Похожие вопросы