حفظ حالة نشاط Android باستخدام Save State




android-activity application-state (18)

لقد كنت أعمل على نظام Android SDK ، ومن غير الواضح كيف يتم حفظ حالة التطبيق. لذا ، نظرًا لإمكانية إعادة استخدام الأدوات البسيطة في مثال "Hello، Android":

package com.android.hello;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class HelloAndroid extends Activity {

  private TextView mTextView = null;

  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mTextView = new TextView(this);

    if (savedInstanceState == null) {
       mTextView.setText("Welcome to HelloAndroid!");
    } else {
       mTextView.setText("Welcome back.");
    }

    setContentView(mTextView);
  }
}

اعتقدت أنه سيكون كافيًا لأبسط الحالات ، ولكنه يستجيب دائمًا للرسالة الأولى ، بغض النظر عن كيفية التنقل من التطبيق.

أنا متأكد من أن الحل بسيط مثل تجاوز onPause أو شيء من هذا القبيل ، ولكن أنا بدس بعيدا في الوثائق لمدة 30 دقيقة أو نحو ذلك ولم نجد أي شيء واضح.


إضافة LiveData (مكونات بنية Android) إلى مشروعك

أضف التبعية التالية

implementation "android.arch.lifecycle:extensions:1.1.0"

يأخذ LiveData في مراقب وإعلامه حول تغييرات البيانات فقط عندما يكون في حالة STARTED أو RESUMED. تكمن الميزة في LiveData في أنه عندما ينتقل نشاطك إلى أي حالة أخرى غير STARTED أو RESUMED فلن يقوم باستدعاء الأسلوب onChanged على المراقب .

private TextView mTextView;
private MutableLiveData<String> mMutableLiveData;

@Override
protected void onCreate(Bundle savedInstanceState) {
    mTextView = (TextView) findViewById(R.id.textView);
    mMutableLiveData = new MutableLiveData<>();
    mMutableLiveData.observe(this, new Observer<String>() {
        @Override
        public void onChanged(@Nullable String s) {
            mTextView.setText(s);
        }
    });

}

أعتقد أنني وجدت الإجابة. اسمحوا لي أن أقول ما فعلته بكلمات بسيطة:

لنفترض أن لدي نشاطين ، النشاط 1 والنشاط 2 وأنا أتنقل من النشاط 1 إلى النشاط 2 (لقد أجريت بعض الأعمال في النشاط 2) وأعد مرة أخرى إلى النشاط 1 من خلال النقر على زر في النشاط 1. الآن في هذه المرحلة ، أردت العودة إلى النشاط 2 وأريد أن أرى نشاطي 2 في نفس الحالة عندما غادرت النشاط الأخير 2.

بالنسبة للسيناريو أعلاه ، ما فعلته هو أنني قمت في البيان بطرح بعض التغييرات على هذا النحو:

<activity android:name=".activity2"
          android:alwaysRetainTaskState="true"      
          android:launchMode="singleInstance">
</activity>

وفي النشاط 1 على زر انقر فوق الحدث فعلت مثل هذا:

Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
intent.setClassName(this,"com.mainscreen.activity2");
startActivity(intent);

وفي النشاط 2 على زر انقر فوق الحدث فعلت مثل هذا:

Intent intent=new Intent();
intent.setClassName(this,"com.mainscreen.activity1");
startActivity(intent);

الآن ما سيحدث هو أنه بغض النظر عن التغييرات التي أجريناها في النشاط 2 ، لن نفقدها ، ويمكننا عرض النشاط 2 في نفس الحالة التي غادرناها في السابق.

أعتقد أن هذا هو الحل وهذا يعمل بشكل جيد بالنسبة لي. صحح لي إن كنت مخطئ.


إنقاذ الدولة هو kludge في أحسن الأحوال بقدر ما أشعر بالقلق. إذا كنت بحاجة إلى حفظ البيانات الدائمة ، SQLite عليك سوى استخدام قاعدة بيانات SQLite . الروبوت يجعل من السهل SOOO .

شيء من هذا القبيل:

import java.util.Date;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class dataHelper {

    private static final String DATABASE_NAME = "autoMate.db";
    private static final int DATABASE_VERSION = 1;

    private Context context;
    private SQLiteDatabase db;
    private OpenHelper oh ;

    public dataHelper(Context context) {
        this.context = context;
        this.oh = new OpenHelper(this.context);
        this.db = oh.getWritableDatabase();
    }

    public void close()
    {
        db.close();
        oh.close();
        db = null;
        oh = null;
        SQLiteDatabase.releaseMemory();
    }


    public void setCode(String codeName, Object codeValue, String codeDataType)
    {
        Cursor codeRow = db.rawQuery("SELECT * FROM code WHERE codeName = '"+  codeName + "'", null);
        String cv = "" ;

        if (codeDataType.toLowerCase().trim().equals("long") == true)
        {
            cv = String.valueOf(codeValue);
        }
        else if (codeDataType.toLowerCase().trim().equals("int") == true)
        {
            cv = String.valueOf(codeValue);
        }
        else if (codeDataType.toLowerCase().trim().equals("date") == true)
        {
            cv = String.valueOf(((Date)codeValue).getTime());
        }
        else if (codeDataType.toLowerCase().trim().equals("boolean") == true)
        {
            String.valueOf(codeValue);
        }
        else
        {
            cv = String.valueOf(codeValue);
        }

        if(codeRow.getCount() > 0) //exists-- update
        {
            db.execSQL("update code set codeValue = '" + cv +
                "' where codeName = '" + codeName + "'");
        }
        else // does not exist, insert
        {
            db.execSQL("INSERT INTO code (codeName, codeValue, codeDataType) VALUES(" +
                    "'" + codeName + "'," +
                    "'" + cv + "'," +
                    "'" + codeDataType + "')" );
        }
    }

    public Object getCode(String codeName, Object defaultValue)
    {
        //Check to see if it already exists
        String codeValue = "";
        String codeDataType = "";
        boolean found = false;
        Cursor codeRow  = db.rawQuery("SELECT * FROM code WHERE codeName = '"+  codeName + "'", null);
        if (codeRow.moveToFirst())
        {
            codeValue = codeRow.getString(codeRow.getColumnIndex("codeValue"));
            codeDataType = codeRow.getString(codeRow.getColumnIndex("codeDataType"));
            found = true;
        }

        if (found == false)
        {
            return defaultValue;
        }
        else if (codeDataType.toLowerCase().trim().equals("long") == true)
        {
            if (codeValue.equals("") == true)
            {
                return (long)0;
            }
            return Long.parseLong(codeValue);
        }
        else if (codeDataType.toLowerCase().trim().equals("int") == true)
        {
            if (codeValue.equals("") == true)
            {
                return (int)0;
            }
            return Integer.parseInt(codeValue);
        }
        else if (codeDataType.toLowerCase().trim().equals("date") == true)
        {
            if (codeValue.equals("") == true)
            {
                return null;
            }
            return new Date(Long.parseLong(codeValue));
        }
        else if (codeDataType.toLowerCase().trim().equals("boolean") == true)
        {
            if (codeValue.equals("") == true)
            {
                return false;
            }
            return Boolean.parseBoolean(codeValue);
        }
        else
        {
            return (String)codeValue;
        }
    }


    private static class OpenHelper extends SQLiteOpenHelper {

        OpenHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL("CREATE TABLE IF  NOT EXISTS code" +
            "(id INTEGER PRIMARY KEY, codeName TEXT, codeValue TEXT, codeDataType TEXT)");
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        }
    }
}

مكالمة بسيطة بعد ذلك

dataHelper dh = new dataHelper(getBaseContext());
String status = (String) dh.getCode("appState", "safetyDisabled");
Date serviceStart = (Date) dh.getCode("serviceStartTime", null);
dh.close();
dh = null;

تحتاج إلى تجاوز onSaveInstanceState(Bundle savedInstanceState) وكتابة قيم حالة التطبيق التي تريد تغييرها إلى معلمة Bundle مثل:

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
  super.onSaveInstanceState(savedInstanceState);
  // Save UI state changes to the savedInstanceState.
  // This bundle will be passed to onCreate if the process is
  // killed and restarted.
  savedInstanceState.putBoolean("MyBoolean", true);
  savedInstanceState.putDouble("myDouble", 1.9);
  savedInstanceState.putInt("MyInt", 1);
  savedInstanceState.putString("MyString", "Welcome back to Android");
  // etc.
}

تعد الحزمة بشكل أساسي وسيلة لتخزين خريطة NVP ("اسم - قيمة زوج") ، وسيتم تمريرها إلى onCreate() وأيضا onRestoreInstanceState() حيث يمكنك استخراج قيم مثل:

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
  super.onRestoreInstanceState(savedInstanceState);
  // Restore UI state from the savedInstanceState.
  // This bundle has also been passed to onCreate.
  boolean myBoolean = savedInstanceState.getBoolean("MyBoolean");
  double myDouble = savedInstanceState.getDouble("myDouble");
  int myInt = savedInstanceState.getInt("MyInt");
  String myString = savedInstanceState.getString("MyString");
}

عادة ما تستخدم هذه التقنية لتخزين قيم المثيل لتطبيقك (التحديدات ، النص غير المحفوظة ، وما إلى ذلك).


كلا الطريقتين مفيدتان وصالحتان وكلاهما مناسبان لسيناريوهات مختلفة:

  1. ينهي المستخدم التطبيق ويعيد فتحه في وقت لاحق ، ولكن يحتاج التطبيق إلى إعادة تحميل البيانات من الجلسة الأخيرة - وهذا يتطلب نهج تخزين مستمر مثل استخدام SQLite.
  2. يقوم المستخدم بتبديل التطبيق ثم يعود إلى النسخة الأصلية ويريد onSaveInstanceState() حيث توقف - حفظ واستعادة بيانات الحزمة (مثل بيانات حالة التطبيق) في onSaveInstanceState() وعادة ما يكون onRestoreInstanceState() مناسبًا.

إذا قمت بحفظ بيانات الحالة بطريقة مستمرة ، فيمكن إعادة تحميلها في onResume() أو onCreate() (أو في الواقع على أي مكالمة لدورة الحياة). هذا قد يكون أو لا يكون السلوك المطلوب. إذا قمت بتخزينها في حزمة في InstanceState ، فإنها تكون عابرة وتناسب فقط تخزين البيانات لاستخدامها في "الجلسة" للمستخدم نفسه (أستخدم مصطلح الجلسة بشكل فضفاض) ولكن ليس بين "الجلسات".

لا يعني ذلك أن أحد المقاربات أفضل من الآخر ، مثل كل شيء ، فمن المهم فقط فهم السلوك الذي تحتاج إليه واختيار الأسلوب الأنسب.


لاحظ أنه ليس من الآمن استخدام onSaveInstanceState و onRestoreInstanceState للبيانات المستمرة ، وفقًا للوثائق الخاصة بحالات النشاط في developer.android.com/reference/android/app/Activity.html .

يوضح المستند (في قسم "دورة حياة النشاط"):

لاحظ أنه من المهم حفظ البيانات الدائمة في onPause() بدلاً من onSaveInstanceState(Bundle) لأن الجزء الأحدث ليس جزءًا من الاسترجاعات عن دورة الحياة ، لذلك لن يتم استدعاؤها في كل موقف كما هو موضح في وثائقها.

بمعنى آخر ، ضع رمز الحفظ / الاستعادة للبيانات الدائمة في onPause() و onResume() !

تعديل : لمزيد من التوضيح ، إليك onSaveInstanceState() :

يتم استدعاء هذه الطريقة قبل أن يتم قتل النشاط بحيث أنه عندما يعود لبعض الوقت في المستقبل يمكنه استعادة حالته. على سبيل المثال ، إذا تم تشغيل النشاط B أمام النشاط A ، وعند نقطة معينة يتم قتل النشاط A لاستعادة الموارد ، فسيكون للنشاط A فرصة لحفظ الحالة الحالية لواجهة المستخدم الخاصة به عبر هذه الطريقة بحيث عندما يعود المستخدم بالنشاط A ، يمكن استعادة حالة واجهة المستخدم عبر onCreate(Bundle) أو onRestoreInstanceState(Bundle) .


يتم استدعاء onSaveInstanceState عندما يحتاج النظام إلى ذاكرة ويقتل تطبيقًا. لا يتم استدعاء عندما يقوم المستخدم فقط إغلاق التطبيق. لذلك أعتقد أن حالة التطبيق يجب حفظها أيضًا في onPause يجب حفظها في بعض التخزين الدائم مثل Preferences أو Sqlite


onSaveInstanceState()للبيانات العابرة (المستعادة في onCreate()/ onRestoreInstanceState()) ، onPause()للبيانات الثابتة (المستعادة في onResume()). من موارد تقنية Android:

يتم استدعاء onSaveInstanceState () من Android إذا تم إيقاف النشاط وقد يتم قتله قبل استئنافه! هذا يعني أنه يجب تخزين أي حالة ضرورية لإعادة التهيئة إلى نفس الشرط عند إعادة تشغيل النشاط. وهو نظير أسلوب onCreate () ، وفي الواقع حزمة savedInstanceState التي تم تمريرها إلى onCreate () هي نفس الحزمة التي تقوم بإنشائها كـ outState في أسلوب onSaveInstanceState ().

onPause () و onResume () هي طرق مجانية أيضًا. يتم استدعاء onPause () دائمًا عند انتهاء النشاط ، حتى إذا قمنا بتحريض ذلك (مع استدعاء إنهاء () على سبيل المثال). سنستخدم هذا لحفظ الملاحظة الحالية مرة أخرى إلى قاعدة البيانات. وتتمثل الممارسة الجيدة في نشر أي موارد يمكن إطلاقها أثناء onPause () أيضًا ، للحصول على موارد أقل عندما تكون في الحالة السلبية.


عندما يتم إنشاء نشاط ، يتم استدعاء طريقة onCreate ().

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

savedInstanceState هو عنصر من فئة Bundle الذي يكون خاليًا لأول مرة ، ولكنه يحتوي على قيم عند إعادة إنشائه. لحفظ حالة النشاط ، يجب تجاوز onSaveInstanceState ().

   @Override
    protected void onSaveInstanceState(Bundle outState) {
      outState.putString("key","Welcome Back")
        super.onSaveInstanceState(outState);       //save state
    }

ضع قيمك في عنصر الحزمة "outState" مثل outState.putString ("key"، "Welcome Back") وحفظ عن طريق الاتصال super. عندما يتم تدمير النشاط ، يتم حفظ الحالة في كائن الحزمة ويمكن استعادتها بعد الاستجمام في onCreate () أو onRestoreInstanceState (). الحزمة المتلقاة في onCreate () و onRestoreInstanceState () هي نفسها.

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

          //restore activity's state
         if(savedInstanceState!=null){
          String reStoredString=savedInstanceState.getString("key");
            }
    }

أو

  //restores activity's saved state
 @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
      String restoredMessage=savedInstanceState.getString("key");
    }

كود كوتلين:

حفظ:

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState.apply {
        putInt("intKey", 1)
        putString("stringKey", "String Value")
        putParcelable("parcelableKey", parcelableObject)
    })
}

ثم في onCreate()أوonRestoreInstanceState()

    val restoredInt = savedInstanceState?.getInt("intKey") ?: 1 //default int
    val restoredString = savedInstanceState?.getString("stringKey") ?: "default string"
    val restoredParcelable = savedInstanceState?.getParcelable<ParcelableClass>("parcelableKey") ?: ParcelableClass() //default parcelable

أضف القيم الافتراضية إذا كنت لا تريد أن يكون لديك اختيارات


وفي الوقت نفسه لا أقوم به بشكل عام

Bundle savedInstanceState & Co

الدورة الحية هي لمعظم الأنشطة معقدة للغاية وغير ضرورية. وتقول جوجل نفسها ، فهي ليست موثوقة حتى.

طريقي هو حفظ أي تغييرات على الفور في التفضيلات

 SharedPreferences p;
 p.edit().put(..).commit()

بطريقة ما تعمل SharedPreferences مماثلة مثل الحزم. وبطبيعة الحال وفي البداية يجب أن تكون هذه القيم حمراء من التفضيلات.

في حالة البيانات المعقدة ، يمكنك استخدام Sqlite بدلاً من استخدام التفضيلات.

عند تطبيق هذا المفهوم ، يستمر النشاط في استخدام آخر حالة تم حفظها ، بغض النظر عما إذا كانت مفتوحة مبدئية مع إعادة التمهيد أو إعادة فتحها بسبب مكدس الظهر.


الآن يوفر Android ViewModels لتوفير الحالة ، يجب أن تحاول استخدام هذا بدلاً من saveInstanceState.


حقا onSaveInstanceاستدعاء callen عندما يذهب النشاط إلى الخلفية

اقتباس من المستندات: " onSaveInstanceState(Bundle)يتم استدعاء الأسلوب قبل وضع النشاط في حالة الخلفية هذه"


على الرغم من أن الإجابة المقبولة صحيحة ، فهناك طريقة أسرع وأسهل لحفظ حالة النشاط على Android باستخدام مكتبة تسمى Icepick . Icepick هو معالج توضيحي يهتم بكافة التعليمات البرمجية المستخدمة في حفظ الحالة واستعادتها.

القيام بشيء من هذا القبيل مع Icepick:

class MainActivity extends Activity {
  @State String username; // These will be automatically saved and restored
  @State String password;
  @State int age;

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Icepick.restoreInstanceState(this, savedInstanceState);
  }

  @Override public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    Icepick.saveInstanceState(this, outState);
  }
}

هو نفس ما تفعله:

class MainActivity extends Activity {
  String username;
  String password;
  int age;

  @Override
  public void onSaveInstanceState(Bundle savedInstanceState) {
    super.onSaveInstanceState(savedInstanceState);
    savedInstanceState.putString("MyString", username);
    savedInstanceState.putString("MyPassword", password);
    savedInstanceState.putInt("MyAge", age); 
    /* remember you would need to actually initialize these variables before putting it in the
    Bundle */
  }

  @Override
  public void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    username = savedInstanceState.getString("MyString");
    password = savedInstanceState.getString("MyPassword");
    age = savedInstanceState.getInt("MyAge");
  }
}

يعمل Icepick مع أي كائن يحفظ حالته مع Bundle.


لست متأكدًا مما إذا كان الحل الخاص بي قد تم العبوس عليه أم لا ولكنني استخدم خدمة منضمين للاستمرار في حالة ViewModel. تعتمد إمكانية تخزينها في الذاكرة في الخدمة أو استمرارها واستردادها من قاعدة بيانات SqlLite على متطلباتك. هذه هي الخدمات التي تقدمها أي نكهة ، فإنها توفر خدمات مثل الحفاظ على حالة التطبيق والمنطق العملي المشترك المجرد.

نظرًا لقيود الذاكرة والمعالجة المتأصلة على أجهزة الجوّال ، أتعامل مع طرق عرض Android بطريقة مماثلة لصفحة الويب. لا تحتفظ الصفحة بحالة ، بل هي مكون طبقة عرض فقط ، والغرض الوحيد منها هو تقديم حالة التطبيق وقبول إدخال المستخدم. تستخدم الاتجاهات الحديثة في بنية تطبيق الويب استخدام نموذج الطراز القديم ، العرض ، المراقب المالي (MVC) ، حيث تكون الصفحة هي طريقة العرض ، وبيانات المجال هي النموذج ، ووحدة التحكم تقع خلف خدمة الويب. يمكن استخدام النمط نفسه في android مع عرض يجري بشكل جيد ... the View ، والنموذج هو بيانات نطاقك ويتم تنفيذ وحدة التحكم كخدمة مرتبطة بنظام Android. عندما تريد عرضًا للتفاعل مع وحدة التحكم ، قم بربطه عند البدء / الاستئناف والتأكيد على التوقف / الإيقاف المؤقت.

يمنحك هذا النهج مكافأة إضافية لفرض مبدأ تصميم فصل الاهتمام في أنه يمكن نقل كل منطق تطبيق الأعمال الخاص بك إلى خدمتك مما يقلل من المنطق المكرر عبر وجهات النظر المتعددة ويسمح للرؤية بفرض مبدأ تصميم مهم آخر ، المسؤولية الفردية.


للإجابة على السؤال الأصلي مباشرة. saveInstancestate فارغ لأن نشاطك لا يتم إعادة إنشائه أبدًا.

لن تتم إعادة إنشاء نشاطك إلا باستخدام حزمة حالة عندما:

  • تغييرات التكوين مثل تغيير الاتجاه أو لغة الهاتف التي قد تتطلب إنشاء مثيل نشاط جديد.
  • تعود إلى التطبيق من الخلفية بعد أن يكون نظام التشغيل قد دمر النشاط.

سيدمر Android أنشطة الخلفية عندما يكون تحت ضغط الذاكرة أو بعد أن يكون في الخلفية لفترة ممتدة من الوقت.

عند اختبار مثال hello world لديك ، هناك بعض الطرق لمغادرة النشاط والعودة إليه.

  • عند الضغط على زر الرجوع ، يتم الانتهاء من النشاط. إعادة إطلاق التطبيق هو مثال جديد تمامًا. أنت لا تستأنف من الخلفية على الإطلاق.
  • عند الضغط على زر الصفحة الرئيسية أو استخدام مبدّل المهام ، سينتقل النشاط إلى الخلفية. عند الانتقال إلى التطبيق مرة أخرى ، لن يتم الاتصال إلا إذا كان النشاط يجب تدميره.

في معظم الحالات ، إذا كنت تضغط على المنزل فقط ثم تطلق التطبيق مرة أخرى ، فلن يلزم إعادة إنشاء النشاط. يوجد بالفعل في الذاكرة لذا لن يتم استدعاء onCreate ().

هناك خيار ضمن الإعدادات -> خيارات المطور التي تسمى "لا تحتفظ بالأنشطة". عندما يتم تمكينه ، سيعمل Android دائمًا على تدمير الأنشطة وإعادة إنشائها عند وضعها في الخلفية. يعد هذا خيارًا رائعًا لتمكين التمكين عند التطوير لأنه يحاكي السيناريو الأسوأ. (يقوم جهاز ذاكرة منخفض بإعادة تدوير أنشطتك طوال الوقت).

الإجابات الأخرى ذات قيمة في أنها تعلمك الطرق الصحيحة لتخزين الحالة ، لكنني لم أشعر بأنهم أجابوا حقاً لماذا لم تعمل الكود بالشكل الذي توقعتموه.


للمساعدة في تقليل boilerplate استخدم التالي interfaceو classللقراءة / الكتابة إلى حالة Bundleحفظ الحالة.

أولاً ، قم بإنشاء واجهة سيتم استخدامها لتوضيح المتغيرات الخاصة بك:

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({
        ElementType.FIELD
})
public @interface SaveInstance {

}

بعد ذلك ، قم بإنشاء فئة حيث سيتم استخدام الانعكاس لحفظ القيم في الحزمة:

import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;

import java.io.Serializable;
import java.lang.reflect.Field;

/**
 * Save and load fields to/from a {@link Bundle}. All fields should be annotated with {@link
 * SaveInstance}.</p>
 */
public class Icicle {

    private static final String TAG = "Icicle";

    /**
     * Find all fields with the {@link SaveInstance} annotation and add them to the {@link Bundle}.
     *
     * @param outState
     *         The bundle from {@link Activity#onSaveInstanceState(Bundle)} or {@link
     *         Fragment#onSaveInstanceState(Bundle)}
     * @param classInstance
     *         The object to access the fields which have the {@link SaveInstance} annotation.
     * @see #load(Bundle, Object)
     */
    public static void save(Bundle outState, Object classInstance) {
        save(outState, classInstance, classInstance.getClass());
    }

    /**
     * Find all fields with the {@link SaveInstance} annotation and add them to the {@link Bundle}.
     *
     * @param outState
     *         The bundle from {@link Activity#onSaveInstanceState(Bundle)} or {@link
     *         Fragment#onSaveInstanceState(Bundle)}
     * @param classInstance
     *         The object to access the fields which have the {@link SaveInstance} annotation.
     * @param baseClass
     *         Base class, used to get all superclasses of the instance.
     * @see #load(Bundle, Object, Class)
     */
    public static void save(Bundle outState, Object classInstance, Class<?> baseClass) {
        if (outState == null) {
            return;
        }
        Class<?> clazz = classInstance.getClass();
        while (baseClass.isAssignableFrom(clazz)) {
            String className = clazz.getName();
            for (Field field : clazz.getDeclaredFields()) {
                if (field.isAnnotationPresent(SaveInstance.class)) {
                    field.setAccessible(true);
                    String key = className + "#" + field.getName();
                    try {
                        Object value = field.get(classInstance);
                        if (value instanceof Parcelable) {
                            outState.putParcelable(key, (Parcelable) value);
                        } else if (value instanceof Serializable) {
                            outState.putSerializable(key, (Serializable) value);
                        }
                    } catch (Throwable t) {
                        Log.d(TAG, "The field '" + key + "' was not added to the bundle");
                    }
                }
            }
            clazz = clazz.getSuperclass();
        }
    }

    /**
     * Load all saved fields that have the {@link SaveInstance} annotation.
     *
     * @param savedInstanceState
     *         The saved-instance {@link Bundle} from an {@link Activity} or {@link Fragment}.
     * @param classInstance
     *         The object to access the fields which have the {@link SaveInstance} annotation.
     * @see #save(Bundle, Object)
     */
    public static void load(Bundle savedInstanceState, Object classInstance) {
        load(savedInstanceState, classInstance, classInstance.getClass());
    }

    /**
     * Load all saved fields that have the {@link SaveInstance} annotation.
     *
     * @param savedInstanceState
     *         The saved-instance {@link Bundle} from an {@link Activity} or {@link Fragment}.
     * @param classInstance
     *         The object to access the fields which have the {@link SaveInstance} annotation.
     * @param baseClass
     *         Base class, used to get all superclasses of the instance.
     * @see #save(Bundle, Object, Class)
     */
    public static void load(Bundle savedInstanceState, Object classInstance, Class<?> baseClass) {
        if (savedInstanceState == null) {
            return;
        }
        Class<?> clazz = classInstance.getClass();
        while (baseClass.isAssignableFrom(clazz)) {
            String className = clazz.getName();
            for (Field field : clazz.getDeclaredFields()) {
                if (field.isAnnotationPresent(SaveInstance.class)) {
                    String key = className + "#" + field.getName();
                    field.setAccessible(true);
                    try {
                        Object fieldVal = savedInstanceState.get(key);
                        if (fieldVal != null) {
                            field.set(classInstance, fieldVal);
                        }
                    } catch (Throwable t) {
                        Log.d(TAG, "The field '" + key + "' was not retrieved from the bundle");
                    }
                }
            }
            clazz = clazz.getSuperclass();
        }
    }

}

استخدام المثال:

public class MainActivity extends Activity {

    @SaveInstance
    private String foo;

    @SaveInstance
    private int bar;

    @SaveInstance
    private Intent baz;

    @SaveInstance
    private boolean qux;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Icicle.load(savedInstanceState, this);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Icicle.save(outState, this);
    }

}

ملاحظة: تم تعديل هذا الرمز من مشروع مكتبة باسم AndroidAutowire مرخص بموجب ترخيص معهد ماساتشوستس للتكنولوجيا .


هنا تعليق من جواب ستيف موسلي (بواسطة ToolmakerSteve ) يضع الأمور في نصابها (في كامل onSaveInstanceState مقابل onPause ، التكلفة الشرقية مقابل ملحمة التكلفة الغربية)

VVK - أنا لا أوافق جزئيًا. لا يتم تشغيل بعض الطرق للخروج من تطبيق onSaveInstanceState (oSIS). هذا يحد من فائدة OSIS. يستحق دعمه ، للحصول على الحد الأدنى من موارد نظام التشغيل ، ولكن إذا كان التطبيق يريد إرجاع المستخدم إلى الحالة التي كان عليها ، بغض النظر عن كيفية خروج التطبيق ، فمن الضروري استخدام نهج التخزين الثابت بدلاً من ذلك. أستخدم onCreate للتحقق من وجود حزمة ، وإذا كانت مفقودة ، فافحص وحدة التخزين الدائمة. هذا يبرمج اتخاذ القرار. يمكنني التعافي من التعطل ، أو الخروج من زر الرجوع أو عنصر القائمة المخصص Exit ، أو الرجوع إلى مستخدم الشاشة بعد عدة أيام. - ToolmakerSteve Sep 19 '15 at 10:38





application-state