Как сохранить пользовательский ArrayList при повороте экрана Android?

У меня есть ArrayList с пользовательскими объектами, которые я хотел бы сохранять и восстанавливать при повороте экрана.

Я знаю, что это можно сделать с помощью onSaveInstanceState и onRestoreInstanceState, если бы я сделал ArrayList своим собственным классом, реализующим либо Parcelable, либо Serializable... Но есть ли способ сделать это без создания другого класса?


person Kalina    schedule 19.09.2012    source источник


Ответы (4)


Вам не нужно создавать новый класс для передачи ArrayList ваших пользовательских объектов. Вы должны просто реализовать класс Parcelable для своего объекта и использовать Bundle#putParcelableArrayList() в onSaveInstanceState() и onRestoreInstanceState(). Этот метод сам по себе будет хранить ArrayList Parcelables.


Поскольку тема Parcelables (а также Serializables и Bundles) иногда вызывает у меня головную боль, вот базовый пример ArrayList, содержащего пользовательские объекты Parcelable, хранящиеся в Bundle. (Это можно вырезать и вставить, макет не требуется.)

Реализация Parcelable

public class MyObject implements Parcelable {
    String color;
    String number;

    public MyObject(String number, String color) {
        this.color = color;
        this.number = number;
    }

    private MyObject(Parcel in) {
        color = in.readString();
        number = in.readString();
    }

    public int describeContents() {
        return 0;
    }

    @Override
    public String toString() {
        return number + ": " + color;
    }

    public void writeToParcel(Parcel out, int flags) {
        out.writeString(color);
        out.writeString(number);
    }

    public static final Parcelable.Creator<MyObject> CREATOR = new Parcelable.Creator<MyObject>() {
        public MyObject createFromParcel(Parcel in) {
            return new MyObject(in);
        }

        public MyObject[] newArray(int size) {
            return new MyObject[size];
        }
    };
}

Сохранить/восстановить состояния

public class Example extends ListActivity {
    ArrayList<MyObject> list;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if(savedInstanceState == null || !savedInstanceState.containsKey("key")) {
            String[] colors = {"black", "red", "orange", "cyan", "green", "yellow", "blue", "purple", "magenta", "white"};
            String[] numbers = {"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"};

            list = new ArrayList<MyObject>();
            for(int i = 0; i < numbers.length; i++) 
                list.add(new MyObject(numbers[i], colors[i]));
        }
        else {
            list = savedInstanceState.getParcelableArrayList("key");
        }

        setListAdapter(new ArrayAdapter<MyObject>(this, android.R.layout.simple_list_item_1, list));
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        outState.putParcelableArrayList("key", list);
        super.onSaveInstanceState(outState);
    }
}
person Sam    schedule 19.09.2012
comment
Спасибо! Однако я потерял бит private MyObject(Parcel in) { color = in.readString(); number = in.readString(); }... Это легко для строк, но как мне прочитать свой пользовательский массив? in.readArrayList(MyObject.class.getClassLoader()) выдает ошибку. - person Kalina; 20.09.2012
comment
Хм, в своем вопросе вы спрашивали об ArrayList с пользовательскими объектами, подразумевающими ArrayList<MyObject>, которые этот ответ обрабатывает в Parcelable.Creator; однако ваш комментарий только что спросил, как мне прочитать мой пользовательский массив, подразумевающий MyArrayList. Какой класс вы пытаетесь расширить? (Извините за технические подробности, но я хочу дать вам лучший ответ как можно быстрее.) - person Sam; 20.09.2012
comment
Извините, я имею в виду ArrayList‹MyObject› - person Kalina; 20.09.2012
comment
Хорошо, тогда вам не нужно readArrrayList() в вашем классе объектов. Посмотрите на onCreate() в примере действия, которое я опубликовал. Я проверяю: если ArrayList<MyObject> еще не существует, создайте его; если он существует, прочитайте в ArrayList<MyObject> с list = savedInstanceState.getParcelableArrayList("key");. Имеет ли это смысл? Я могу легко прокомментировать свой пример более подробно, если это поможет. - person Sam; 20.09.2012
comment
@Sam Как бы вы использовали Parcelable со списком строк ArrayList, а не с настраиваемыми объектами? - person Vishwa Iyer; 12.07.2014
comment
Я пытался реализовать это решение, но когда я вызываю outState.putParcelableArrayList("key", list);, он не вызывает MyObject.writeToParcel. Вместо этого он автоматически сохраняет все свойства в пакете. - person Marlon; 30.01.2017
comment
writeToParcel не разрешено для пользовательских списков, поэтому приложение зависает, например, при нажатии кнопки «Домой». - person user25; 11.03.2017
comment
@Сэм, привет, сэр. у меня очень большая проблема с сохранением и восстановлением, не могли бы вы мне помочь по этой теме? stackoverflow.com/q/43972147/6596724 - person tux-world; 15.05.2017
comment
@Sam Итак, оба они нужны? if(savedInstanceState == null || !savedInstanceState.containsKey(key)) Зачем нужен containsKey(), если первый тест ==null выполняется в тесте? - person AJW; 06.09.2018

Вы можете использовать onRetainNonConfigurationInstance(). Он позволяет сохранить любой объект перед изменением конфигурации и восстановить его после с помощью getLastNonConfigurationInstanceState().

Внутри активности:

    @Override
    public Object onRetainNonConfigurationInstance() {
        return myArrayList;
    }

Внутри onCreate():

    try{
        ArrayList myArrayList = (ArrayList)getLastNonConfigurationInstance();
    } catch(NullPointerException e) {}

Обработка изменений во время выполнения: http://developer.android.com/guide/topics/resources/runtime-changes.html Документация: http://developer.android.com/reference/android/app/Activity.html#onRetainNonConfigurationInstance%28%29

person Techwolf    schedule 20.09.2012
comment
Вау, никогда не знал об этой функции! Супер просто и именно то, что мне было нужно... Однако я вижу, что это устарело... - person Kalina; 20.09.2012
comment
К сожалению, это так, но он все еще работает, и нет другого простого способа сделать это. Ответ Сэма - единственный другой способ, который я нашел, но его намного сложнее реализовать. - person Techwolf; 20.09.2012
comment
Если вы используете фрагменты, вы можете вместо этого использовать Fragment.setRetainInstance(true), который является более новой версией (но работает только с фрагментами). Документация: developer.android.com/reference/android/app / - person Techwolf; 20.09.2012
comment
Это работает только в том случае, если фрагмент не был добавлен в стопку, к сожалению. - person Tyler Pfaff; 19.01.2013

Да, вы можете сохранить составной объект в общих настройках. Скажем:

Student mStudentObject = new Student();
     SharedPreferences appSharedPrefs = PreferenceManager
      .getDefaultSharedPreferences(this.getApplicationContext());
      Editor prefsEditor = appSharedPrefs.edit();
      Gson gson = new Gson();
      String json = gson.toJson(mStudentObject);
      prefsEditor.putString("MyObject", json);
      prefsEditor.commit(); 

и теперь вы можете получить свой объект как:

     SharedPreferences appSharedPrefs = PreferenceManager
     .getDefaultSharedPreferences(this.getApplicationContext());
     Editor prefsEditor = appSharedPrefs.edit();
     Gson gson = new Gson();
     String json = appSharedPrefs.getString("MyObject", "");
     Student mStudentObject = gson.fromJson(json, Student.class);
person Andriya    schedule 06.07.2015
comment
Плохая идея сделать это - в зависимости от размера сохраняемого списка. SharedPreferences — это файл XML, он не предназначен для хранения огромных наборов данных. - person mwieczorek; 12.08.2016

person    schedule
comment
Добро пожаловать в Stack Overflow. Не могли бы вы немного пояснить, как это решение отвечает на вопрос? - person tshimkus; 19.02.2019
comment
Это может сработать. Я еще не пробовал. Хотя это адский взлом. - person Josi; 20.08.2019