应用程序从错误的活动重新启动

Posted

技术标签:

【中文标题】应用程序从错误的活动重新启动【英文标题】:App restarts from wrong activity 【发布时间】:2015-04-07 17:18:44 【问题描述】:

这是一个难题:

我打开我的应用程序。它启动一个作为启动屏幕 (ASplashscreen) 的活动,我在其中从本地存储 (raw 文件夹) 加载一些 JSON 数据并将其存储在内存中的 singleton object (静态) 中。此过程完成后,它会自动移动到主要活动 (AMain)

我通过按 home button 退出应用程序并运行其他应用程序、游戏等。当我重新打开我的应用程序时,应用程序在 AMainonCreate 方法内崩溃,因为它试图使用一些singleton object 中的数据,但数据是 null。所以它会抛出一个NullPointerException。 它似乎重新启动了AMain 而不是ASplashscreen,因此singleton 没有机会重新初始化。

这会在多次此类尝试中随机发生...

我有两个假设...

    根据我对 android 操作系统的了解,我的第一个假设是,当我运行其他应用程序(尤其是游戏)时,其中一个需要大量内存,因此操作系统从内存中释放了我的应用程序腾出空间,所以singleton datagarbage collected

    我还推测,虽然 gc 从内存中删除了我的单例,但操作系统仍然保留了一些与当前运行活动的“状态”相关的数据,所以它至少知道它有 @987654338 @活动在我关闭应用程序之前打开。这可以解释为什么它重新打开了 AMain 活动而不是 ASplashscreen

我说的对吗?还是有另一种解释为什么我会得到这个异常?欢迎任何建议/澄清。

此外,处理此问题的最佳方法是什么?我的方法是在我尝试使用单例数据时检查它是否存在,如果它为空,则基本上重新启动应用程序。这使它通过ASplashscreen,所以JSON被初始化并且一切正常。

编辑根据要求,这是我的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>

如果你们真的想要,这里是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'
            
        
    


【问题讨论】:

您的 AndroidManifest 文件可能有问题。可以发一下吗? 在您的应用被清除之前,您的单身不应该被清除(至少不像您所说的那样在一致的基础上)。可能发生的情况是您的应用程序被完全杀死并试图启动,但正如所指出的那样,跳过了您的启动屏幕。正如@curob 所说,我将首先发布您的清单和可能的 ASplashscreen 代码(因为它可能正在做一些您认为实际上会导致问题的事情)。 您是否考虑过将初始屏幕活动的launchMode 更改为standard? (而不是singleTask 您也可以尝试将数据存储在 SharedPreferences 对象而不是单例中,这样可以使其更持久。 您在singleton object 为空时重新启动应用程序的方法对我来说听起来不错。我会把这个空检查放在onResumeAMain 中。 【参考方案1】:

这几乎是标准的 Android 行为。当您的应用程序处于后台时,它可以随时以任何原因被终止。 Android 只会杀死托管您应用的操作系统进程。

当用户返回您的应用(或重新启动您的应用)时,Android 意识到它之前已经杀死了您的应用,因此它会创建一个新的操作系统进程来托管您的应用,然后它会为您的应用实例化 Application 实例,然后它实例化任务堆栈中最顶层的Activity(即:应用程序进入后台时屏幕上的Activity),然后在该Activity 上调用onCreate(),这样@ 987654326@可以自行恢复。 Android 将Activity 的最近保存的实例状态作为Bundle 参数传递给onCreate()。这样Activity就有机会自我恢复了。

您的Activity 正在崩溃,因为它依赖于之前应该设置的数据。在 Android 杀死然后重新创建应用的操作系统进程的情况下,这些数据就消失了。

有多种方法可以解决这个问题,其中一种您已经使用过:

在所有活动的onCreate() 中,检查是否使用public static 变量或单例执行了“应用程序初始化”。如果初始化尚未完成,您知道您的应用程序的进程已被杀死并重新创建,您需要将用户重定向到您的根Activity(即:重新启动应用程序)或立即在@中进行初始化Activity的987654335@。

将您需要的数据保存在onSaveInstanceState() 中并在onCreate() 和/或onRestoreInstanceState() 或两者中恢复。

不要将此数据保存在内存中。将其保存在数据库或其他非基于内存的持久结构中。

注意:一般情况下,您不应使用launchMode="singleTask"。在大多数情况下,这是不必要的,而且通常会导致比它解决的问题更多的问题。这与您遇到的进程终止/重新创建问题无关,但您仍应避免使用特殊启动模式singleTasksingleInstance。只有在创建主屏幕替换时才需要这些。

【讨论】:

【参考方案2】:

在使用单例时,应该有一些 getInstane 方法只有 return instance,因此您可以将支票放入其中,如下所示:

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

我想你可以将所有数据加载代码放在静态 StaticMethodToLoadInstance() 中。

更新

是的,加载数据会花费很多时间,因此可以使用其他方法进行。首先,创建自己的界面:

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

然后通过以下方式更改getInstance

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();

而您的 getInstance 用法从

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

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

通过这种方式,您的数据将异步加载。是的,它看起来要困难得多,但它可以显示进度对话框 - 用户可以看到应用仍在运行。

【讨论】:

嗨。是的,这是我自己想到的一种方法,但问题是数据的加载需要很多时间,所以我真的希望我能找到问题的根源而不是这样做。我可以很容易地检查是否缺少数据,如果它发生,则启动整个过程以再次加载它,但这会使我的应用程序不时随机地有一个加载器。我想避免这种情况...... 我添加到我的答案解决方案中,因为我可以看到它。我想,无论如何,你应该花一些时间加载到空实例中。【参考方案3】:

解决方案:

您不应将启动活动设置为singleTask,这意味着活动堆栈的根,而您的 MainActivity 应将singleTask 设置为根。

当你的应用程序回到前台时,在onCreate(...),你应该在使用它们之前检查你的单例类的静态引用如果不为空,如果为空则跳回你的启动活动(意味着重新加载并将数据恢复到静态引用)。换句话说,如果静态引用被系统回收,那么只需重启你的应用程序。

希望对您有所帮助!

【讨论】:

一般来说你不应该使用launchMode="singleTask"。在大多数情况下,这是不必要的,而且通常会导致比它解决的问题更多的问题。 在这种情况下,因为splash是启动活动,所以如果你不想在退出应用程序之前返回splash,那么设置一个root MainActivity可能是一种方式 有几种方法可以在不使用特殊启动模式的情况下实现这一目标。这里有两个:1) 启动Main 时会调用finish()。 2) 覆盖onBackPressed(),以便它用Splash 调用startActivity(),在Intent 中添加一个额外的内容,告诉Splash 立即调用finish()【参考方案4】:

嗯,在我看来,有两种方法可以改进您当前的重启应用方法(您的方法还可以,但有点脏):

1) 摆脱ASplashscreen,将加载逻辑移至某个辅助类,并安排从MainActivity 加载数据,同时在Activity 布局上显示您的启动画面。如果您将RelativeLayout 用于您的MainActivity,您可以通过在视图层次结构的底部添加带有"match_parent" 参数的不可见视图(以与其他视图重叠)并在必要时使其可见,从而轻松实现此目的。与两个Activities 和应用重启相比,这更干净。

2) 制作你的单例 Parcelable 并将其存储在 onSaveInstanceState() 中的 MainActivity 中。 (当然,使用这种方法,您的单身人士将不再是单身人士)。在这种情况下,Android 会将您的数据与MainActivity 一起保存,并且在将其恢复到OnCreate() 之后,一切都会到位。这比将其保存到 SharedPreferences 更简洁。

【讨论】:

【参考方案5】:

我猜你的 JSON 数据格式如下。

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

现在您可以拥有一个名为JsonData 的类,其结构如下,

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

现在您可以使用gson 库将您的 json 数据转换为 Java 对象。

现在创建一个类似ObjectHolder 的类,其结构如下。

public class ObjectHolder 
    public static JsonData jsonData;

将转换后的对象存储在ObjectHolder.jsonData 中。现在您可以随时在整个项目中访问此对象。

注意:当您从resent apps list 清除应用程序时,此对象将变为null

这个方法对我有用,所以我希望这对你也有帮助。

【讨论】:

以上是关于应用程序从错误的活动重新启动的主要内容,如果未能解决你的问题,请参考以下文章

从通知意图启动活动时重新创建 Android ViewModel

从另一个活动重新创建/重新启动活动

Android Activity 错误地重新设置为应用程序任务

如何修复在 Android 中崩溃的活动的重新启动?

将数据从通知发送到活动类而不重新启动意图

从通知启动时防止重新创建活动活动