Утечка памяти с функцией writeToParcel (), вызываемой несколько раз после onSaveInstanceState () - PullRequest
0 голосов
/ 26 октября 2018

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

У действия есть собственный просмотрщик, содержащий около 60 фрагментов внутри (но до 350).Модель передается из действия во все фрагменты, а действия пользователя во фрагментах сохраняются в модели.

Всякий раз, когда я приостанавливаю свою активность, onSaveInstanceState запускается один раз и сразу после нескольких триггеров моего пакета.writeToParcel метод.Количество триггеров зависит от количества фрагментов, когда-либо загруженных в viewpager + 1. Поэтому при запуске активности, если я выключаю и снова включаю эмулятор, writeToParcel вызывается 3 раза (загружаются только 1-й и 2-й фрагменты).), если я проведу один раз вправо и сделаю это снова, он вызывается 4 раза (2-й показывает, а 3-й загружен), если я setExtPosition() на адаптере и перехожу к 10-му фрагменту, writeToParcel вызывается 7 раз(9, 10 и 11 ч загружены).

Конечно, если мой пользователь проведет пальцем по каждому фрагменту, он в конечном итоге получит уродливое TransactionTooLargeException, что приводит меня сюда.

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

В моей деятельности:

@Override
public void onSaveInstanceState (Bundle outState) {
    outState.putParcelable("model", myParcelable);
    super.onSaveInstanceState(outState);
}

В моем фрагменте:

public static MyFragment newInstance(Model model) {
    MyFragment fragment = new MyFragment();
    Bundle args = new Bundle();
    args.putParcelable(KEY_MODEL, model);
    fragment.setArguments(args);
    return fragment;
}

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Bundle args = getArguments();
    mModel = args.getParcelable(KEY_MODEL);
}

В моей продаваемой модели:

@Override
public void writeToParcel(Parcel dest, int flags) {
    int startSize = dest.dataSize();
    dest.writeString(foo); //one of these string is supposed to be null
    dest.writeString(bar);
    dest.writeString(foobar);
    dest.writeByte((byte) (isMyObjectTrue ? 1 : 0));
    dest.writeList(someStringList);
    dest.writeList(someIntegerList);
    dest.writeBundle(someBundle); //I use a Bundle to save a Map<String,String>
    int endSize = dest.dataSize();
}

Я запустил отладчик внутри метода writeToParcel(), и я был удивлен, увидев, что startSizeникогда не равен 0. Это нормально?

Я искал по всему коду, и putParcelable() или любой метод записи с parcelable в его имени вызывается только в этом упражнении и во фрагменте newInstance().

Как я могу найти причину этого странного экспоненциального поведения?

PS: конечно, не стесняйтесь просить больше кода.

РЕДАКТИРОВАТЬ

Я реализовал решение, рекомендованное @Ben P., и проблема значительно улучшилась, но не полностью решена.В моей работе реализован интерфейс, в котором теперь есть метод getModel(), называемый onAttach(), и setUserInput(char userInput), который я использую для обновления модели из фрагмента.Метод фрагмента newInstance() больше не сохраняет модель.

MyFragment

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    try {
        callBack = (MyInterface) context; //callBack is a class field
        mModel = callBack.getModel();     //mModel too
    } catch (ClassCastException e) {
        throw new ClassCastException(context.toString() + " must implement MyInterface");
    }
}

Это превратило экспоненциальную задачу в линейную задачу, которая явно лучше, но все же проблема.

Теперь writeToParcel() вызывается только один раз, но общий размер посылки продолжает расти с увеличением количества загруженных элементов.Моя модель занимает около 3 КБ внутри посылки (+/- 10% в зависимости от количества входов), что измеряется endSize-startSize.

Как узнать, откуда происходит рост?

1 Ответ

0 голосов
/ 26 октября 2018

Прежде чем конкретно перейти к вашей проблеме, я хочу отметить, что Bundle, переданный setArguments(), является частью состояния экземпляра фрагмента.Каждый раз, когда фрагмент уничтожается и воссоздается, эти аргументы должны быть сохранены.Таким образом, все, что вы добавляете в Bundle, может быть разобрано и разобрано во время изменений конфигурации.


У действия есть собственный просмотрщик, содержащий около 60 фрагментов внутри (но до 350).Модель передается из действия во все фрагменты, а действия пользователя во фрагментах сохраняются в модели.

Похоже, у вас есть один объект Model, который разделяют все фрагменты.В этом случае я рекомендую , а не передавать объект модели каждому фрагменту как часть его аргументов Bundle.Это приведет к огромному дублированию при сохранении и восстановлении состояния экземпляра.Вместо этого я бы выставил метод в вашем Activity (что-то вроде getModel()) и затем вызвал бы его из ваших фрагментов для получения экземпляра модели.

С другой стороны, это также звучит так, как будто выВы только начинаете с того же объекта Model, и каждый фрагмент может каким-то образом мутировать его.Это будет означать, что вы do должны что-то сохранить в состояние экземпляра для каждого фрагмента ... но вы можете оптимизировать здесь.Вместо того, чтобы сохранять и восстанавливать весь объект Model, возможно, вы могли бы просто сохранить различия.То есть, если фрагмент # 1 изменяет name модели, а фрагмент # 2 меняет value модели, тогда вы можете иметь фрагмент # 1 только для сохранения нового имени и фрагмент № 2 только для нового значения.Выполнение этого вместо сохранения двух дополнительных копий объекта модели потенциально может привести к огромной экономии.

...