片段,保存onSaveInstanceState上的大型数据列表(如何防止TransactionTooLargeException)

时间:2017-01-31 08:50:14

标签: android android-fragments exception

在我的应用程序中,我在ViewPager中有Fragment。片段包含RecyclerView,其中包含根据用户选择从web api获取的数据列表。

在我的片段onSaveInstanceState上,我将列表数据保存到Bunde,以保存有关配置更改的数据等。

public void onSaveInstanceState(Bundle savedState) {
    super.onSaveInstanceState(savedState);
    savedState.putParcelableArrayList(LIST_STORAGE_KEY, new ArrayList<>(mItemAdapter.getModels()));
}

现在,我已开始在应用错误报告上看到TransactionTooLargeException

在某些情况下,似乎我放入Bundle的列表太大(因为它是相当复杂的对象的集合)。

我该如何处理这个案子?如何存储(和恢复)我的Fragment状态。

在ViewPager中的Fragments上使用setRetainInstance(true)是否可以?

3 个答案:

答案 0 :(得分:7)

为了保留大块数据,Google建议使用保留实例的Fragment。想法是创建没有视图的空片段和所有必需的字段,否则这些字段将被保存在Bundle中。 添加setRetainInstance(true);到Fragment的onCreate方法。而不是将数据保存在Activity的onDestroy上的Fragment中并将它们加载到onCreate上。以下是活动的例子:

public class MyActivity extends Activity {

private DataFragment dataFragment;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    // find the retained fragment on activity restarts
    FragmentManager fm = getFragmentManager();
    dataFragment = (DataFragment) fm.findFragmentByTag("data");

    // create the fragment and data the first time
    if (dataFragment == null) {
        // add the fragment
        dataFragment = new DataFragment();
        fm.beginTransaction().add(dataFragment, "data").commit();
        // load the data from the web
        dataFragment.setData(loadMyData());
    }

    // the data is available in dataFragment.getData()
    ...
}

@Override
public void onDestroy() {
    super.onDestroy();
    // store the data in the fragment
    dataFragment.setData(collectMyLoadedData());
}
}

片段的例子:

public class DataFragment extends Fragment {

// data object we want to retain
private MyDataObject data;

// this method is only called once for this fragment
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // retain this fragment
    setRetainInstance(true);
}

public void setData(MyDataObject data) {
    this.data = data;
}

public MyDataObject getData() {
    return data;
}
}

答案 1 :(得分:2)

如果您不希望您的片段使用setRetainInstance(true),那么您可以在活动中添加一个setRetainInstance(true)的空片段。这很有用,因为子片段无法使用setRetainInstance(true)

示例:

public class BaseActivity extends Activity {

  RetainedFragment retainedFragment;

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    retainedFragment = (RetainedFragment) getFragmentManager().findFragmentByTag("retained_fragment");
    if (retainedFragment == null) {
      retainedFragment = new RetainedFragment();
      getFragmentManager().beginTransaction().add(retainedFragment, "retained_fragment").commit();
    }
  }

  public <T> T getState(String key) {
    //noinspection unchecked
    return (T) retainedFragment.map.get(key);
  }

  public void saveState(String key, Object value) {
    retainedFragment.map.put(key, value);
  }

  public boolean has(String key) {
    return retainedFragment.map.containsKey(key);
  }

  public static class RetainedFragment extends Fragment {

    HashMap<String, Object> map = new HashMap<>();

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

  }

}

然后,在您的片段中,您可以将getActivity()投射到您的Activity类,并使用saveState(String, Object)getState(String)来保存您的列表。

关于此问题还有其他讨论,可在以下地点找到:

What to do on TransactionTooLargeException

android.os.TransactionTooLargeException on Nougat(接受的答案建议setRetainInstance(true))。

答案 2 :(得分:0)

setRetainInstance()是没有副作用的最好方法。使用static会导致内存泄漏,并且无法在onSaveInstanceState()中保存状态并将其恢复,因为setRetainInstance()会为您执行此操作。

因此,在片段类中为列表创建一个字段,并始终检查列表的nullsize以开始获取最新数据的操作