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 full-size slide

  2. TWITTER
    @cyrilmottier
    !
    WEBSITE
    cyrilmottier.com

    View full-size slide

  3. The story of a newbie
    Android developer

    View full-size slide

  4. Kevin has just developed his first
    Android app

    View full-size slide

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

    View full-size slide

  6. 3 options
    Don’t care

    View full-size slide

  7. 3 options
    Don’t care Block orientation

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  10. Kevin’s satisfied

    View full-size slide

  11. Still having issues on…

    View full-size slide

  12. language changes
    Still having issues on…

    View full-size slide

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

    View full-size slide

  14. ANGRY!
    Angry Kevin is

    View full-size slide

  15. 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 full-size slide

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

    View full-size slide

  17. God save the
    STATE

    View full-size slide

  18. State restoration
    key components

    View full-size slide

  19. The container
    Parcel

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  22. 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 full-size slide

  23. 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 full-size slide

  24. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  28. Activity level
    state restoration

    View full-size slide

  29. onCreate(null)

    View full-size slide

  30. onCreate(null)

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  37. 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 full-size slide

  38. onSaveInstanceState saves
    Window

    View full-size slide

  39. Window Fragments
    onSaveInstanceState saves

    View full-size slide

  40. Window Fragments Dialogs
    onSaveInstanceState saves

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  43. Developer options
    Don’t keep activities

    View full-size slide

  44. Developer options
    Don’t keep activities

    View full-size slide

  45. View level
    state restoration

    View full-size slide

  46. Android saves UI state
    AUTOMAGICALLY

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size 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 full-size slide

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

    View full-size slide

  68. Ensure your Views’ IDs are
    unique & constant

    View full-size slide

  69. 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 full-size slide

  70. 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 full-size slide

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

    View full-size slide

  72. 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 full-size slide

  73. 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 full-size slide

  74. 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 full-size slide

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

    View full-size slide

  76. 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 full-size slide

  77. Fragment level
    state restoration

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  81. 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 full-size slide

  82. Summarizing
    in three rules

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  86. Thank you!
    @cyrilmottier
    cyrilmottier.com

    View full-size slide

  87. 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 full-size slide