AndreiBogdan
AndreiBogdan

Reputation: 11164

App restarts from wrong activity

This is quite a conundrum:

I open my app. It launches an activity which acts as a splashscreen (ASplashscreen) in which I load some JSON data from local storage (raw folder) and store it in memory in a singleton object (static). After this process is done it automatically moves along to the main activity (AMain)

I exit the app by pressing the home buttonand run other applications, games, etc. When I reopen my app, the app crashes inside the onCreate method of the AMain because it tries to use some of the data inside the singleton object but the data is null. So it throws a NullPointerException when it does so. It appears that it restarts the AMain instead of ASplashscreen so the singleton doesn't have a chance to reinitialize.

This happens randomly across multiple such tries...

I have two presumptions ...

  1. My first presumption, and from what I know about the Android OS, is that while I was running those other applications (especially the games) one of them required a lot of memory so the OS released my app from memory to make room, so the singleton data was garbage collected.

  2. I also presume that while the gc removed my singleton from memory, the OS still kept some data relating to the "state" of the current running activity, so it knew at least that it had the AMain activity opened before i closed the app. This would explain why it reopened the AMain activity instead of the ASplashscreen.

Am I right? Or is there another explanation why I get this exception? Any suggestions/clarifications are welcomed.

Also, what would be the best approach to handle this? My approach is to check the existence of he singleton data whenever I try to use it and if it's null then just basically restart the app. This makes it go through the ASplashscreen so the JSON gets initialized and everything is ok.

EDIT As requested, here's my AndroidManifest

 <uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="com.android.vending.BILLING"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

<application
    android:name=".global.App"
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:largeHeap="true"
    android:theme="@style/AppTheme">

    <!--SPLASH SCREEN-->
    <activity
        android:name=".activities.ASplashscreen"
        android:label="@string/app_name"
        android:launchMode="singleTask"
        android:screenOrientation="portrait"
        android:theme="@style/AppTheme">
        <intent-filter>
            <action android:name="android.intent.action.MAIN"/>

            <category android:name="android.intent.category.LAUNCHER"/>
        </intent-filter>
    </activity>

    <!--MAIN-->
    <activity
        android:name=".activities.AMain"
        android:label="@string/app_name"
        android:launchMode="singleTask"
        android:screenOrientation="portrait"
        android:theme="@style/AppTheme"/>

    <!--MENU-->
    <activity
        android:name=".activities.AMenu"
        android:label="@string/app_name"
        android:launchMode="singleTask"
        android:screenOrientation="portrait"
        android:theme="@style/AppTheme"/>

    <!--HELP-->
    <activity
        android:name=".activities.AHelp"
        android:label="@string/app_name"
        android:launchMode="singleTask"
        android:screenOrientation="portrait"
        android:theme="@style/AppTheme"/>

    <!--ADMOB-->
    <activity
        android:name="com.google.android.gms.ads.AdActivity"
        android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
        android:theme="@android:style/Theme.Translucent"/>

    <!--FACEBOOK LOGIN ACTIVITY (SDK)-->
    <activity
        android:name="com.facebook.LoginActivity"
        android:label="@string/app_name"
        android:screenOrientation="portrait"
        android:theme="@style/AppTheme"/>

    <!--This meta-data tag is required to use Google Play Services.-->
    <meta-data
        android:name="com.google.android.gms.version"
        android:value="@integer/google_play_services_version"/>

    <!--FACEBOOK STUFF-->
    <meta-data
        android:name="com.facebook.sdk.ApplicationId"
        android:value="@string/facebook_app_id"/>

    <!--GOOGLE PLUS-->
    <meta-data
        android:name="com.google.android.gms.version"
        android:value="@integer/google_play_services_version"/>

    <!--CRASHLYTICS-->
    <meta-data
        android:name="com.crashlytics.ApiKey"
        android:value="9249....."/>

</application>

If you guys really want it, here's the content of the ASplashscreen

/**
 * @author MAB
 */
public class ASplashscreen extends ABase implements IIosLikeDialogListener {

    private final float SHEEP_WIDTH_FRAC = 0.8f;

    private final int SPLASHSCREEN_DELAY_MS = 500;

    //View references
    private View sheep_image;

    /** The timestamp recorded when this screen came into view. We'll used this to determine how much we'll need to keep the splash screen awake */
    private long mStartTimestamp;

    private IosLikeDialog mDialog;

    private IabHelper mIabHelper;

    // Listener that's called when we finish querying the items and subscriptions we own
    IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
        public void onQueryInventoryFinished(IabResult result, Inventory inventory) {

            // Have we been disposed of in the meantime? If so, quit.
            if (mIabHelper == null) {
                System.out.println("=== IAB INVENTORY PROBLEM :: WE'VE BEEN DISPOSED");
                displayAppStoreUnavailableDialog();
                return;
            }

            // Is it a failure?
            if (result.isFailure()) {
                displayAppStoreUnavailableDialog();
                System.out.println("=== IAB INVENTORY PROBLEM :: FAILED TO QUERY INVENTORY :: " + result);
                return;
            }

            //Sync our static stuff with the app store
            HSounds.instance().populate(ASplashscreen.this, inventory);
            HLights.instance().populate(ASplashscreen.this, inventory);

            //Store the stuff locally just to be sure
            HStorage.persistObjectToFile(ASplashscreen.this, HVersions.SOUNDS);
            HStorage.persistObjectToFile(ASplashscreen.this, HVersions.LIGHTS);

            System.out.println("=== SUCCESSFULLY SYNCED WITH STORE !");

            jumpToMainActivity();

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.a_splashscreen);

        init();

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        if (mIabHelper != null) {
            mIabHelper.dispose();
        }
        mIabHelper = null;
    }

    @Override
    public void onIosLikeDialogBtnsClick(int btnStringResID) {
        if (btnStringResID == IosLikeDialog.BTN_OK) {
            jumpToMainActivity();
        }
    }

    private void init() {
        //Get view references
        sheep_image = findViewById(R.id.splashscreen_sheep);

        mStartTimestamp = System.currentTimeMillis();

        VersionTracking.setVersions(this);

        //Set the width of the sheep
        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) sheep_image.getLayoutParams();
        params.width = (int) ((float) UScreen.getScreenWidthInPortrait(this) * SHEEP_WIDTH_FRAC);
        sheep_image.setLayoutParams(params);

        mDialog = new IosLikeDialog()
                .with(findViewById(R.id.ios_like_dialog_main_container))
                .listen(this);

        new Thread(new Runnable() {
            @Override
            public void run() {

                parseJsons();

                //Get the filler bar values from shared prefs
                HBrightness.instance().retrieveFromPersist(ASplashscreen.this);
                HSensorAndTimer.instance().retrieveFromPersist(ASplashscreen.this);

                WsBuilder.build(ASplashscreen.this).getGift(new ResponseListener<EGift>() {
                    @Override
                    public void onSuccess(EGift gifts) {
                        long now = System.currentTimeMillis();
                        SimpleDateFormat fmt = new SimpleDateFormat(HJsonDataBase.GIFT_DATE_FORMAT);
                        Date start;
                        Date end;

                        //Handle the gifts
                        if (gifts != null && gifts.data != null && gifts.responseOK()) {
                            //Go through the SOUNDS and check if we need to set them as gifts, if not reset them
                            for (ESound sound : HSounds.instance().getValues().getSounds()) {
                                String sku = sound.getSku(ASplashscreen.this);
                                sound.giftStart = null;
                                sound.giftEnd = null;
                                for (String giftSku : gifts.data.inapps) {
                                    if (giftSku.equals(sku)) {
                                        sound.giftStart = gifts.data.start_date;
                                        sound.giftEnd = gifts.data.end_date;
                                        break;
                                    }
                                }
                                //Check if redeemed gift expired and if so, reset the dates
                                checkSoundGiftExpired(sound, fmt, now);
                            }
                            //Go through the LIGHTS and check if we need to set them as gifts, if not reset them
                            for (ELight light : HLights.instance().getValues().getLights()) {
                                String sku = light.getSku(ASplashscreen.this);
                                light.giftStart = null;
                                light.giftEnd = null;
                                for (String giftSku : gifts.data.inapps) {
                                    if (giftSku.equals(sku)) {
                                        light.giftStart = gifts.data.start_date;
                                        light.giftEnd = gifts.data.end_date;
                                        break;
                                    }
                                }
                                //Check if redeemed gift expired and if so, reset the dates
                                checkLightGiftExpired(light, fmt, now);
                            }
                            //Persist the data in the local storage
                            HStorage.persistObjectToFile(ASplashscreen.this, HVersions.SOUNDS);
                            HStorage.persistObjectToFile(ASplashscreen.this, HVersions.LIGHTS);
                        }

                        //Run the IAB helper now
                        runIabHelper();
                    }

                    @Override
                    public void onErrorResponse(VolleyError error) {
                        //This might mean we're in offline mode, so check if the gifts expired
                        checkAllLightsGiftExpired();
                        checkAllSoundsGiftExpired();

                        //Run the IAB helper now
                        runIabHelper();
                    }
                }, getPackageName());
            }
        });
    }

    /**
     * This is run on a non-UI thread !!
     */
    private void parseJsons() {

        /**
         * Versions
         */
        parseVersions();


        /**
         * BACKGROUND
         */
        parseBackgrounds();
        try {
            validateBackgrounds();
        } catch (NullPointerException e) {
            removeBackgroundsFile();
            parseBackgrounds();
        }

        /**
         * LIGHTS
         */
        parseLights();
        try {
            validateLights();
        } catch (NullPointerException e) {
            removeLightsFile();
            parseLights();
        }

        /**
         * SOUNDS
         */
        parseSounds();
        try {
            validateSounds();
        } catch (NullPointerException e) {
            removeSoundsFile();
            parseSounds();
        }

    }

    private void parseVersions() {
        InputStream in = getResources().openRawResource(R.raw.versions);
        EVersions versions = null;
        try {
            versions = UGson.jsonToObject(in, EVersions.class);
        } catch (Exception e) {
            System.out.println("==== PARSE ERROR :: VERSIONS :: " + e.getMessage());
            e.printStackTrace();
            return;
        }
        HVersions.instance().setValues(this, versions);
    }

    private void parseBackgrounds() {
        //Get the version of he JSONS at which we've last updated them from the "raw" folder
        int lastVersionBckgnds = UPersistent.getInt(ASplashscreen.this, HVersions.SHARED_PREF_LAST_JSONS_VERSION_BCKGNDS, 0);

        InputStream in;
        //If there are no files in local storage OR there's a new version of the JSON files that we need to retrieve
        if (!HStorage.fileExists(ASplashscreen.this, HStorage.FILE_JSON_BACKGROUNDS) ||
                HVersions.instance().shouldUpdateFromResources(HVersions.BACKGROUNDS, lastVersionBckgnds)) { //Update from raw folder
            in = getResources().openRawResource(R.raw.backgrounds);
        } else { //Update from local storage
            in = HStorage.getInputStreamForFile(ASplashscreen.this, HStorage.FILE_JSON_BACKGROUNDS);
        }
        EBackgrounds bckgnds = null;
        try {
            bckgnds = UGson.jsonToObject(in, EBackgrounds.class);
        } catch (Exception e) {
            System.out.println("==== PARSE ERROR :: BACKGROUNDS :: " + e.getMessage());
            e.printStackTrace();
        }
        HBackgrounds.instance().setValues(this, bckgnds);
    }

    private void parseLights() {
        //Get the version of he JSONS at which we've last updated them from the "raw" folder
        int lastVersionLights = UPersistent.getInt(ASplashscreen.this, HVersions.SHARED_PREF_LAST_JSONS_VERSION_LIGHTS, 0);

        InputStream in;
        //If there are no files in local storage OR there's a new version of the JSON files that we need to retrieve
        if (!HStorage.fileExists(ASplashscreen.this, HStorage.FILE_JSON_LIGHTS) ||
                HVersions.instance().shouldUpdateFromResources(HVersions.LIGHTS, lastVersionLights)) { //Update from raw folder
            in = getResources().openRawResource(R.raw.lights);
        } else { //Update from local storage
            in = HStorage.getInputStreamForFile(ASplashscreen.this, HStorage.FILE_JSON_LIGHTS);
        }
        ELights lights = null;
        try {
            lights = UGson.jsonToObject(in, ELights.class);
        } catch (Exception e) {
            System.out.println("==== PARSE ERROR :: LIGHTS :: " + e.getMessage());
            e.printStackTrace();
        }
        if (lights != null) {
            HLights.instance().setValues(this, lights);
        }
    }

    private void parseSounds() {
        int lastVersionSounds = UPersistent.getInt(ASplashscreen.this, HVersions.SHARED_PREF_LAST_JSONS_VERSION_SOUNDS, 0);

        InputStream in;
        //If there are no files in local storage OR there's a new version of the JSON files that we need to retrieve
        if (!HStorage.fileExists(ASplashscreen.this, HStorage.FILE_JSON_SOUNDS) ||
                HVersions.instance().shouldUpdateFromResources(HVersions.SOUNDS, lastVersionSounds)) { //Update from raw folder
            in = getResources().openRawResource(R.raw.sounds);
        } else { //Update from local storage
            in = HStorage.getInputStreamForFile(ASplashscreen.this, HStorage.FILE_JSON_SOUNDS);
        }
        ESounds sounds = null;
        try {
            sounds = UGson.jsonToObject(in, ESounds.class);
        } catch (Exception e) {
            System.out.println("==== PARSE ERROR :: SOUNDS" + e.getMessage());
        }
        if (sounds != null) {
            HSounds.instance().setValues(this, sounds);
        }
    }

    private void validateBackgrounds() throws NullPointerException {
        if (HBackgrounds.instance().getValues() == null) {
            throw new NullPointerException();
        }
        if (HBackgrounds.instance().getValues().getBackgrounds() == null) {
            throw new NullPointerException();
        }
    }

    private void validateLights() throws NullPointerException {
        if (HLights.instance().getValues() == null) {
            throw new NullPointerException();
        }
        if (HLights.instance().getValues().getLights() == null) {
            throw new NullPointerException();
        }
    }

    private void validateSounds() throws NullPointerException {
        if (HSounds.instance().getValues() == null) {
            throw new NullPointerException();
        }
        if (HSounds.instance().getValues().getSounds() == null) {
            throw new NullPointerException();
        }
    }

    private void removeBackgroundsFile() {
        HStorage.deleteFile(this, HStorage.FILE_JSON_BACKGROUNDS);
    }

    private void removeLightsFile() {
        HStorage.deleteFile(this, HStorage.FILE_JSON_LIGHTS);
    }

    private void removeSoundsFile() {
        HStorage.deleteFile(this, HStorage.FILE_JSON_SOUNDS);
    }

    private void runIabHelper() {

        //If there's no network connection, then ... sorry
        if (!UNetwork.isNetworkAvailable(this)) {
            displayAppStoreUnavailableDialog();
            System.out.println("=== IAB ERROR :: NO NETWORK");
            return;
        }

        try {
            mIabHelper = new IabHelper(ASplashscreen.this, CIab.IAB_PUBLIC_KEY);
            mIabHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
                @Override
                public void onIabSetupFinished(IabResult result) {
                    if (!result.isSuccess()) {
                        // Oh noes, there was a problem.
                        System.out.println("=== IAB ERROR :: CONNECTION :: " + result);
                        displayAppStoreUnavailableDialog();
                        return;
                    }

                    //Obtain and create the list of skus from both the LIGHTS and the SOUNDS handlers
                    List<String> skus = new ArrayList<String>();
                    skus.addAll(HSounds.instance().createSkuList(ASplashscreen.this, true));
                    skus.addAll(HLights.instance().createSkuList(ASplashscreen.this, true));

                    //Get the inventory
                    try {
                        mIabHelper.queryInventoryAsync(true, skus, mGotInventoryListener, new Thread.UncaughtExceptionHandler() {
                            @Override
                            public void uncaughtException(Thread thread, Throwable ex) {
                                //                            Crashlytics.logException(ex);
                                System.out.println("=== IAB ERROR :: query inventory crashed :: " + ex.getMessage());
                                displayAppStoreUnavailableDialog();
                            }
                        });
                    } catch (IllegalStateException e) {
                        displayAppStoreUnavailableDialog();
                    }
                }
            });
        } catch (NullPointerException e1) {
            //            Crashlytics.logException(e1);
            System.out.println("=== IAB ERROR :: query inventory crashed :: " + e1.getMessage());
            displayAppStoreUnavailableDialog();
        } catch (IllegalArgumentException e2) {
            //            Crashlytics.logException(e2);
            System.out.println("=== IAB ERROR :: query inventory crashed :: " + e2.getMessage());
            displayAppStoreUnavailableDialog();
        }
    }

    private void displayAppStoreUnavailableDialog() {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (mDialog == null) {
                    return;
                }
                mDialog.reset()
                        .header(R.string.inapp_store_unavailable_header)
                        .subheader(R.string.inapp_store_unavailable_subheader)
                        .btnOK()
                        .show();
            }
        });
    }

    private void jumpToMainActivity() {

        int timePassed = (int) (System.currentTimeMillis() - mStartTimestamp);

        int delay = (timePassed > SPLASHSCREEN_DELAY_MS) ? 0 : (SPLASHSCREEN_DELAY_MS - timePassed);

        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {

                //In case we need to display the tutorial, then do so
                if (AHelp.shouldDisplayTutorial(ASplashscreen.this)) {
                    CrashReport.log("ASplashscreen -> AHelp");
                    Intent i = new Intent(ASplashscreen.this, AHelp.class);
                    i.putExtra(AHelp.BUNDLE_SHOW_TUTORIAL, true);
                    startActivity(i);
                    finish();
                    overridePendingTransition(R.anim.anim_slide_in_from_bottom, R.anim.anim_stay_put);
                    return;
                } else { //Otherwise continue with normal flow
                    CrashReport.log("ASplashscreen -> AMain");
                    Intent i = new Intent(ASplashscreen.this, AMain.class);
                    i.putExtra(AMain.BUNDLE_DEBUGGING_CAME_FROM_SPLASHSCREEN, true);
                    startActivity(i);
                    finish();
                }

            }
        }, delay);
    }

    private void checkAllSoundsGiftExpired() {
        SimpleDateFormat fmt = new SimpleDateFormat(HJsonDataBase.GIFT_DATE_FORMAT);
        long now = System.currentTimeMillis();

        for (ESound sound : HSounds.instance().getValues().getSounds()) {
            if (sound != null) {
                checkSoundGiftExpired(sound, fmt, now);
            }
        }
    }

    private void checkAllLightsGiftExpired() {
        SimpleDateFormat fmt = new SimpleDateFormat(HJsonDataBase.GIFT_DATE_FORMAT);
        long now = System.currentTimeMillis();

        for (ELight light : HLights.instance().getValues().getLights()) {
            if (light != null) {
                checkLightGiftExpired(light, fmt, now);
            }
        }
    }

    private void checkSoundGiftExpired(ESound sound, SimpleDateFormat fmt, long now) {
        if (UString.stringsExist(sound.giftExpireStart, sound.giftExpireEnd)) {
            try {
                Date start = fmt.parse(sound.giftExpireStart);
                Date end = fmt.parse(sound.giftExpireEnd);
                if (now < start.getTime() || end.getTime() < now) {
                    sound.giftExpireStart = null;
                    sound.giftExpireEnd = null;
                }
            } catch (ParseException e) {
                //Do nothin'
            }
        }
    }

    private void checkLightGiftExpired
            (ELight light, SimpleDateFormat fmt, long now) {
        if (UString.stringsExist(light.giftExpireStart, light.giftExpireEnd)) {
            try {
                Date start = fmt.parse(light.giftExpireStart);
                Date end = fmt.parse(light.giftExpireEnd);
                if (now < start.getTime() || end.getTime() < now) {
                    light.giftExpireStart = null;
                    light.giftExpireEnd = null;
                }
            } catch (ParseException e) {
                //Do nothin'
            }
        }
    }

}

Upvotes: 2

Views: 4325

Answers (5)

Gunaseelan
Gunaseelan

Reputation: 15535

I guess your JSON data is in following format.

{ 
    a : "A",
    b : "B",
    c : "C"
}

Now you can have a class called JsonData which is structured as follows,

public class JsonData {
   public String a;
   public String b;
   public String c;
}

Now you can convert your json data as Java object using gson library.

Now crate a class like ObjectHolder, which is structured as follows.

public class ObjectHolder {
    public static JsonData jsonData;
}

Store your converted object in ObjectHolder.jsonData. Now you can access this object throughout project anytime.

Note: this object will becomes null when you clear your app from "recent apps" list.

Upvotes: 2

David Wasser
David Wasser

Reputation: 95618

This is pretty much standard Android behaviour. When your app is in the background it can be killed at any time, for any reason. Android simply kills the OS process hosting your app.

When the user returns to your app (or restarts your app), Android realizes that it had killed your app previously, so it creates a new OS process to host your app, then it instantiates the Application instance for your app, then it instantiates the topmost Activity in the task stack (ie: the Activity that was on screen at the time your app went into the background), then it calls onCreate() on that Activity so that the Activity can restore itself. Android passes the most recently saved instaces state of the Activity as the Bundle parameter to onCreate(). In this way, the Activity has a chance to restore itself.

Your Activity is crashing because it is relying on data that should have been set up previously. In the case where Android kills and then recreates the app's OS process, this data is gone.

There are various ways to work around this, one of which you have already used:

  • in onCreate() of all activities, check if the "app initialization" has been performed by using a public static variable or singleton. If the initialization has not been done, you know that your app's process was killed and recreated and you need to either redirect the user to your root Activity (ie: start the app all over again) or do the initialization immediately in the onCreate() of the Activity.

  • Save the data you need in onSaveInstanceState() and restore it in onCreate() and/or onRestoreInstanceState() or both.

  • Don't keep this data in memory. Keep it in a database or other non-memory-based persistent structure.

NOTE: In general you should not be using launchMode="singleTask". For most situations this is unnecessary, and usually causes more problems than it solves. This has nothing to do with the process kill/recreate problem you are having, but you should still avoid using the special launch modes singleTask and singleInstance. These should only be necessary if you are creating a HOME screen replacement.

Upvotes: 14

Xcihnegn
Xcihnegn

Reputation: 11597

Solution:

  • you should not set splash activity as singleTask that means root of activity stack, instead your MainActivity should set singleTask as root.

  • when your app back to foreground, in onCreate(...), you should check your static refrences of singleton class if not null before using them, if null then jump back to your splash activity (means reload and restore data into static references). In other word, if static references are recycled by system, then just restart your app.

Hope this help!

Upvotes: 3

Ircover
Ircover

Reputation: 2446

While using singleton there should be some getInstane method with just return instance, so you can place your check inside it like following:

public static SingletonClass getInstance() {
    if(instance == null) {
        instance = StaticMethodToLoadInstance();
    }
    return instance;
}

I guess you can place all your data loading code inside static StaticMethodToLoadInstance().

UPDATED

Yes, loading data can spend much time, so it could be made with another method. First of all, create your own interface:

public static interface OnInstanceLoadedListener {
    public void onIntsanceLoaded(SingletonClass instance);
}

Then change getInstance by following:

public static void getInstance(final OnInstanceLoadedListener listener, Activity context) {
    final ProgressDialog dialog = null;
    if(instance == null) {//if there should be loading
        dialog = StaticMethodToCreateProgressDialog(context);
        dialog.show();
    }
    new Thread(new Runnable() {
        @Override
        public void run() {
            if(instance == null) {
                instance = StaticMethodToLoadInstance();
            }
            context.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    listener.onIntsanceLoaded(instance);
                    if(dialog != null && dialog.isShowing())
                        dialog.dismiss();
                }
            });
        }
    }).start();
}

And your getInstance usage changes from

SingletonClass object = SingletonClass.getInstance();
String data = object.getData();

to

getInstance(new OnInstanceLoadedListener() {
    @Override
    public void onIntsanceLoaded(SingletonClass instance) {
        String data = instance.getData();
    }
}, YourActivityClass.this);

In this way your data will load asynchronously. Yes, it looks much more difficult, but it can show progress dialog - user can see app is still working.

Upvotes: 7

Dmide
Dmide

Reputation: 6462

Well, in my opinion there are two ways to improve your current restart-app approach (your approach is ok but a little dirty):

1) Get rid of ASplashscreen, move loading logic to some helper class and schedule loading of your data from MainActivity while showing your splash on top of Activity layout. If you use RelativeLayout for your MainActivity you may easily achieve this by adding invisible view with "match_parent" params on the bottom of view hierarchy (to overlap other views) and by making it visible when necessary. This is cleaner compared to two Activities and app restarting.

2) Make your singleton Parcelable and store it in onSaveInstanceState() in MainActivity. (of course your singleton won't be a singleton any more with this approach). In this case Android will save your data along with MainActivity and after restoring it in OnCreate() everything will be in place. This is a little cleaner than just saving it to SharedPreferences.

Upvotes: 2

Related Questions