From 88aec77116324487265c2ccdf63e06b6597789da Mon Sep 17 00:00:00 2001 From: Ralf Wondratschek Date: Mon, 19 Dec 2016 19:44:20 +0100 Subject: [PATCH 1/3] Provide a generic interface to serialize and deserialize presenters. This is useful to recreate presenters after a process has died. --- .../thirtyinch/TiConfiguration.java | 27 ++++++++++--- .../grandcentrix/thirtyinch/TiPresenter.java | 28 ++++++++++++- .../thirtyinch/internal/PresenterSavior.java | 16 +++----- .../internal/TiActivityDelegate.java | 39 +++++++++++++----- .../internal/TiFragmentDelegate.java | 36 +++++++++++++---- .../serialize/TiPresenterSerializer.java | 40 +++++++++++++++++++ 6 files changed, 152 insertions(+), 34 deletions(-) create mode 100644 thirtyinch/src/main/java/net/grandcentrix/thirtyinch/serialize/TiPresenterSerializer.java diff --git a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiConfiguration.java b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiConfiguration.java index 94e7d97b..6fe28d4c 100644 --- a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiConfiguration.java +++ b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiConfiguration.java @@ -15,16 +15,17 @@ package net.grandcentrix.thirtyinch; -import net.grandcentrix.thirtyinch.callonmainthread.CallOnMainThread; -import net.grandcentrix.thirtyinch.distinctuntilchanged.DistinctUntilChanged; -import net.grandcentrix.thirtyinch.internal.PresenterSavior; -import net.grandcentrix.thirtyinch.internal.TiPresenterProvider; - import android.app.Activity; import android.app.Application; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; +import net.grandcentrix.thirtyinch.callonmainthread.CallOnMainThread; +import net.grandcentrix.thirtyinch.distinctuntilchanged.DistinctUntilChanged; +import net.grandcentrix.thirtyinch.internal.PresenterSavior; +import net.grandcentrix.thirtyinch.internal.TiPresenterProvider; +import net.grandcentrix.thirtyinch.serialize.TiPresenterSerializer; + /** * Configure how ThirtyInch should handle the {@link TiPresenter}. * Can be applied to the constructor of a presenter {@link TiPresenter#TiPresenter(TiConfiguration)} @@ -198,6 +199,16 @@ public Builder setUseStaticSaviorToRetain(final boolean enabled) { return this; } + /** + * An {@link TiPresenterSerializer} instance should be provided, if the {@link TiPresenter} should + * be restored, after a process has died, but the UI component is being recreated. Since this is + * a rare use case the default value is {@code null} and presenters aren't restored. + * @param serializer An implementation which can serialize and deserialize any presenter. + */ + public Builder setPresenterSerializer(TiPresenterSerializer serializer) { + mConfig.mPresenterSerializer = serializer; + return this; + } } public static final TiConfiguration DEFAULT = new Builder().build(); @@ -210,6 +221,8 @@ public Builder setUseStaticSaviorToRetain(final boolean enabled) { private boolean mUseStaticSaviorToRetain = true; + private TiPresenterSerializer mPresenterSerializer; + /** * use {@link Builder} to construct a configuration. */ @@ -231,4 +244,8 @@ public boolean shouldRetainPresenter() { public boolean useStaticSaviorToRetain() { return mUseStaticSaviorToRetain; } + + public TiPresenterSerializer getPresenterSerializer() { + return mPresenterSerializer; + } } diff --git a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiPresenter.java b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiPresenter.java index 56ac2d21..f0c40006 100644 --- a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiPresenter.java +++ b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiPresenter.java @@ -16,8 +16,6 @@ package net.grandcentrix.thirtyinch; -import net.grandcentrix.thirtyinch.internal.OneTimeRemovable; - import android.app.Activity; import android.content.Intent; import android.support.annotation.NonNull; @@ -25,6 +23,9 @@ import android.support.annotation.VisibleForTesting; import android.support.v4.app.Fragment; +import net.grandcentrix.thirtyinch.internal.OneTimeRemovable; +import net.grandcentrix.thirtyinch.serialize.TiPresenterSerializer; + import java.util.ArrayList; import java.util.List; import java.util.Queue; @@ -84,6 +85,7 @@ public enum State { private boolean mCalled = true; private final TiConfiguration mConfig; + private final String mId; private LinkedBlockingQueue> mPostponedViewActions = new LinkedBlockingQueue<>(); @@ -106,6 +108,7 @@ public TiPresenter() { */ public TiPresenter(final TiConfiguration config) { mConfig = config; + mId = generateId(); } /** @@ -521,4 +524,25 @@ private void sendPostponedActionsToView(V view) { mPostponedViewActions.poll().call(view); } } + + private String generateId() { + return getClass().getSimpleName() + ":" + hashCode() + ":" + System.nanoTime(); + } + + /** + * Persists this instance if the configuration provides a {@link TiPresenterSerializer}. + */ + public void persist() { + TiPresenterSerializer serializer = mConfig.getPresenterSerializer(); + if (serializer != null) { + serializer.serialize(this, mId); + } + } + + /** + * @return A unique ID of this instance. + */ + public final String getId() { + return mId; + } } diff --git a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/PresenterSavior.java b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/PresenterSavior.java index 11486b93..6d7b4ed1 100644 --- a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/PresenterSavior.java +++ b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/PresenterSavior.java @@ -15,14 +15,14 @@ package net.grandcentrix.thirtyinch.internal; -import net.grandcentrix.thirtyinch.TiActivity; -import net.grandcentrix.thirtyinch.TiLog; -import net.grandcentrix.thirtyinch.TiPresenter; - import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; +import net.grandcentrix.thirtyinch.TiActivity; +import net.grandcentrix.thirtyinch.TiLog; +import net.grandcentrix.thirtyinch.TiPresenter; + import java.util.HashMap; /** @@ -52,7 +52,7 @@ public TiPresenter recover(final String id) { } public String safe(@NonNull final TiPresenter presenter) { - final String id = generateId(presenter); + final String id = presenter.getId(); TiLog.v(TAG, "safe presenter with id " + id + " " + presenter); mPresenters.put(id, presenter); return id; @@ -62,10 +62,4 @@ public String safe(@NonNull final TiPresenter presenter) { void clear() { mPresenters.clear(); } - - private String generateId(@NonNull final TiPresenter presenter) { - return presenter.getClass().getSimpleName() - + ":" + presenter.hashCode() - + ":" + System.nanoTime(); - } } diff --git a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiActivityDelegate.java b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiActivityDelegate.java index a6141c44..ad8ffeb9 100644 --- a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiActivityDelegate.java +++ b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiActivityDelegate.java @@ -15,6 +15,13 @@ package net.grandcentrix.thirtyinch.internal; +import android.app.Activity; +import android.content.res.Configuration; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; + import net.grandcentrix.thirtyinch.BindViewInterceptor; import net.grandcentrix.thirtyinch.Removable; import net.grandcentrix.thirtyinch.TiActivity; @@ -24,13 +31,7 @@ import net.grandcentrix.thirtyinch.TiView; import net.grandcentrix.thirtyinch.callonmainthread.CallOnMainThreadInterceptor; import net.grandcentrix.thirtyinch.distinctuntilchanged.DistinctUntilChangedInterceptor; - -import android.app.Activity; -import android.content.res.Configuration; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; +import net.grandcentrix.thirtyinch.serialize.TiPresenterSerializer; import java.util.List; @@ -137,9 +138,9 @@ public void onCreate_afterSuper(final Bundle savedInstanceState) { } // try to recover with the PresenterSavior + final String recoveredPresenterId; if (savedInstanceState != null) { - final String recoveredPresenterId = savedInstanceState - .getString(SAVED_STATE_PRESENTER_ID); + recoveredPresenterId = savedInstanceState.getString(SAVED_STATE_PRESENTER_ID); if (mPresenter == null) { if (recoveredPresenterId != null) { @@ -166,7 +167,14 @@ public void onCreate_afterSuper(final Bundle savedInstanceState) { // this presenter from the savior PresenterSavior.INSTANCE.free(recoveredPresenterId); mPresenterId = PresenterSavior.INSTANCE.safe(mPresenter); + + TiPresenterSerializer serializer = mPresenter.getConfig().getPresenterSerializer(); + if (serializer != null && recoveredPresenterId != null) { + serializer.cleanup(recoveredPresenterId); + } } + } else { + recoveredPresenterId = null; } if (mPresenter == null) { @@ -174,6 +182,13 @@ public void onCreate_afterSuper(final Bundle savedInstanceState) { mPresenter = mPresenterProvider.providePresenter(); TiLog.v(mLogTag.getLoggingTag(), "created Presenter: " + mPresenter); final TiConfiguration config = mPresenter.getConfig(); + final TiPresenterSerializer presenterSerializer = config.getPresenterSerializer(); + + if (recoveredPresenterId != null && presenterSerializer != null) { + mPresenter = presenterSerializer.deserialize(mPresenter, recoveredPresenterId); + TiLog.v(mLogTag.getLoggingTag(), "deserialized Presenter: " + mPresenter); + } + if (config.shouldRetainPresenter() && config.useStaticSaviorToRetain()) { mPresenterId = PresenterSavior.INSTANCE.safe(mPresenter); } @@ -228,6 +243,11 @@ public void onDestroy_afterSuper() { if (destroyPresenter) { mPresenter.destroy(); PresenterSavior.INSTANCE.free(mPresenterId); + TiPresenterSerializer serializer = mPresenter.getConfig().getPresenterSerializer(); + if (serializer != null) { + serializer.cleanup(mPresenterId); + } + } else { TiLog.v(mLogTag.getLoggingTag(), "not destroying " + mPresenter + " which will be reused by the next Activity instance, recreating..."); @@ -236,6 +256,7 @@ public void onDestroy_afterSuper() { public void onSaveInstanceState_afterSuper(final Bundle outState) { outState.putString(SAVED_STATE_PRESENTER_ID, mPresenterId); + mPresenter.persist(); } public void onStart_afterSuper() { diff --git a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiFragmentDelegate.java b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiFragmentDelegate.java index cb25366b..8393ad66 100644 --- a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiFragmentDelegate.java +++ b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiFragmentDelegate.java @@ -1,5 +1,11 @@ package net.grandcentrix.thirtyinch.internal; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.ViewGroup; + import net.grandcentrix.thirtyinch.BindViewInterceptor; import net.grandcentrix.thirtyinch.Removable; import net.grandcentrix.thirtyinch.TiConfiguration; @@ -10,12 +16,7 @@ import net.grandcentrix.thirtyinch.TiView; import net.grandcentrix.thirtyinch.callonmainthread.CallOnMainThreadInterceptor; import net.grandcentrix.thirtyinch.distinctuntilchanged.DistinctUntilChangedInterceptor; - -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.view.LayoutInflater; -import android.view.ViewGroup; +import net.grandcentrix.thirtyinch.serialize.TiPresenterSerializer; import java.util.List; @@ -102,10 +103,11 @@ public void onCreateView_beforeSuper(final LayoutInflater inflater, } public void onCreate_afterSuper(final Bundle savedInstanceState) { + final String recoveredPresenterId; if (mPresenter == null && savedInstanceState != null) { // recover with Savior // this should always work. - final String recoveredPresenterId = savedInstanceState + recoveredPresenterId = savedInstanceState .getString(SAVED_STATE_PRESENTER_ID); if (recoveredPresenterId != null) { TiLog.v(mLogTag.getLoggingTag(), @@ -118,15 +120,29 @@ public void onCreate_afterSuper(final Bundle savedInstanceState) { // this presenter from the savior PresenterSavior.INSTANCE.free(recoveredPresenterId); mPresenterId = PresenterSavior.INSTANCE.safe(mPresenter); + + TiPresenterSerializer serializer = mPresenter.getConfig().getPresenterSerializer(); + if (serializer != null) { + serializer.cleanup(recoveredPresenterId); + } } TiLog.v(mLogTag.getLoggingTag(), "recovered Presenter " + mPresenter); } + } else { + recoveredPresenterId = null; } if (mPresenter == null) { mPresenter = mPresenterProvider.providePresenter(); TiLog.v(mLogTag.getLoggingTag(), "created Presenter: " + mPresenter); final TiConfiguration config = mPresenter.getConfig(); + final TiPresenterSerializer presenterSerializer = config.getPresenterSerializer(); + + if (recoveredPresenterId != null && presenterSerializer != null) { + mPresenter = presenterSerializer.deserialize(mPresenter, recoveredPresenterId); + TiLog.v(mLogTag.getLoggingTag(), "deserialized Presenter: " + mPresenter); + } + if (config.shouldRetainPresenter() && config.useStaticSaviorToRetain()) { mPresenterId = PresenterSavior.INSTANCE.safe(mPresenter); } @@ -191,6 +207,11 @@ public void onDestroy_afterSuper() { if (destroyPresenter) { mPresenter.destroy(); PresenterSavior.INSTANCE.free(mPresenterId); + TiPresenterSerializer serializer = mPresenter.getConfig().getPresenterSerializer(); + if (serializer != null) { + serializer.cleanup(mPresenterId); + } + } else { TiLog.v(mLogTag.getLoggingTag(), "not destroying " + mPresenter + " which will be reused by the next Activity instance, recreating..."); @@ -199,6 +220,7 @@ public void onDestroy_afterSuper() { public void onSaveInstanceState_afterSuper(final Bundle outState) { outState.putString(SAVED_STATE_PRESENTER_ID, mPresenterId); + mPresenter.persist(); } public void onStart_afterSuper() { diff --git a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/serialize/TiPresenterSerializer.java b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/serialize/TiPresenterSerializer.java new file mode 100644 index 00000000..b646e57b --- /dev/null +++ b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/serialize/TiPresenterSerializer.java @@ -0,0 +1,40 @@ +package net.grandcentrix.thirtyinch.serialize; + +import android.support.annotation.NonNull; + +import net.grandcentrix.thirtyinch.TiPresenter; +import net.grandcentrix.thirtyinch.TiView; + +/** + * Interface to serialize and deserialize presenters. The interface should be implemented, if + * a {@link TiPresenter} should be restored, after a process has died, but the UI component is + * being recreated. + */ +public interface TiPresenterSerializer { + + /** + * Serialize the given presenter and write the data to disk. + * + * @param presenter The {@link TiPresenter} which should be serialized. + * @param presenterId The ID of the given presenter. + */ + void serialize(@NonNull TiPresenter presenter, @NonNull String presenterId); + + /** + * Deserialize the given presenter. Implementations can either return the same value + * with adjusted fields or a new instance. + * + * @param presenter The {@link TiPresenter} which should be deserialized and recreated. + * @param presenterId The ID of the given presenter. + * @return Either the same instance as the argument or a new object. + */ + @NonNull + > P deserialize(@NonNull P presenter, @NonNull String presenterId); + + /** + * Optionally clean up temporary data. + * + * @param presenterId The ID of the presenter which should be freed. + */ + void cleanup(@NonNull String presenterId); +} From 0f5c13974d6e158ea810d422a391e84a83918e23 Mon Sep 17 00:00:00 2001 From: Pascal Welsch Date: Thu, 29 Dec 2016 14:03:04 +0100 Subject: [PATCH 2/3] Make de-/serialization an implementation which allows to save private fields all byte[] based --- .../thirtyinch/TiConfiguration.java | 42 +++++------ .../grandcentrix/thirtyinch/TiPresenter.java | 71 ++++++++++++------- .../thirtyinch/TiPresenterSerializer.java | 28 ++++++++ .../internal/TiActivityDelegate.java | 40 +++++------ .../internal/TiFragmentDelegate.java | 2 +- .../serialize/TiPresenterSerializer.java | 40 ----------- 6 files changed, 111 insertions(+), 112 deletions(-) create mode 100644 thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiPresenterSerializer.java delete mode 100644 thirtyinch/src/main/java/net/grandcentrix/thirtyinch/serialize/TiPresenterSerializer.java diff --git a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiConfiguration.java b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiConfiguration.java index 6fe28d4c..e8511c2b 100644 --- a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiConfiguration.java +++ b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiConfiguration.java @@ -15,16 +15,16 @@ package net.grandcentrix.thirtyinch; -import android.app.Activity; -import android.app.Application; -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; - import net.grandcentrix.thirtyinch.callonmainthread.CallOnMainThread; import net.grandcentrix.thirtyinch.distinctuntilchanged.DistinctUntilChanged; import net.grandcentrix.thirtyinch.internal.PresenterSavior; import net.grandcentrix.thirtyinch.internal.TiPresenterProvider; -import net.grandcentrix.thirtyinch.serialize.TiPresenterSerializer; + +import android.app.Activity; +import android.app.Application; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; /** * Configure how ThirtyInch should handle the {@link TiPresenter}. @@ -145,6 +145,12 @@ public Builder setDistinctUntilChangedInterceptorEnabled(final boolean enabled) return this; } + //TODO documentation + public Builder setPresenterSerializer(TiPresenterSerializer serializer) { + mConfig.mPresenterSerializer = serializer; + return this; + } + /** * When set to true the {@link TiPresenter} will be restored when the {@link * Activity} recreates due to a configuration changes such as the orientation change. @@ -198,17 +204,6 @@ public Builder setUseStaticSaviorToRetain(final boolean enabled) { mConfig.mUseStaticSaviorToRetain = enabled; return this; } - - /** - * An {@link TiPresenterSerializer} instance should be provided, if the {@link TiPresenter} should - * be restored, after a process has died, but the UI component is being recreated. Since this is - * a rare use case the default value is {@code null} and presenters aren't restored. - * @param serializer An implementation which can serialize and deserialize any presenter. - */ - public Builder setPresenterSerializer(TiPresenterSerializer serializer) { - mConfig.mPresenterSerializer = serializer; - return this; - } } public static final TiConfiguration DEFAULT = new Builder().build(); @@ -217,18 +212,23 @@ public Builder setPresenterSerializer(TiPresenterSerializer serializer) { private boolean mDistinctUntilChangedInterceptorEnabled = true; + private TiPresenterSerializer mPresenterSerializer; + private boolean mRetainPresenter = true; private boolean mUseStaticSaviorToRetain = true; - private TiPresenterSerializer mPresenterSerializer; - /** * use {@link Builder} to construct a configuration. */ private TiConfiguration() { } + @Nullable + public TiPresenterSerializer getPresenterSerializer() { + return mPresenterSerializer; + } + public boolean isCallOnMainThreadInterceptorEnabled() { return mCallOnMainThreadInterceptorEnabled; } @@ -244,8 +244,4 @@ public boolean shouldRetainPresenter() { public boolean useStaticSaviorToRetain() { return mUseStaticSaviorToRetain; } - - public TiPresenterSerializer getPresenterSerializer() { - return mPresenterSerializer; - } } diff --git a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiPresenter.java b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiPresenter.java index f0c40006..f83bbe13 100644 --- a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiPresenter.java +++ b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiPresenter.java @@ -16,6 +16,8 @@ package net.grandcentrix.thirtyinch; +import net.grandcentrix.thirtyinch.internal.OneTimeRemovable; + import android.app.Activity; import android.content.Intent; import android.support.annotation.NonNull; @@ -23,9 +25,6 @@ import android.support.annotation.VisibleForTesting; import android.support.v4.app.Fragment; -import net.grandcentrix.thirtyinch.internal.OneTimeRemovable; -import net.grandcentrix.thirtyinch.serialize.TiPresenterSerializer; - import java.util.ArrayList; import java.util.List; import java.util.Queue; @@ -85,6 +84,7 @@ public enum State { private boolean mCalled = true; private final TiConfiguration mConfig; + private final String mId; private LinkedBlockingQueue> mPostponedViewActions = new LinkedBlockingQueue<>(); @@ -240,6 +240,11 @@ public final void destroy() { // release everything, no new states will be posted mLifecycleObservers.clear(); + + final TiPresenterSerializer serializer = getConfig().getPresenterSerializer(); + if (serializer != null) { + serializer.free(this); + } } /** @@ -283,6 +288,13 @@ public TiConfiguration getConfig() { return mConfig; } + /** + * @return A unique id of this instance. + */ + public final String getId() { + return mId; + } + /** * @return the current lifecycle state */ @@ -318,6 +330,16 @@ public boolean isViewAttached() { return mState == State.VIEW_ATTACHED; } + + //TODO documentation + public void persistState() { + TiPresenterSerializer serializer = mConfig.getPresenterSerializer(); + if (serializer != null) { + final byte[] data = onSavePersistentState(); + serializer.serialize(this, data); + } + } + @Override public String toString() { final String viewName; @@ -332,6 +354,18 @@ public String toString() { + "{view = " + viewName + "}"; } + + //TODO documentation + @Nullable + protected byte[] getPersistentState() { + final TiPresenterSerializer serializer = mConfig.getPresenterSerializer(); + if (serializer != null) { + return serializer.deserialize(this); + } + + return null; + } + /** * Gives access to the postponed actions while the view is not attached. * @@ -403,6 +437,12 @@ protected void onDetachView() { mCalled = true; } + //TODO documentation + @Nullable + protected byte[] onSavePersistentState() { + return null; + } + /** * @deprecated use {@link #onDetachView()} instead */ @@ -457,6 +497,10 @@ protected void sendToView(ViewAction action) { } } + private String generateId() { + return getClass().getSimpleName() + ":" + hashCode() + ":" + System.nanoTime(); + } + /** * moves the presenter to the new state and validates the correctness of the transition * @@ -524,25 +568,4 @@ private void sendPostponedActionsToView(V view) { mPostponedViewActions.poll().call(view); } } - - private String generateId() { - return getClass().getSimpleName() + ":" + hashCode() + ":" + System.nanoTime(); - } - - /** - * Persists this instance if the configuration provides a {@link TiPresenterSerializer}. - */ - public void persist() { - TiPresenterSerializer serializer = mConfig.getPresenterSerializer(); - if (serializer != null) { - serializer.serialize(this, mId); - } - } - - /** - * @return A unique ID of this instance. - */ - public final String getId() { - return mId; - } } diff --git a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiPresenterSerializer.java b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiPresenterSerializer.java new file mode 100644 index 00000000..c4078de5 --- /dev/null +++ b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiPresenterSerializer.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2016 grandcentrix GmbH + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.grandcentrix.thirtyinch; + +import android.support.annotation.NonNull; + +public interface TiPresenterSerializer { + + @NonNull + byte[] deserialize(@NonNull TiPresenter presenter); + + void free(@NonNull TiPresenter presenter); + + void serialize(@NonNull final TiPresenter presenter, final byte[] data); +} diff --git a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiActivityDelegate.java b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiActivityDelegate.java index ad8ffeb9..19b75eb4 100644 --- a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiActivityDelegate.java +++ b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiActivityDelegate.java @@ -15,13 +15,6 @@ package net.grandcentrix.thirtyinch.internal; -import android.app.Activity; -import android.content.res.Configuration; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; - import net.grandcentrix.thirtyinch.BindViewInterceptor; import net.grandcentrix.thirtyinch.Removable; import net.grandcentrix.thirtyinch.TiActivity; @@ -31,7 +24,14 @@ import net.grandcentrix.thirtyinch.TiView; import net.grandcentrix.thirtyinch.callonmainthread.CallOnMainThreadInterceptor; import net.grandcentrix.thirtyinch.distinctuntilchanged.DistinctUntilChangedInterceptor; -import net.grandcentrix.thirtyinch.serialize.TiPresenterSerializer; +import net.grandcentrix.thirtyinch.TiPresenterSerializer; + +import android.app.Activity; +import android.content.res.Configuration; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; import java.util.List; @@ -138,18 +138,17 @@ public void onCreate_afterSuper(final Bundle savedInstanceState) { } // try to recover with the PresenterSavior - final String recoveredPresenterId; if (savedInstanceState != null) { - recoveredPresenterId = savedInstanceState.getString(SAVED_STATE_PRESENTER_ID); + final String presenterId = savedInstanceState.getString(SAVED_STATE_PRESENTER_ID); if (mPresenter == null) { - if (recoveredPresenterId != null) { + if (presenterId != null) { // recover with Savior // this should always work. TiLog.v(mLogTag.getLoggingTag(), - "try to recover Presenter with id: " + recoveredPresenterId); + "try to recover Presenter with id: " + presenterId); //noinspection unchecked - mPresenter = (P) PresenterSavior.INSTANCE.recover(recoveredPresenterId); + mPresenter = (P) PresenterSavior.INSTANCE.recover(presenterId); TiLog.v(mLogTag.getLoggingTag(), "recovered Presenter from savior " + mPresenter); } else { @@ -165,29 +164,22 @@ public void onCreate_afterSuper(final Bundle savedInstanceState) { // save recovered presenter with new id. No other instance of this activity, // holding the presenter before, is now able to remove the reference to // this presenter from the savior - PresenterSavior.INSTANCE.free(recoveredPresenterId); + PresenterSavior.INSTANCE.free(presenterId); mPresenterId = PresenterSavior.INSTANCE.safe(mPresenter); TiPresenterSerializer serializer = mPresenter.getConfig().getPresenterSerializer(); - if (serializer != null && recoveredPresenterId != null) { - serializer.cleanup(recoveredPresenterId); + if (serializer != null && presenterId != null) { + serializer.cleanup(presenterId); } } - } else { - recoveredPresenterId = null; } if (mPresenter == null) { // could not recover, create a new presenter mPresenter = mPresenterProvider.providePresenter(); TiLog.v(mLogTag.getLoggingTag(), "created Presenter: " + mPresenter); - final TiConfiguration config = mPresenter.getConfig(); - final TiPresenterSerializer presenterSerializer = config.getPresenterSerializer(); - if (recoveredPresenterId != null && presenterSerializer != null) { - mPresenter = presenterSerializer.deserialize(mPresenter, recoveredPresenterId); - TiLog.v(mLogTag.getLoggingTag(), "deserialized Presenter: " + mPresenter); - } + final TiConfiguration config = mPresenter.getConfig(); if (config.shouldRetainPresenter() && config.useStaticSaviorToRetain()) { mPresenterId = PresenterSavior.INSTANCE.safe(mPresenter); diff --git a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiFragmentDelegate.java b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiFragmentDelegate.java index 8393ad66..8c6523ec 100644 --- a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiFragmentDelegate.java +++ b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiFragmentDelegate.java @@ -16,7 +16,7 @@ import net.grandcentrix.thirtyinch.TiView; import net.grandcentrix.thirtyinch.callonmainthread.CallOnMainThreadInterceptor; import net.grandcentrix.thirtyinch.distinctuntilchanged.DistinctUntilChangedInterceptor; -import net.grandcentrix.thirtyinch.serialize.TiPresenterSerializer; +import net.grandcentrix.thirtyinch.TiPresenterSerializer; import java.util.List; diff --git a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/serialize/TiPresenterSerializer.java b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/serialize/TiPresenterSerializer.java deleted file mode 100644 index b646e57b..00000000 --- a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/serialize/TiPresenterSerializer.java +++ /dev/null @@ -1,40 +0,0 @@ -package net.grandcentrix.thirtyinch.serialize; - -import android.support.annotation.NonNull; - -import net.grandcentrix.thirtyinch.TiPresenter; -import net.grandcentrix.thirtyinch.TiView; - -/** - * Interface to serialize and deserialize presenters. The interface should be implemented, if - * a {@link TiPresenter} should be restored, after a process has died, but the UI component is - * being recreated. - */ -public interface TiPresenterSerializer { - - /** - * Serialize the given presenter and write the data to disk. - * - * @param presenter The {@link TiPresenter} which should be serialized. - * @param presenterId The ID of the given presenter. - */ - void serialize(@NonNull TiPresenter presenter, @NonNull String presenterId); - - /** - * Deserialize the given presenter. Implementations can either return the same value - * with adjusted fields or a new instance. - * - * @param presenter The {@link TiPresenter} which should be deserialized and recreated. - * @param presenterId The ID of the given presenter. - * @return Either the same instance as the argument or a new object. - */ - @NonNull - > P deserialize(@NonNull P presenter, @NonNull String presenterId); - - /** - * Optionally clean up temporary data. - * - * @param presenterId The ID of the presenter which should be freed. - */ - void cleanup(@NonNull String presenterId); -} From 1305a9f409f5c1006006948f555add264ce8f9ed Mon Sep 17 00:00:00 2001 From: Pascal Welsch Date: Fri, 30 Dec 2016 17:31:43 +0100 Subject: [PATCH 3/3] Implement persitent state in a rudimentary way it works but the API doesn't look nice --- .../sample/HelloWorldPresenter.java | 20 +++- .../thirtyinch/sample/SampleApp.java | 7 ++ .../grandcentrix/thirtyinch/TiPresenter.java | 86 +++++++++++--- .../thirtyinch/internal/PresenterSavior.java | 8 +- .../internal/TiActivityDelegate.java | 51 +++------ .../internal/TiFragmentDelegate.java | 32 ++---- .../thirtyinch/serialize/FileUtils.java | 106 ++++++++++++++++++ .../serialize/PresenterStateSerializer.java | 82 ++++++++++++++ 8 files changed, 311 insertions(+), 81 deletions(-) create mode 100644 thirtyinch/src/main/java/net/grandcentrix/thirtyinch/serialize/FileUtils.java create mode 100644 thirtyinch/src/main/java/net/grandcentrix/thirtyinch/serialize/PresenterStateSerializer.java diff --git a/sample/src/main/java/net/grandcentrix/thirtyinch/sample/HelloWorldPresenter.java b/sample/src/main/java/net/grandcentrix/thirtyinch/sample/HelloWorldPresenter.java index 59604239..7f08d0d7 100644 --- a/sample/src/main/java/net/grandcentrix/thirtyinch/sample/HelloWorldPresenter.java +++ b/sample/src/main/java/net/grandcentrix/thirtyinch/sample/HelloWorldPresenter.java @@ -20,7 +20,9 @@ import net.grandcentrix.thirtyinch.rx.RxTiPresenterUtils; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import java.math.BigInteger; import java.util.concurrent.TimeUnit; import rx.Observable; @@ -66,7 +68,16 @@ public void call(final Void aVoid) { protected void onCreate() { super.onCreate(); - mText.onNext("Hello World!"); + final byte[] state = getPersistentState(); + if (state != null) { + mCounter = new BigInteger(state).intValue(); + } + + if (mCounter == 0) { + mText.onNext("Click the Button"); + } else { + mText.onNext("Count: " + mCounter); + } rxSubscriptionHelper.manageSubscription(Observable.interval(0, 1, TimeUnit.SECONDS) .compose(RxTiPresenterUtils.deliverLatestToView(this)) @@ -105,6 +116,13 @@ public void call(final Integer integer) { .subscribe()); } + @Nullable + @Override + protected byte[] onSavePersistentState() { + final byte[] bytes = BigInteger.valueOf(mCounter).toByteArray(); + return bytes; + } + /** * fake a heavy calculation */ diff --git a/sample/src/main/java/net/grandcentrix/thirtyinch/sample/SampleApp.java b/sample/src/main/java/net/grandcentrix/thirtyinch/sample/SampleApp.java index 7d0d3c7f..c64fcdb9 100644 --- a/sample/src/main/java/net/grandcentrix/thirtyinch/sample/SampleApp.java +++ b/sample/src/main/java/net/grandcentrix/thirtyinch/sample/SampleApp.java @@ -16,7 +16,10 @@ package net.grandcentrix.thirtyinch.sample; +import net.grandcentrix.thirtyinch.TiConfiguration; import net.grandcentrix.thirtyinch.TiLog; +import net.grandcentrix.thirtyinch.TiPresenter; +import net.grandcentrix.thirtyinch.serialize.PresenterStateSerializer; import android.app.Application; @@ -28,5 +31,9 @@ public void onCreate() { // log ThirtyInch output with logcat TiLog.setLogger(TiLog.LOGCAT); + + TiPresenter.setDefaultConfig(new TiConfiguration.Builder() + .setPresenterSerializer(new PresenterStateSerializer(this)) + .build()); } } diff --git a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiPresenter.java b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiPresenter.java index f83bbe13..e0602ce6 100644 --- a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiPresenter.java +++ b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiPresenter.java @@ -28,6 +28,11 @@ import java.util.ArrayList; import java.util.List; import java.util.Queue; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; /** @@ -85,7 +90,12 @@ public enum State { private final TiConfiguration mConfig; - private final String mId; + private String mId; + + private final ExecutorService mPersistentStateExecutorService = + Executors.newSingleThreadExecutor(); + + private Future mPersistentStateFuture; private LinkedBlockingQueue> mPostponedViewActions = new LinkedBlockingQueue<>(); @@ -97,18 +107,17 @@ public static void setDefaultConfig(final TiConfiguration config) { sDefaultConfig = config; } - public TiPresenter() { this(sDefaultConfig); } + /** * Constructs a presenter with a different configuration then the default one. Change the * default configuration with {@link #setDefaultConfig(TiConfiguration)} */ public TiPresenter(final TiConfiguration config) { mConfig = config; - mId = generateId(); } /** @@ -138,7 +147,6 @@ public void onRemove() { }; } - /** * bind a new view to this presenter. * @@ -280,6 +288,11 @@ public final void detachView() { mView = null; } + public void generatNewId() { + final String id = getClass().getSimpleName() + ":" + hashCode() + ":" + System.nanoTime(); + setId(id); + } + /** * @return the presenter configuration */ @@ -330,13 +343,48 @@ public boolean isViewAttached() { return mState == State.VIEW_ATTACHED; } - //TODO documentation public void persistState() { - TiPresenterSerializer serializer = mConfig.getPresenterSerializer(); - if (serializer != null) { - final byte[] data = onSavePersistentState(); - serializer.serialize(this, data); + mPersistentStateExecutorService.submit(new Runnable() { + @Override + public void run() { + TiPresenterSerializer serializer = mConfig.getPresenterSerializer(); + if (serializer != null) { + final byte[] data = onSavePersistentState(); + serializer.serialize(TiPresenter.this, data); + } + } + }); + } + + @NonNull + public Future prefetchPersistentState() { + mPersistentStateFuture = mPersistentStateExecutorService.submit(new Callable() { + @Override + public byte[] call() throws Exception { + final TiPresenterSerializer serializer = mConfig.getPresenterSerializer(); + if (serializer != null) { + return serializer.deserialize(TiPresenter.this); + } + return null; + } + }); + + return mPersistentStateFuture; + } + + /** + * the id can only be set once + */ + public void setId(@NonNull final String id) { + //noinspection ConstantConditions + if (id == null) { + throw new IllegalArgumentException("the id cannot be null"); + } + if (mId == null) { + mId = id; + } else { + throw new IllegalArgumentException("the id can only be set once"); } } @@ -354,15 +402,21 @@ public String toString() { + "{view = " + viewName + "}"; } - //TODO documentation @Nullable protected byte[] getPersistentState() { - final TiPresenterSerializer serializer = mConfig.getPresenterSerializer(); - if (serializer != null) { - return serializer.deserialize(this); + Future future = mPersistentStateFuture; + if (future == null) { + future = mPersistentStateFuture = prefetchPersistentState(); + } + try { + // wait for result even when not prefetched + return future.get(); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); } - return null; } @@ -497,10 +551,6 @@ protected void sendToView(ViewAction action) { } } - private String generateId() { - return getClass().getSimpleName() + ":" + hashCode() + ":" + System.nanoTime(); - } - /** * moves the presenter to the new state and validates the correctness of the transition * diff --git a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/PresenterSavior.java b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/PresenterSavior.java index 6d7b4ed1..2f6e17df 100644 --- a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/PresenterSavior.java +++ b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/PresenterSavior.java @@ -15,14 +15,14 @@ package net.grandcentrix.thirtyinch.internal; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; - import net.grandcentrix.thirtyinch.TiActivity; import net.grandcentrix.thirtyinch.TiLog; import net.grandcentrix.thirtyinch.TiPresenter; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; + import java.util.HashMap; /** diff --git a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiActivityDelegate.java b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiActivityDelegate.java index 19b75eb4..26716719 100644 --- a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiActivityDelegate.java +++ b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiActivityDelegate.java @@ -24,7 +24,6 @@ import net.grandcentrix.thirtyinch.TiView; import net.grandcentrix.thirtyinch.callonmainthread.CallOnMainThreadInterceptor; import net.grandcentrix.thirtyinch.distinctuntilchanged.DistinctUntilChangedInterceptor; -import net.grandcentrix.thirtyinch.TiPresenterSerializer; import android.app.Activity; import android.content.res.Configuration; @@ -62,12 +61,6 @@ public class TiActivityDelegate

, V extends TiView> */ private P mPresenter; - /** - * The id of the presenter this view got attached to. Will be stored in the savedInstanceState - * to find the same presenter after the Activity got recreated. - */ - private String mPresenterId; - private final TiPresenterProvider

mPresenterProvider; private final DelegatedTiActivity

mTiActivity; @@ -137,18 +130,19 @@ public void onCreate_afterSuper(final Bundle savedInstanceState) { "recovered Presenter from lastCustomNonConfigurationInstance " + mPresenter); } + String recoveredPresenterId = null; // try to recover with the PresenterSavior if (savedInstanceState != null) { - final String presenterId = savedInstanceState.getString(SAVED_STATE_PRESENTER_ID); + recoveredPresenterId = savedInstanceState.getString(SAVED_STATE_PRESENTER_ID); if (mPresenter == null) { - if (presenterId != null) { + if (recoveredPresenterId != null) { // recover with Savior // this should always work. TiLog.v(mLogTag.getLoggingTag(), - "try to recover Presenter with id: " + presenterId); + "try to recover Presenter with id: " + recoveredPresenterId); //noinspection unchecked - mPresenter = (P) PresenterSavior.INSTANCE.recover(presenterId); + mPresenter = (P) PresenterSavior.INSTANCE.recover(recoveredPresenterId); TiLog.v(mLogTag.getLoggingTag(), "recovered Presenter from savior " + mPresenter); } else { @@ -160,30 +154,26 @@ public void onCreate_afterSuper(final Bundle savedInstanceState) { TiLog.i(mLogTag.getLoggingTag(), "could not recover the Presenter " + "although it's not the first start of the Activity. This is normal when " + "configured as .setRetainPresenterEnabled(false)."); - } else { - // save recovered presenter with new id. No other instance of this activity, - // holding the presenter before, is now able to remove the reference to - // this presenter from the savior - PresenterSavior.INSTANCE.free(presenterId); - mPresenterId = PresenterSavior.INSTANCE.safe(mPresenter); - - TiPresenterSerializer serializer = mPresenter.getConfig().getPresenterSerializer(); - if (serializer != null && presenterId != null) { - serializer.cleanup(presenterId); - } } } if (mPresenter == null) { // could not recover, create a new presenter mPresenter = mPresenterProvider.providePresenter(); - TiLog.v(mLogTag.getLoggingTag(), "created Presenter: " + mPresenter); - final TiConfiguration config = mPresenter.getConfig(); + if (recoveredPresenterId == null) { + mPresenter.generatNewId(); + } else { + mPresenter.setId(recoveredPresenterId); + mPresenter.prefetchPersistentState(); + } + TiLog.v(mLogTag.getLoggingTag(), "created Presenter: " + mPresenter); + final TiConfiguration config = mPresenter.getConfig(); if (config.shouldRetainPresenter() && config.useStaticSaviorToRetain()) { - mPresenterId = PresenterSavior.INSTANCE.safe(mPresenter); + PresenterSavior.INSTANCE.safe(mPresenter); } + mPresenter.create(); } @@ -234,12 +224,7 @@ public void onDestroy_afterSuper() { if (destroyPresenter) { mPresenter.destroy(); - PresenterSavior.INSTANCE.free(mPresenterId); - TiPresenterSerializer serializer = mPresenter.getConfig().getPresenterSerializer(); - if (serializer != null) { - serializer.cleanup(mPresenterId); - } - + PresenterSavior.INSTANCE.free(mPresenter.getId()); } else { TiLog.v(mLogTag.getLoggingTag(), "not destroying " + mPresenter + " which will be reused by the next Activity instance, recreating..."); @@ -247,8 +232,8 @@ public void onDestroy_afterSuper() { } public void onSaveInstanceState_afterSuper(final Bundle outState) { - outState.putString(SAVED_STATE_PRESENTER_ID, mPresenterId); - mPresenter.persist(); + outState.putString(SAVED_STATE_PRESENTER_ID, mPresenter.getId()); + mPresenter.persistState(); } public void onStart_afterSuper() { diff --git a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiFragmentDelegate.java b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiFragmentDelegate.java index 8c6523ec..f2810894 100644 --- a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiFragmentDelegate.java +++ b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiFragmentDelegate.java @@ -1,11 +1,5 @@ package net.grandcentrix.thirtyinch.internal; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.view.LayoutInflater; -import android.view.ViewGroup; - import net.grandcentrix.thirtyinch.BindViewInterceptor; import net.grandcentrix.thirtyinch.Removable; import net.grandcentrix.thirtyinch.TiConfiguration; @@ -16,7 +10,12 @@ import net.grandcentrix.thirtyinch.TiView; import net.grandcentrix.thirtyinch.callonmainthread.CallOnMainThreadInterceptor; import net.grandcentrix.thirtyinch.distinctuntilchanged.DistinctUntilChangedInterceptor; -import net.grandcentrix.thirtyinch.TiPresenterSerializer; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.ViewGroup; import java.util.List; @@ -120,28 +119,15 @@ public void onCreate_afterSuper(final Bundle savedInstanceState) { // this presenter from the savior PresenterSavior.INSTANCE.free(recoveredPresenterId); mPresenterId = PresenterSavior.INSTANCE.safe(mPresenter); - - TiPresenterSerializer serializer = mPresenter.getConfig().getPresenterSerializer(); - if (serializer != null) { - serializer.cleanup(recoveredPresenterId); - } } TiLog.v(mLogTag.getLoggingTag(), "recovered Presenter " + mPresenter); } - } else { - recoveredPresenterId = null; } if (mPresenter == null) { mPresenter = mPresenterProvider.providePresenter(); TiLog.v(mLogTag.getLoggingTag(), "created Presenter: " + mPresenter); final TiConfiguration config = mPresenter.getConfig(); - final TiPresenterSerializer presenterSerializer = config.getPresenterSerializer(); - - if (recoveredPresenterId != null && presenterSerializer != null) { - mPresenter = presenterSerializer.deserialize(mPresenter, recoveredPresenterId); - TiLog.v(mLogTag.getLoggingTag(), "deserialized Presenter: " + mPresenter); - } if (config.shouldRetainPresenter() && config.useStaticSaviorToRetain()) { mPresenterId = PresenterSavior.INSTANCE.safe(mPresenter); @@ -207,10 +193,6 @@ public void onDestroy_afterSuper() { if (destroyPresenter) { mPresenter.destroy(); PresenterSavior.INSTANCE.free(mPresenterId); - TiPresenterSerializer serializer = mPresenter.getConfig().getPresenterSerializer(); - if (serializer != null) { - serializer.cleanup(mPresenterId); - } } else { TiLog.v(mLogTag.getLoggingTag(), "not destroying " + mPresenter @@ -220,7 +202,7 @@ public void onDestroy_afterSuper() { public void onSaveInstanceState_afterSuper(final Bundle outState) { outState.putString(SAVED_STATE_PRESENTER_ID, mPresenterId); - mPresenter.persist(); + mPresenter.persistState(); } public void onStart_afterSuper() { diff --git a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/serialize/FileUtils.java b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/serialize/FileUtils.java new file mode 100644 index 00000000..d78a2d2a --- /dev/null +++ b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/serialize/FileUtils.java @@ -0,0 +1,106 @@ +package net.grandcentrix.thirtyinch.serialize; + +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; + +/** + * Created by rwondratschek on 12/14/16. + */ +@SuppressWarnings({"unused", "WeakerAccess"}) +/*package*/ final class FileUtils { + + public static void close(Closeable closeable) { + if (closeable != null) { + if (closeable instanceof OutputStream) { + try { + ((OutputStream) closeable).flush(); + } catch (IOException ignored) { + } + } + + if (closeable instanceof FileOutputStream) { + try { + ((FileOutputStream) closeable).getFD().sync(); + } catch (IOException ignored) { + } + } + + try { + closeable.close(); + } catch (IOException ignored) { + } + } + } + + public static void delete(File file) throws IOException { + if (!file.exists()) { + return; + } + if (file.isDirectory()) { + File[] files = file.listFiles(); + for (File file1 : files) { + delete(file1); + } + } + if (!file.delete()) { + throw new IOException("could not delete file " + file); + } + } + + public static byte[] readFile(File file) throws IOException { + FileInputStream fis = null; + try { + fis = new FileInputStream(file); + byte[] buffer = new byte[(int) file.length()]; + + int read; + int offset = 0; + + while ((read = fis.read(buffer, offset, buffer.length - offset)) >= 0 + && offset < buffer.length) { + offset += read; + } + + if (offset != buffer.length) { + return Arrays.copyOf(buffer, offset); + } else { + return buffer; + } + + } finally { + close(fis); + } + } + + public static void writeFile(File file, byte[] data) throws IOException { + if (file == null || data == null) { + throw new IllegalArgumentException(); + } + + if (!file.getParentFile().exists() && !file.getParentFile().mkdirs()) { + throw new IOException("Could not load parent directory for " + file.getAbsolutePath()); + } + + if (!file.exists() && !file.createNewFile()) { + throw new IOException("Could not load file for " + file.getAbsolutePath()); + } + + FileOutputStream fos = null; + try { + fos = new FileOutputStream(file); + fos.write(data); + + } finally { + close(fos); + } + } + + private FileUtils() { + new AssertionError("no instances"); + } +} diff --git a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/serialize/PresenterStateSerializer.java b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/serialize/PresenterStateSerializer.java new file mode 100644 index 00000000..d2e6540e --- /dev/null +++ b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/serialize/PresenterStateSerializer.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2016 grandcentrix GmbH + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.grandcentrix.thirtyinch.serialize; + + +import net.grandcentrix.thirtyinch.TiLog; +import net.grandcentrix.thirtyinch.TiPresenter; +import net.grandcentrix.thirtyinch.TiPresenterSerializer; + +import android.content.Context; +import android.support.annotation.NonNull; + +import java.io.File; +import java.io.IOException; + +public class PresenterStateSerializer implements TiPresenterSerializer { + + private static final String TAG = PresenterStateSerializer.class.getSimpleName(); + + private final File mCacheDir; + + public PresenterStateSerializer(@NonNull final Context context) { + mCacheDir = new File(context.getCacheDir(), "TiPresenterStates"); + } + + @NonNull + @Override + public byte[] deserialize(@NonNull final TiPresenter presenter) { + final File file = getStateFile(presenter); + TiLog.v(TAG, "deserialize " + file.getName()); + try { + return FileUtils.readFile(file); + } catch (IOException e) { + TiLog.v(TAG, "could not read from file " + file.getName()); + e.printStackTrace(); + + } + return null; + } + + @Override + public void free(@NonNull final TiPresenter presenter) { + final File file = getStateFile(presenter); + TiLog.v(TAG, "free " + file.getName()); + try { + FileUtils.delete(file); + } catch (IOException e) { + TiLog.v(TAG, "could not delete file " + file.getName()); + e.printStackTrace(); + } + } + + @Override + public void serialize(@NonNull final TiPresenter presenter, final byte[] data) { + final File file = getStateFile(presenter); + TiLog.v(TAG, "serialize " + file.getName()); + try { + FileUtils.writeFile(file, data); + } catch (IOException e) { + TiLog.v(TAG, "could not write to file " + file.getName()); + e.printStackTrace(); + } + } + + @NonNull + private File getStateFile(final @NonNull TiPresenter presenter) { + return new File(mCacheDir, presenter.getId()); + } +}