mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-17 15:25:52 +00:00
Bug 1494748 - Ensure GeckoView saved state lives at least as long as the app process. r=snorp
The arguably most interesting bit of state of BrowserApp/GeckoApp, namely the currently open tabs, are living partly in Gecko and partly in the Tabs manager singleton, the lifetimes of both of which are tied to the lifetime of the app process. If the whole process has been killed, things are simple: Neither the Tabs manager nor Gecko know anything about any tabs and we simply restore them through the session store if enabled. If GeckoApp is however being restored into an app process in which it had already executed earlier on, meaning that we have some open tabs, it relies on the savedInstanceState in order to correctly reconnect its GeckoView instance with the correct previous GeckoSession. We can however end up in a state where we don't have a savedInstanceState (e.g. because the user swiped away the BrowserApp activity in the task switcher), but the app process keeps running throughout (if another activity of ours is still present in the task switcher, e.g. a custom tab, or else if a service is active, then standard Android keeps the process running even if the user swipes away an activity). In that case, if GeckoApp is subsequently recreated, the Android UI sees all the Android-side tabs in the Tabs manager, and Gecko in fact still has the Window open that is containing all those tabs, but without the savedInstanceState GeckoApp doesn't know anything about that Window and proceeds to open a fresh session instead. This means that all previous tabs will appear white and unresponsive, while freshly opened tabs will load, but they won't be correctly saved in the session store, their context menu isn't working, etc., because we're not really expecting to handle multiple Gecko-side Windows. To fix this, we disable automatic state-saving for GeckoApp's GeckoView instance and instead do it manually, so we can keep another reference to the saved state in GeckoApplication, and therefore are able to retrieve it from there for as long as the app process keeps running. Differential Revision: https://phabricator.services.mozilla.com/D16393 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
a1c718855a
commit
f74e1879b8
@ -63,6 +63,7 @@ import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Parcelable;
|
||||
import android.os.StrictMode;
|
||||
import android.provider.ContactsContract;
|
||||
import android.support.annotation.NonNull;
|
||||
@ -71,6 +72,7 @@ import android.support.design.widget.Snackbar;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.util.SparseIntArray;
|
||||
import android.view.KeyEvent;
|
||||
@ -136,6 +138,7 @@ public abstract class GeckoApp extends GeckoActivity
|
||||
|
||||
public static final String INTENT_REGISTER_STUMBLER_LISTENER = "org.mozilla.gecko.STUMBLER_REGISTER_LOCAL_LISTENER";
|
||||
|
||||
private static final String GECKOVIEW_STATE_BUNDLE = "geckoViewState";
|
||||
public static final String EXTRA_STATE_BUNDLE = "stateBundle";
|
||||
|
||||
public static final String PREFS_ALLOW_STATE_BUNDLE = "allowStateBundle";
|
||||
@ -658,6 +661,16 @@ public abstract class GeckoApp extends GeckoActivity
|
||||
}
|
||||
|
||||
outState.putBoolean(SAVED_STATE_IN_BACKGROUND, isApplicationInBackground());
|
||||
|
||||
// There are situations where the saved instance state will be cleared (e.g. user swipes
|
||||
// away activity in the task switcher), but Gecko will actually remain alive (because
|
||||
// another activity or service of ours is still running in this process). The saved state is
|
||||
// the only way we can reconnect to our previous GeckoView session and all the user's open
|
||||
// tabs, so we need to keep a copy of the state ourselves.
|
||||
SparseArray<Parcelable> geckoViewState = new SparseArray<>();
|
||||
mLayerView.saveHierarchyState(geckoViewState);
|
||||
outState.putSparseParcelableArray(GECKOVIEW_STATE_BUNDLE, geckoViewState);
|
||||
getGeckoApplication().setSavedState(geckoViewState);
|
||||
}
|
||||
|
||||
public void addTab(int flags) { }
|
||||
@ -696,7 +709,7 @@ public abstract class GeckoApp extends GeckoActivity
|
||||
rec.recordGeckoStartupTime(mGeckoReadyStartupTimer.getElapsed());
|
||||
}
|
||||
|
||||
((GeckoApplication) getApplicationContext()).onDelayedStartup();
|
||||
getGeckoApplication().onDelayedStartup();
|
||||
|
||||
// Reset the crash loop counter if we remain alive for at least half a minute.
|
||||
ThreadUtils.postDelayedToBackgroundThread(new Runnable() {
|
||||
@ -976,6 +989,11 @@ public abstract class GeckoApp extends GeckoActivity
|
||||
**/
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
// Within onCreate(), we might inject a different savedInstanceState for testing, but this
|
||||
// won't influence what the OS will do with regards to calling onSaveInstanceState().
|
||||
// Therefore, record whether we were passed some data or not.
|
||||
final boolean receivedSavedInstanceState = (savedInstanceState != null);
|
||||
|
||||
// Enable Android Strict Mode for developers' local builds (the "default" channel).
|
||||
if ("default".equals(AppConstants.MOZ_UPDATE_CHANNEL)) {
|
||||
enableStrictMode();
|
||||
@ -1106,6 +1124,9 @@ public abstract class GeckoApp extends GeckoActivity
|
||||
mGeckoLayout = (RelativeLayout) findViewById(R.id.gecko_layout);
|
||||
mMainLayout = (RelativeLayout) findViewById(R.id.main_layout);
|
||||
mLayerView = (GeckoView) findViewById(R.id.layer_view);
|
||||
// Disable automatic state staving - we require some special handling that we need to do
|
||||
// ourselves.
|
||||
mLayerView.setSaveFromParentEnabled(false);
|
||||
|
||||
final GeckoSession session = new GeckoSession(
|
||||
new GeckoSessionSettings.Builder()
|
||||
@ -1119,6 +1140,9 @@ public abstract class GeckoApp extends GeckoActivity
|
||||
}
|
||||
mLayerView.setSession(session, GeckoApplication.getRuntime());
|
||||
mLayerView.setOverScrollMode(View.OVER_SCROLL_NEVER);
|
||||
if (mIsRestoringActivity && !receivedSavedInstanceState) {
|
||||
restoreGeckoViewState(getGeckoApplication().getSavedState());
|
||||
}
|
||||
|
||||
getAppEventDispatcher().registerGeckoThreadListener(this,
|
||||
"Locale:Set",
|
||||
@ -1313,6 +1337,26 @@ public abstract class GeckoApp extends GeckoActivity
|
||||
mWasFirstTabShownAfterActivityUnhidden = false; // onStart indicates we were hidden.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
|
||||
final SparseArray<Parcelable> stateToRestore =
|
||||
savedInstanceState.getSparseParcelableArray(GECKOVIEW_STATE_BUNDLE);
|
||||
restoreGeckoViewState(stateToRestore);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the given state into our GeckoView and clears any state we might have kept locally
|
||||
* within our process, as it has now become obsolete.
|
||||
*/
|
||||
private void restoreGeckoViewState(final SparseArray<Parcelable> state) {
|
||||
if (state != null) {
|
||||
mLayerView.restoreHierarchyState(state);
|
||||
}
|
||||
getGeckoApplication().setSavedState(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
@ -2534,6 +2578,10 @@ public abstract class GeckoApp extends GeckoActivity
|
||||
return mLayerView;
|
||||
}
|
||||
|
||||
protected GeckoApplication getGeckoApplication() {
|
||||
return (GeckoApplication) getApplicationContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setRequestedOrientationForCurrentActivity(int requestedActivityInfoOrientation) {
|
||||
// We want to support the Screen Orientation API, and it always makes sense to lock the
|
||||
|
@ -16,6 +16,7 @@ import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.Parcelable;
|
||||
import android.os.Process;
|
||||
import android.os.SystemClock;
|
||||
import android.provider.MediaStore;
|
||||
@ -26,6 +27,7 @@ import android.support.multidex.MultiDex;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.squareup.leakcanary.LeakCanary;
|
||||
import com.squareup.leakcanary.RefWatcher;
|
||||
@ -81,6 +83,10 @@ public class GeckoApplication extends Application
|
||||
|
||||
private LightweightTheme mLightweightTheme;
|
||||
|
||||
// GeckoApp *must* keep its GeckoView state around for as long as our app process (and
|
||||
// therefore Gecko) keeps running, even if Android clears the normal savedInstanceState.
|
||||
private SparseArray<Parcelable> mSavedState;
|
||||
|
||||
private RefWatcher mRefWatcher;
|
||||
|
||||
private final EventListener mListener = new EventListener();
|
||||
@ -641,6 +647,14 @@ public class GeckoApplication extends Application
|
||||
mLightweightTheme = new LightweightTheme(this);
|
||||
}
|
||||
|
||||
/* package */ void setSavedState(SparseArray<Parcelable> savedState) {
|
||||
mSavedState = savedState;
|
||||
}
|
||||
|
||||
/* package */ SparseArray<Parcelable> getSavedState() {
|
||||
return mSavedState;
|
||||
}
|
||||
|
||||
public static void createShortcut() {
|
||||
final Tab selectedTab = Tabs.getInstance().getSelectedTab();
|
||||
if (selectedTab != null) {
|
||||
|
Loading…
Reference in New Issue
Block a user