Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Deep Dive Into Android State Restoration

Deep Dive Into Android State Restoration

Learn about how Android saves state in general in order to be able to restore an application in the exact same state the process was prior being killed because of a low memory condition or a configuration change. In this talk we mainly focus on the Parcelable and Parcel objects and how Android uses them to save/restore some important stateful information such as the complete UI state.

Cyril Mottier

September 22, 2014
Tweet

More Decks by Cyril Mottier

Other Decks in Programming

Transcript

  1. Deep Dive Into Android
    State Restoration

    View Slide

  2. TWITTER
    @cyrilmottier
    !
    WEBSITE
    cyrilmottier.com

    View Slide

  3. View Slide

  4. View Slide

  5. View Slide

  6. View Slide

  7. The story of a newbie
    Android developer

    View Slide

  8. Kevin has just developed his first
    Android app

    View Slide

  9. He discovers an annoying bug:
    Fields are cleared on rotate

    View Slide

  10. 3 options

    View Slide

  11. 3 options
    Don’t care

    View Slide

  12. 3 options
    Don’t care Block orientation

    View Slide

  13. 3 options
    Don’t care Block orientation Use configChanges
    Hint: all options are

    View Slide

  14. 1 2 android:name=".HomeActivity"!
    3 android:configChanges="orientation">!
    4 !
    5 !
    6 !
    7

    View Slide

  15. Kevin’s satisfied

    View Slide

  16. Still having issues on…

    View Slide

  17. language changes
    Still having issues on…

    View Slide

  18. 1 2 android:name=".HomeActivity"!
    3 android:configChanges="orientation!
    4 !
    5 !
    6 !
    7
    |locale">

    View Slide

  19. ANGRY!
    Angry Kevin is

    View Slide

  20. 1 2 android:name=".HomeActivity"!
    3 android:configChanges=“orientation|locale|!
    mcc|mnc|touchscreen|keyboard|!
    keyboardHidden|navigation|uiMode|!
    screenLayout|fontScale|screenSize|!
    smallestScreenSize">!
    4 !
    5 !
    6 !
    7

    View Slide

  21. The nightmare continues…
    Still having issues when moving the app to the background

    View Slide

  22. God save the
    STATE

    View Slide

  23. State restoration
    key components

    View Slide

  24. The container
    Parcel

    View Slide

  25. Parcelable
    The container
    Parcel
    The content
    Primitives types
    Primitives arrays

    View Slide

  26. The content
    Primitives types
    Primitives arrays
    1 parcel.writeInt(1);!
    2 parcel.writeLong(2L);!
    3 parcel.writeFloat(3F);!
    4 parcel.writeString("Hi!");
    Parcelable

    View Slide

  27. 1 parcel.writeIntArray(new int[]{1, 2, 3});!
    2 parcel.writeLongArray(new long[]{1L, 2L, 3L});!
    3 parcel.writeDoubleArray(new double[]{1, 2, 3});!
    4 parcel.writeStringArray(new String[]{!
    5 "Hi", "Droidcon", "guys!"!
    6 });
    The content
    Primitives types
    Primitives arrays
    Parcelable

    View Slide

  28. 1 public final class Suggestion implements Parcelable {!
    2 !
    3 public final String id;!
    4 public final String name;!
    5 public final int type;!
    6 !
    7 public Suggestion(String id, String name, int type) {!
    8 this.id = Objects.requireNonNull(id);!
    9 this.name = Objects.requireNonNull(name);!
    10 this.type = type;!
    11 }!
    12 !
    13 }

    View Slide

  29. 1 @Override!
    2 public int describeContents() {!
    3 return 0;!
    4 }!
    5 !
    6 @Override!
    7 public void writeToParcel(Parcel dest, int flags) {!
    8 dest.writeString(id);!
    9 dest.writeString(name);!
    10 dest.writeInt(type);!
    11 }!
    12
    13 public static final Parcelable.Creator CREATOR = !
    14 new Parcelable.Creator() {!
    15 public Suggestion createFromParcel(Parcel in) {!
    16 return new Suggestion(in.readString(), // !
    17 in.readString(), //!
    18 in.readInt());!
    19 }!
    20 !
    21 public Suggestion[] newArray(int size) {!
    22 return new Suggestion[size];!
    23 }!
    24 };

    View Slide

  30. Parcelable.Creator!
    The base creator interface
    !
    Parcelable.ClassLoaderCreator!
    A creator with the ClassLoader passed on read.
    !
    ParcelableCompat &
    ParcelableCompatCreatorCallbacks!
    Compatibility stuff

    View Slide

  31. Bundle
    A key-value map & type-safe Parcelable

    View Slide

  32. Parcel!
    internally uses reflection
    (required to get the CREATOR instance)
    …i.e. beware Proguard

    View Slide

  33. Activity level
    state restoration

    View Slide

  34. onCreate(null)

    View Slide

  35. onCreate(null)

    View Slide

  36. : non-null Bundle
    onSaveInstanceState( )
    onCreate(null)

    View Slide

  37. onSaveInstanceState( )
    onCreate( )
    onCreate(null)
    : non-null Bundle

    View Slide

  38. onSaveInstanceState( )
    onCreate( )
    onRestoreInstanceState( )
    onCreate(null)
    : non-null Bundle

    View Slide

  39. onSaveInstanceState( )
    onCreate( )
    onRestoreInstanceState( )
    onCreate(null)
    : non-null Bundle

    View Slide

  40. onSaveInstanceState( )
    onCreate( )
    onRestoreInstanceState( )
    onCreate(null)
    : non-null Bundle

    View Slide

  41. What to save?
    Non persistent or non reconstructible info

    View Slide

  42. 1 public class SearchActivity extends Activity {!
    2 !
    3 private static final String STATE_OUTWARD = "state:outward";!
    4 !
    5 private DateComponents mOutward;!
    6 !
    7 @Override!
    8 protected void onCreate(Bundle inState) {!
    9 super.onCreate(inState);!
    10 !
    11 if (inState != null) {!
    12 DateComponents components = inState.getParcelable(STATE_OUTWARD);!
    13 if (components != null) {!
    14 setOutward(components);!
    15 }!
    16 }!
    17 }!
    18 !
    19 @Override!
    20 protected void onSaveInstanceState(Bundle outState) {!
    21 super.onSaveInstanceState(outState);!
    22 outState.putParcelable(STATE_OUTWARD, mOutward);!
    23 }!
    24 }

    View Slide

  43. onSaveInstanceState saves
    Window

    View Slide

  44. Window Fragments
    onSaveInstanceState saves

    View Slide

  45. Window Fragments Dialogs
    onSaveInstanceState saves

    View Slide

  46. Always call the
    SUPER METHODS
    Android has no guards on save-related methods

    View Slide

  47. android:stateNotNeeded
    For restart-on-crash apps only
    (i.e. launcher app)

    View Slide

  48. Developer options
    Don’t keep activities

    View Slide

  49. Developer options
    Don’t keep activities

    View Slide

  50. View level
    state restoration

    View Slide

  51. Android saves UI state
    AUTOMAGICALLY

    View Slide

  52. Android saves UI state
    AUTOMAGICALLY
    (aka “It just works!™”)

    View Slide

  53. Android saves UI state
    AUTOMAGICALLY
    (aka “It just works!™”)
    …except in some cases

    View Slide

  54. Works out-of-the-box if Views
    1. Have an ID
    2. Are “save” enabled
    3. Come from the framework

    View Slide

  55. saveHierarchyState()
    It always begins with a call to

    View Slide

  56. EditText!
    @id/text
    RelativeLayout!
    @id/container
    CheckBox!
    @id/check_box
    TextView

    View Slide

  57. EditText!
    @id/text
    RelativeLayout!
    @id/container
    CheckBox!
    @id/check_box
    TextView
    SparseArray

    View Slide

  58. RelativeLayout!
    @id/container
    CheckBox!
    @id/check_box
    TextView
    SparseArray
    S1
    EditText!
    @id/text

    View Slide

  59. @id/container
    SparseArray
    RelativeLayout!
    @id/container
    CheckBox!
    @id/check_box
    TextView
    S1
    EditText!
    @id/text

    View Slide

  60. EditText!
    @id/text
    RelativeLayout!
    @id/container
    CheckBox!
    @id/check_box
    TextView
    @id/container S1
    S2 SparseArray

    View Slide

  61. @id/text
    @id/container S1
    S2
    SparseArray
    RelativeLayout!
    @id/container
    CheckBox!
    @id/check_box
    TextView
    EditText!
    @id/text

    View Slide

  62. RelativeLayout!
    @id/container
    CheckBox!
    @id/check_box
    TextView
    @id/text
    @id/container S1
    S2
    SparseArray
    S3
    EditText!
    @id/text

    View Slide

  63. @id/text
    @id/container
    @id/check_box
    S1
    S2
    S3
    SparseArray
    RelativeLayout!
    @id/container
    CheckBox!
    @id/check_box
    TextView
    EditText!
    @id/text

    View Slide

  64. Controlling save
    setSaveEnabled(boolean)
    setSaveFromParentEnabled(boolean)

    View Slide

  65. restoreHierarchyState()
    It always ends with a call to

    View Slide

  66. @id/container S1
    @id/text S2
    @id/check_box S3
    SparseArray
    RelativeLayout!
    @id/container
    CheckBox!
    @id/check_box
    TextView
    EditText!
    @id/text

    View Slide

  67. @id/container S1
    S2
    S3
    @id/text
    @id/check_box
    SparseArray
    RelativeLayout!
    @id/container
    CheckBox!
    @id/check_box
    TextView
    EditText!
    @id/text

    View Slide

  68. RelativeLayout!
    @id/container
    CheckBox!
    @id/check_box
    TextView
    @id/container S1
    S2
    @id/check_box S3
    S1
    @id/text
    SparseArray
    EditText!
    @id/text

    View Slide

  69. @id/container S1
    @id/text S2
    @id/check_box S3
    SparseArray
    RelativeLayout!
    @id/container
    CheckBox!
    @id/check_box
    TextView
    EditText!
    @id/text

    View Slide

  70. EditText!
    @id/text
    RelativeLayout!
    @id/container
    CheckBox!
    @id/check_box
    TextView
    S2
    @id/container S1
    @id/text
    @id/check_box S3
    S2 SparseArray

    View Slide

  71. @id/container S1
    @id/text S2
    @id/check_box S3
    SparseArray
    RelativeLayout!
    @id/container
    CheckBox!
    @id/check_box
    TextView
    EditText!
    @id/text

    View Slide

  72. RelativeLayout!
    @id/container
    CheckBox!
    @id/check_box
    TextView
    @id/container S1
    @id/text S2
    @id/check_box S3
    S3
    SparseArray
    EditText!
    @id/text

    View Slide

  73. Ensure your Views’ IDs are
    unique & constant

    View Slide

  74. 1 static class SavedState extends BaseSavedState {!
    2 int checked;!
    3 !
    4 SavedState(Parcelable superState) { super(superState); }!
    5 !
    6 private SavedState(Parcel in) {!
    7 super(in);!
    8 checked = in.readInt();!
    9 }!
    10 !
    11 @Override!
    12 public void writeToParcel(Parcel out, int flags) {!
    13 super.writeToParcel(out, flags);!
    14 out.writeInt(checked);!
    15 }!
    16 !
    17 public static final Parcelable.Creator CREATOR = !
    18 new Parcelable.Creator() {!
    19 public SavedState createFromParcel(Parcel in) {!
    20 return new SavedState(in);!
    21 }!
    22 public SavedState[] newArray(int size) {!
    23 return new SavedState[size];!
    24 }!
    25 };!
    26 }

    View Slide

  75. 1 @Override!
    2 public Parcelable onSaveInstanceState() {!
    3 final Parcelable superState = super.onSaveInstanceState();!
    4 SavedState ss = new SavedState(superState);!
    5 ss.checked = isChecked() ? 1 : 0;!
    6 return ss;!
    7 }!
    8 !
    9 @Override!
    10 public void onRestoreInstanceState(Parcelable state) {!
    11 SavedState ss = (SavedState) state;!
    12 super.onRestoreInstanceState(ss.getSuperState());!
    13 setChecked(ss.checked == 1);!
    14 }

    View Slide

  76. FrameLayout!
    @id/root
    ImageBox!
    @id/container1
    CheckBox!
    @id/check_box
    ImageView!
    @id/image
    ImageBox!
    @id/container2
    CheckBox!
    @id/check_box
    ImageView!
    @id/image

    View Slide

  77. FrameLayout!
    @id/root
    ImageBox!
    @id/container1
    ImageView!
    @id/image
    CheckBox!
    @id/check_box
    ImageBox!
    @id/container2
    ImageView!
    @id/image
    CheckBox!
    @id/check_box
    Custom views with children with same IDs

    View Slide

  78. 1 static class SavedState extends BaseSavedState {!
    2 !
    3 SparseArray childrenStates;!
    4 !
    5 SavedState(Parcelable superState) { super(superState); }!
    6 !
    7 private SavedState(Parcel in, ClassLoader loader) {!
    8 super(in);!
    9 childrenStates = in.readSparseArray(loader);!
    10 }!
    11 !
    12 @Override!
    13 public void writeToParcel(Parcel out, int flags) {!
    14 super.writeToParcel(out, flags);!
    15 out.writeSparseArray(childrenStates);!
    16 }!
    17 !
    18 public static final Creator CREATOR = ParcelableCompat.!
    19 newCreator(new ParcelableCompatCreatorCallbacks() {!
    20 @Override!
    21 public SavedState createFromParcel(Parcel source, ClassLoader loader) {!
    22 return new SavedState(source, loader);!
    23 }!
    24 @Override!
    25 public SavedState[] newArray(int size) {!
    26 return new SavedState[size];!
    27 }!
    28 });!
    29 }

    View Slide

  79. 1 @Override!
    2 public Parcelable onSaveInstanceState() {!
    3 final Parcelable superState = super.onSaveInstanceState();!
    4 SavedState ss = new SavedState(superState);!
    5 ss.childrenStates = new SparseArray();!
    6 for (int i = 0; i < getChildCount(); i++) {!
    7 getChildAt(i).saveHierarchyState(ss.childrenStates);!
    8 }!
    9 return ss;!
    10 }!
    11 !
    12 @Override!
    13 public void onRestoreInstanceState(Parcelable state) {!
    14 SavedState ss = (SavedState) state;!
    15 super.onRestoreInstanceState(ss.getSuperState());!
    16 for (int i = 0; i < getChildCount(); i++) {!
    17 getChildAt(i).restoreHierarchyState(ss.childrenStates);!
    18 }!
    19 }

    View Slide

  80. That has solved nothing!
    Still need to block save/restore dispatch

    View Slide

  81. 1 @Override!
    2 protected void dispatchSaveInstanceState(SparseArray container) {!
    3 dispatchFreezeSelfOnly(container);!
    4 }!
    5 !
    6 @Override!
    7 protected void dispatchRestoreInstanceState(SparseArray container) {!
    8 dispatchThawSelfOnly(container);!
    9 }

    View Slide

  82. Fragment level
    state restoration

    View Slide

  83. Very similar to Activities
    state restoration lifecycle.
    (Fragments are tied to Activity after all)

    View Slide

  84. Fragment blocks Activity
    save mechanism
    with framework
    setSaveFromParentEnabled(false)
    with support library
    NoSaveStateFrameLayout

    View Slide

  85. 2 distinct states
    Fragment + View
    common case
    View only
    detach, addToBackStack, etc.

    View Slide

  86. Can to be used to create smooth transitions between
    your Activities:
    !
    - Save the state SA of A
    - Start B with no animations passing SA
    - Apply SA to B
    - Transition between A and B was smooth
    Leveraging save/restore

    View Slide

  87. Summarizing
    in three rules

    View Slide

  88. Always save the state
    An Android app must survive configuration
    changes & low memory conditions.

    View Slide

  89. Only save essential info
    Only save info that is non persistent
    or can not be reconstructed later.

    View Slide

  90. Use correct levels
    Save instance states at the appropriate
    component level: Activity, Fragment or View.

    View Slide

  91. Thank you!
    @cyrilmottier
    cyrilmottier.com

    View Slide

  92. Resources
    Dressed for Iceland • Cécile Bernard
    Moelwynion, Eryri, Cymru • Marc Poppleton
    Happy, Confused, Wink, Sad, Angry • Megan Sheehan
    Floppy-Disk • Alex Auda Samora
    Fonts
    Source Sans Pro
    Courier

    View Slide