सहेजें स्थिति का उपयोग करके गतिविधि स्थिति को कैसे सहेजा जाए?


2620

मैं एंड्रॉइड एसडीके प्लेटफॉर्म पर काम कर रहा हूं, और यह थोड़ा अस्पष्ट है कि किसी एप्लिकेशन की स्थिति को कैसे बचाया जाए। इसलिए 'हैलो, एंड्रॉइड' उदाहरण का यह मामूली री-टूलिंग दिया गया है:

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 मिनट या तो के लिए प्रलेखन में दूर रहा हूं और कुछ भी स्पष्ट नहीं मिला है।


9
कब सहेजा गया है InstanceState == null और कब यह null नहीं है?
Trojan.ZBOT

90
आप स्पष्ट रूप से अपनी गतिविधि को नष्ट कर रहे हैं - जैसा कि आपने कहा, इससे दूर नेविगेट करना, जैसे कि वापस दबाकर। दरअसल, जिस परिदृश्य में इस 'saveInstanceState' का उपयोग किया जाता है, जब Android मनोरंजन के लिए आपकी गतिविधि को नष्ट कर देता है। उद्देश्य के लिए: यदि आप गतिविधि चलाते समय अपने फोन की भाषा को बदल देते हैं (और इसलिए आपके प्रोजेक्ट से विभिन्न संसाधनों को लोड करने की आवश्यकता है)। एक और बहुत ही सामान्य परिदृश्य है जब आप अपने फोन को साइड में घुमाते हैं ताकि गतिविधि फिर से दिखाई दे और परिदृश्य में प्रदर्शित हो।
खलनायक

16
दूसरा संदेश प्राप्त करने के लिए, देव विकल्पों में "गतिविधियों को न रखें" सक्षम करें। एक होम बटन दबाएं और रिकेट्स से वापस आ जाएं।
यारोस्लाव Mytkalyk

5
यह काफी मददगार है। डेवलपर
सैयद रज़ा मेहदी

6
आप इसके साथ कर सकते हैं: onSaveInstanceState (बंडल
सेव किया गया InstanceState

जवाबों:


2568

आपको 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");
}

या एक टुकड़े से।

@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
    super.onViewStateRestored(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");
}

आप आमतौर पर इस तकनीक का उपयोग अपने एप्लिकेशन के लिए इंस्टेंस मान (चयन, बिना सहेजे गए पाठ इत्यादि) के लिए करते हैं।


24
कोई भी मौका यह फोन पर काम करता है, लेकिन एमुलेटर में नहीं? मैं एक गैर-शून्य सहेजा गया प्रतीत नहीं कर सकता। InstanceState।
एडम जैक १

491
CAREFUL: बंडल में अपने मान जोड़ने से पहले आपको super.onSaveInstanceState (saveInstanceState) पर कॉल करने की आवश्यकता है, या वे उस कॉल (Droid X Android 2.2) पर मिटा देंगे।
18 जुलाई को jkschneider

121
सावधान: आधिकारिक दस्तावेज़ीकरण में कहा गया है, कि आपको ऑनपॉज़-मेथड के भीतर महत्वपूर्ण जानकारी को सहेजना चाहिए क्योंकि ऑनसेंस्टेंस-विधि एंड्रॉइड जीवनचक्र का हिस्सा नहीं है। developer.android.com/reference/android/app/Activity.html
schlingel

32
यह तथ्य onSaveInstanceStateकेवल स्क्रीन ओरिएंटेशन परिवर्तनों के मामले को छोड़कर प्रभावी रूप से बेकार बना देता है । लगभग सभी अन्य मामलों में, आप कभी भी इस पर भरोसा नहीं कर सकते हैं और मैन्युअल रूप से अपने UI स्थिति को कहीं और सहेजने की आवश्यकता होगी। या BACK बटन व्यवहार को ओवरराइड करके अपने ऐप को मारे जाने से रोकना। मुझे समझ में नहीं आता कि उन्होंने इसे पहली जगह पर इस तरह क्यों लागू किया। पूरी तरह से अनपेक्षित। और आपके पास वह बंडल नहीं हो सकता है जो आपको इस विशेष पद्धति को छोड़कर चीजों को बचाने के लिए सिस्टम देता है।
चक्र

12
ध्यान दें कि बंडल से / से UI स्थिति को सहेजना / पुनर्स्थापित करना स्वचालित रूप से उस एस के लिएView ध्यान रखा जाता है जिसे आईडी निर्दिष्ट किया गया है । से onSaveInstanceStateडॉक्स: "डिफ़ॉल्ट कार्यान्वयन को फोन करके आप के लिए यूआई प्रति-उदाहरण के राज्य के सबसे का ख्याल रखता है onSaveInstanceState()पदानुक्रम एक आईडी है, में प्रत्येक दृश्य पर और की आईडी को सहेज कर वर्तमान में ध्यान केंद्रित (जो सभी पुनर्स्थापित किया जाता है दृश्य के डिफ़ॉल्ट कार्यान्वयन द्वारा onRestoreInstanceState(Bundle)"
विक्की चिजवानी

433

savedInstanceStateकेवल एक गतिविधि का एक वर्तमान उदाहरण के साथ जुड़े राज्य बचत, उदाहरण के वर्तमान नेविगेशन या चयन की जानकारी के लिए के लिए है, इसलिए है कि अगर एंड्रॉयड नष्ट कर देता है और एक गतिविधि का पुनर्निर्माण करने वाला है, यह वापस आ सकते हैं के रूप में यह पहले था। के लिए दस्तावेज़ देखें onCreateऔरonSaveInstanceState

अधिक लंबे समय तक रहने वाले राज्य के लिए, एक SQLite डेटाबेस, एक फ़ाइल या वरीयताओं का उपयोग करने पर विचार करें। सेविंग परसेंट स्टेट देखें ।


3
कब सहेजा गया है InstanceState == null और कब यह null नहीं है?
Trojan.ZBOT

6
saveInstanceState शून्य है जब सिस्टम आपकी गतिविधि का एक नया उदाहरण बना रहा है और जब यह बहाल हो रहा है तो शून्य नहीं है।
गेब्रियल सेमार

6
... जो सवाल उठाता है कि सिस्टम को गतिविधि का एक नया उदाहरण कब बनाने की आवश्यकता है। ऐप से बाहर निकलने के कुछ तरीके एक बंडल नहीं बनाते हैं, इसलिए एक नया उदाहरण बनाया जाना चाहिए। यह मूलभूत समस्या है; इसका मतलब है कि कोई भी बंडल के अस्तित्व पर भरोसा नहीं कर सकता है, और लगातार भंडारण के कुछ वैकल्पिक साधन करना चाहिए। OnSave / onRestoreInstanceState का लाभ यह है कि यह एक तंत्र है जो सिस्टम को अचानक से कर सकता है , बिना ज्यादा सिस्टम संसाधनों का उपभोग किए। इसलिए इसका समर्थन करना अच्छा है, साथ ही ऐप से अधिक सुंदर निकास के लिए लगातार भंडारण है।
टूलमेकरसैट

415

ध्यान दें कि http://developer.android.com/reference/android/app/Activity.html पर गतिविधि राज्यों में प्रलेखन के अनुसार, इसका उपयोग करना और लगातार डेटा के लिए सुरक्षित नहीं हैonSaveInstanceStateonRestoreInstanceState

दस्तावेज़ बताता है ('गतिविधि जीवनचक्र' अनुभाग में):

ध्यान दें कि इसके onPause()बजाय लगातार डेटा को सहेजना महत्वपूर्ण है onSaveInstanceState(Bundle) क्योंकि बाद में जीवनचक्र कॉलबैक का हिस्सा नहीं है, इसलिए इसे हर स्थिति में नहीं बुलाया जाएगा जैसा कि इसके प्रलेखन में वर्णित है।

दूसरे शब्दों में, में लगातार डेटा के लिए अपनी बचाने / बहाल कोड डाल onPause()और onResume()!

संपादित करें : आगे स्पष्टीकरण के लिए, यहाँ onSaveInstanceState()प्रलेखन है:

किसी गतिविधि को मारने से पहले इस विधि को कहा जाता है ताकि भविष्य में कुछ समय वापस आने पर यह अपनी स्थिति को बहाल कर सके। उदाहरण के लिए, यदि गतिविधि ए के सामने गतिविधि बी लॉन्च की गई है, और कुछ बिंदु पर गतिविधि ए को संसाधनों को पुनः प्राप्त करने के लिए मार दिया जाता है, तो गतिविधि ए में इस विधि के माध्यम से अपने उपयोगकर्ता इंटरफ़ेस की वर्तमान स्थिति को बचाने का मौका होगा ताकि उपयोगकर्ता वापस आ जाए गतिविधि ए के लिए, उपयोगकर्ता इंटरफ़ेस की स्थिति को इसके माध्यम से onCreate(Bundle)या बहाल किया जा सकता है onRestoreInstanceState(Bundle)


55
जस्ट टू नाइटपिक: यह असुरक्षित भी नहीं है। यह सिर्फ इस बात पर निर्भर करता है कि आप क्या संरक्षित करना चाहते हैं और कितनी देर तक, जो @Bernard अपने मूल प्रश्न पर पूरी तरह से स्पष्ट नहीं है। InstanceState वर्तमान UI स्थिति को संरक्षित करने के लिए एकदम सही है (डेटा नियंत्रण में सूचीबद्ध है, सूचियों में वर्तमान स्थिति और इसके बाद), जबकि पॉज़ / रिज्यूम दीर्घकालिक भंडारण के लिए एकमात्र संभावना है।
पोंटस गैगे

30
इसे डाउनवोट किया जाना चाहिए। यह सुरक्षित करने के लिए सुरक्षित नहीं है (सेव | रिस्टोर) इंस्टेंसस्टेट को लाइक साइकिल विधियों की तरह (अर्थात राज्य को बचाने / पुनः स्थापित करने की तुलना में उनमें कुछ और करें)। वे राज्य को बचाने / बहाल करने के लिए पूरी तरह से अच्छे हैं। इसके अलावा, आप onPause और onResume में राज्य को कैसे सहेजना / पुनर्स्थापित करना चाहते हैं? आपको उन विधियों में बंडल नहीं मिलते हैं, जिनका आप उपयोग कर सकते हैं, इसलिए आपको डेटाबेस, फ़ाइलों आदि में कुछ अन्य राज्य-बचत को नियोजित करना होगा, जो कि मूर्खतापूर्ण है।
फेलिक्स

141
हमें इस व्यक्ति को वोट नहीं देना चाहिए, कम से कम उसने प्रलेखन के माध्यम से जाने के प्रयास किए और मुझे लगता है कि हम लोग वास्तव में एक जानकार समुदाय का निर्माण करने के लिए यहां हैं और एक-दूसरे की मदद नहीं करते हैं। इसलिए 1 प्रयास के लिए वोट करें और मैं आप लोगों से अनुरोध करूंगा कि वोट डाउन न करें बल्कि वोट दें या वोट न करें .... इस व्यक्ति ने भ्रम को दूर कर दिया है कि जब कोई दस्तावेज के माध्यम से जाना चाहेगा। 1 वोट :)
AZ_

21
मुझे नहीं लगता कि यह उत्तर एक पतन का पात्र है। कम से कम उसने जवाब देने का प्रयास किया और डोको से एक भाग उद्धृत किया।
4

34
यह उत्तर बिलकुल सही है और यूपी वोट के योग्य है, नीचे नहीं! मुझे उन लोगों के लिए राज्यों के बीच अंतर स्पष्ट करने दें जो इसे नहीं देखते हैं। एक GUI राज्य, चयनित रेडियो-बटन और इनपुट क्षेत्र के कुछ पाठ की तरह, डेटा स्थिति की तुलना में बहुत कम महत्वपूर्ण है, जैसे सूची में प्रदर्शित सूची में जोड़े गए रिकॉर्ड। बाद वाले को ऑनपॉज़ में डेटाबेस में संग्रहीत किया जाना चाहिए क्योंकि यह एकमात्र गारंटीकृत कॉल है। यदि आप इसे onSaveInstanceState में रखते हैं, तो आप डेटा को खोने का जोखिम उठाते हैं यदि इसे नहीं कहा जाता है। लेकिन अगर रेडियो-बटन का चयन उसी कारण से नहीं बचा है - यह कोई बड़ी बात नहीं है।
JBM

206

मेरे सहयोगी एक लेख गतिविधि जीवन चक्र और राज्य सूचना पर स्पष्टीकरण, कैसे राज्य जानकारी संग्रहीत करने के सहित Android उपकरणों पर आवेदन राज्य समझा लिखा था, और राज्य के लिए बचत Bundleऔर SharedPreferencesऔर यहां पर एक नज़र डालें

लेख में तीन दृष्टिकोण शामिल हैं:

इंस्टेंस स्टेट बंडल का उपयोग करके जीवन भर (यानी अस्थायी रूप से) एप्लीकेशन के लिए लोकल वेरिएबल / यूआई कंट्रोल डेटा स्टोर करें

[Code sample  Store state in state bundle]
@Override
public void onSaveInstanceState(Bundle savedInstanceState)
{
  // Store UI state to the savedInstanceState.
  // This bundle will be passed to onCreate on next call.  EditText txtName = (EditText)findViewById(R.id.txtName);
  String strName = txtName.getText().toString();

  EditText txtEmail = (EditText)findViewById(R.id.txtEmail);
  String strEmail = txtEmail.getText().toString();

  CheckBox chkTandC = (CheckBox)findViewById(R.id.chkTandC);
  boolean blnTandC = chkTandC.isChecked();

  savedInstanceState.putString(“Name”, strName);
  savedInstanceState.putString(“Email”, strEmail);
  savedInstanceState.putBoolean(“TandC”, blnTandC);

  super.onSaveInstanceState(savedInstanceState);
}

साझा प्राथमिकताओं का उपयोग करके एप्लिकेशन इंस्टेंस (यानी स्थायी रूप से) के बीच स्थानीय चर / यूआई नियंत्रण डेटा स्टोर करें

[Code sample  store state in SharedPreferences]
@Override
protected void onPause()
{
  super.onPause();

  // Store values between instances here
  SharedPreferences preferences = getPreferences(MODE_PRIVATE);
  SharedPreferences.Editor editor = preferences.edit();  // Put the values from the UI
  EditText txtName = (EditText)findViewById(R.id.txtName);
  String strName = txtName.getText().toString();

  EditText txtEmail = (EditText)findViewById(R.id.txtEmail);
  String strEmail = txtEmail.getText().toString();

  CheckBox chkTandC = (CheckBox)findViewById(R.id.chkTandC);
  boolean blnTandC = chkTandC.isChecked();

  editor.putString(“Name”, strName); // value to store
  editor.putString(“Email”, strEmail); // value to store
  editor.putBoolean(“TandC”, blnTandC); // value to store
  // Commit to storage
  editor.commit();
}

एक गैर-कॉन्फ़िगरेशन इंस्टेंस का उपयोग करके अनुप्रयोग जीवनकाल के भीतर गतिविधियों के बीच स्मृति में ऑब्जेक्ट इंस्टेंस को जीवित रखना

[Code sample  store object instance]
private cMyClassType moInstanceOfAClass; // Store the instance of an object
@Override
public Object onRetainNonConfigurationInstance()
{
  if (moInstanceOfAClass != null) // Check that the object exists
      return(moInstanceOfAClass);
  return super.onRetainNonConfigurationInstance();
}

3
@ मार्टिनबेल्चर-ईगो आर्टिकल ने शेयर्डप्रिफरेंस में डेटा के बारे में कहा है कि "यह डेटा डिवाइस पर डेटाबेस को लिखा जाता है .." मेरा मानना ​​है कि डेटा फ़ाइल में फ़ाइल सिस्टम की ऐप डायरेक्ट्री में संग्रहित है।
टॉम

2
@Tom SharefPrefs डेटा को xml फ़ाइल में लिखा गया है। क्या xml एक तरह का डेटाबेस है? मैं कहूंगा कि यह है;)
मैकियाजॉर्स्की

148

यह एंड्रॉइड डेवलपमेंट का एक क्लासिक 'गोचा' है। यहां दो समस्याएं हैं:

  • एक सूक्ष्म एंड्रॉइड फ्रेमवर्क बग है जो विकास के दौरान अनुप्रयोग स्टैक प्रबंधन को बहुत जटिल करता है, कम से कम विरासत संस्करणों पर (पूरी तरह से निश्चित है कि कब / कब / कैसे तय किया गया था)। मैं नीचे इस बग पर चर्चा करूंगा।
  • इस मुद्दे को प्रबंधित करने का 'सामान्य' या इच्छित तरीका है, स्वयं, बल्कि onPause / onResume और onSaveInstanceState / onRestoreInstanceState के द्वंद्व से जटिल है

इन सभी थ्रेड्स को ब्राउज़ करते हुए, मुझे संदेह है कि बहुत सारे डेवलपर्स एक साथ इन दो अलग-अलग मुद्दों के बारे में बात कर रहे हैं ... इसलिए "यह मेरे लिए काम नहीं करता है" की सभी भ्रम और रिपोर्ट।

सबसे पहले, 'इच्छित' व्यवहार को स्पष्ट करने के लिए: onSaveInstance और onRestoreInstance नाजुक हैं और केवल क्षणिक स्थिति के लिए हैं। जब फोन घुमाया जाता है (अभिविन्यास परिवर्तन) गतिविधि का उपयोग करने के लिए इरादा उपयोग (afaict) है। दूसरे शब्दों में, इच्छित उपयोग तब है जब आपकी गतिविधि अभी भी तार्किक रूप से 'शीर्ष पर' है, लेकिन फिर भी सिस्टम द्वारा पुनः स्थापित किया जाना चाहिए। सहेजे गए बंडल को प्रक्रिया / मेमोरी / gc के बाहर जारी नहीं रखा जाता है, इसलिए यदि आपकी गतिविधि पृष्ठभूमि पर जाती है, तो आप वास्तव में इस पर भरोसा नहीं कर सकते। हां, शायद आपकी गतिविधि की स्मृति पृष्ठभूमि और जीसी से बचने के लिए अपनी यात्रा से बच जाएगी, लेकिन यह विश्वसनीय नहीं है (न ही यह पूर्वानुमान है)।

इसलिए यदि आपके पास एक ऐसा परिदृश्य है जहां सार्थक 'उपयोगकर्ता प्रगति' या स्थिति है जिसे आपके आवेदन के 'लॉन्च' के बीच बनाए रखा जाना चाहिए, तो मार्गदर्शन ऑनपॉज और onResume का उपयोग करना है। आपको स्वयं एक स्थायी स्टोर चुनना और तैयार करना होगा।

लेकिन - एक बहुत भ्रामक बग है जो इस सब को जटिल करता है। विवरण यहाँ हैं:

http://code.google.com/p/android/issues/detail?id=2373

http://code.google.com/p/android/issues/detail?id=5277

मूल रूप से, यदि आपका आवेदन सिंगलटैग ध्वज के साथ लॉन्च किया गया है, और फिर बाद में आप इसे होम स्क्रीन या लॉन्चर मेनू से लॉन्च करते हैं, तो बाद में मंगलाचरण एक नया कार्य करेगा ... आपके पास प्रभावी रूप से आपके ऐप के दो अलग-अलग उदाहरण होंगे उसी ढेर में बसा हुआ ... जो बहुत जल्दी बहुत अजीब हो जाता है। ऐसा तब लगता है जब आप विकास के दौरान अपना ऐप लॉन्च करते हैं (जैसे कि एक्लिप्स या इंटेलीज से), इसलिए डेवलपर्स इसमें बहुत भाग लेते हैं। लेकिन ऐप स्टोर अपडेट तंत्र में से कुछ के माध्यम से भी (इसलिए यह आपके उपयोगकर्ताओं को भी प्रभावित करता है)।

मैंने इन थ्रेड्स के माध्यम से घंटों तक लड़ाई की, इससे पहले कि मुझे एहसास हुआ कि मेरा मुख्य मुद्दा यह बग था, न कि इच्छित रूपरेखा व्यवहार। एक महान लेखन औरवैकल्पिक हल (अद्यतन: नीचे देखें) इस उत्तर में उपयोगकर्ता @kaciula से प्रतीत होता है:

घर कुंजी प्रेस व्यवहार

अद्यतन जून 2013 : महीनों बाद, मैंने अंत में 'सही' समाधान खोज लिया है। आपको किसी भी राज्य के शुरू किए गए फ्लैगशिप फ्लैग को प्रबंधित करने की आवश्यकता नहीं है, आप इसे रूपरेखा से और उचित रूप से जमानत कर सकते हैं। मैं अपने LauncherActivity.onCreate की शुरुआत के पास इसका उपयोग करता हूं:

if (!isTaskRoot()) {
    Intent intent = getIntent();
    String action = intent.getAction();
    if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && action != null && action.equals(Intent.ACTION_MAIN)) {
        finish();
        return;
    }
}

87

onSaveInstanceStateकहा जाता है जब सिस्टम को मेमोरी की आवश्यकता होती है और एक एप्लिकेशन को मारता है। यह तब नहीं कहा जाता है जब उपयोगकर्ता एप्लिकेशन को बंद कर देता है। इसलिए मुझे लगता है कि एप्लीकेशन स्टेट को भी onPauseइसमें सेव किया जाना चाहिए, जैसे Preferencesया कुछ लगातार स्टोरेज में सेव किया जाना चाहिएSqlite


36
क्षमा करें, यह काफी सही नहीं है। गतिविधि को फिर से बनाने से पहले onSaveInstanceState को कॉल किया जाता है। यानी हर बार जब उपयोगकर्ता डिवाइस को घुमाता है। यह क्षणिक दृश्य राज्यों को संग्रहीत करने के लिए है। जब एंड्रॉइड एप्लिकेशन को बंद करने के लिए मजबूर करता है, तो onSaveInstanceState वास्तव में नहीं बुलाया जाता है (यही कारण है कि यह महत्वपूर्ण एप्लिकेशन डेटा संग्रहीत करने के लिए असुरक्षित है)। हालाँकि, गतिविधि समाप्त होने से पहले कॉल करने की गारंटी दी जाती है, इसलिए इसका उपयोग प्राथमिक सूचना या प्राथमिकताओं में स्थायी जानकारी संग्रहीत करने के लिए किया जाना चाहिए। सही उत्तर, गलत कारण।
मूवेवेय

74

दोनों विधियाँ उपयोगी और मान्य हैं और दोनों विभिन्न परिदृश्यों के लिए सबसे उपयुक्त हैं:

  1. उपयोगकर्ता एप्लिकेशन को समाप्त कर देता है और बाद की तारीख में इसे फिर से खोलता है, लेकिन एप्लिकेशन को अंतिम सत्र से डेटा को फिर से लोड करने की आवश्यकता होती है - इसके लिए एक निरंतर भंडारण दृष्टिकोण की आवश्यकता होती है जैसे कि SQLite का उपयोग करना।
  2. उपयोगकर्ता एप्लिकेशन को स्विच करता है और फिर मूल में वापस आ जाता है और जहां वे छोड़ते हैं, वहां चुनना चाहते हैं - बंडल डेटा (जैसे एप्लिकेशन स्टेट डेटा) को सहेजें और पुनर्स्थापित करें onSaveInstanceState()और onRestoreInstanceState()आमतौर पर पर्याप्त है।

आप एक लगातार ढंग से राज्य डेटा सहेजते हैं, तो यह एक में पुनः लोड किया जा सकता है onResume()या onCreate()(किसी भी जीवन चक्र फोन पर या वास्तव में)। यह वांछित व्यवहार हो भी सकता है और नहीं भी। यदि आप इसे एक बंडल में स्टोर करते हैं InstanceState, तो यह क्षणिक है और केवल उसी उपयोगकर्ता 'सत्र' में उपयोग के लिए डेटा संग्रहीत करने के लिए उपयुक्त है (मैं सत्र का उपयोग शिथिल रूप से करता हूं) लेकिन 'सत्र' के बीच नहीं।

ऐसा नहीं है कि एक दृष्टिकोण हर चीज की तरह दूसरे से बेहतर है, यह समझना महत्वपूर्ण है कि आपको किस व्यवहार की आवश्यकता है और सबसे उपयुक्त दृष्टिकोण का चयन करना है।


70

जहाँ तक मेरा सवाल है, बचत राज्य एक कीचड़ है। यदि आपको लगातार डेटा को बचाने की आवश्यकता है, तो बस एक SQLite डेटाबेस का उपयोग करें। Android इसे 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;

9
क्योंकि एक SQLite डेटाबेस को लोड करने में बहुत लंबा समय लगता है, यह देखते हुए कि यह उपयोगकर्ता को ऐप के UI को दिखाने के लिए महत्वपूर्ण पथ पर है। मैंने वास्तव में इसे समयबद्ध नहीं किया है, इसलिए मैं सही होने के लिए खुश हूं, लेकिन निश्चित रूप से डेटाबेस फ़ाइल को लोड करना और खोलना तेज नहीं होगा?
टॉम

5
एक समाधान प्रदान करने के लिए बहुत बहुत धन्यवाद एक नौसिखिया काट सकते हैं और अपने ऐप में पेस्ट कर सकते हैं और तुरंत उपयोग कर सकते हैं! @Tom जहाँ तक गति जाती है 1000 जोड़े को स्टोर करने में लगभग सात सेकंड लगते हैं, लेकिन आप इसे AsyncTask में कर सकते हैं। हालाँकि, आपको अंत में {कर्सर.क्लोज ()} जोड़ना होगा या ऐसा करते समय यह मेमोरी लीक से दुर्घटनाग्रस्त हो जाएगा।
नौमेनन

3
मैं इस पर आया था और जब यह साफ-सुथरा लगता है तो मैं गूगल ग्लास पर इसका उपयोग करने में संकोच कर रहा हूं, जो कि मैं जिस उपकरण पर काम कर रहा हूं / हाल ही में।
स्टीफन टेट्रौल्ट

61

मुझे लगता है कि मुझे जवाब मिल गया। मुझे बताएं कि मैंने सरल शब्दों में क्या किया है:

मान लीजिए कि मेरे पास दो गतिविधियाँ, गतिविधि 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 को उसी स्थिति में देख सकते हैं जब हमने पहले छोड़ दिया था।

मेरा मानना ​​है कि यह उत्तर है और यह मेरे लिए ठीक काम करता है। अगर मैं ग़लत हूं तो मेरी गलती सुझाएं।


2
@bagusflyer देखभाल अधिक विशिष्ट होना चाहिए ??? आपकी टिप्पणी उपयोगी नहीं है और कोई भी उस पर आधारित आपकी मदद नहीं कर सकता है।
स्टीफन टेट्रौल्ट

2
यह एक अलग स्थिति का जवाब है: एक ही ऐप के भीतर दो गतिविधियां। ओपी ऐप छोड़ने के बारे में है (उदाहरण के लिए होम बटन, या किसी अन्य ऐप पर स्विच करने के लिए अन्य साधन)।
टूलमेकरसेव

44

onSaveInstanceState()क्षणिक डेटा के लिए ( onCreate()/ में बहाल onRestoreInstanceState()), onPause()लगातार डेटा (बहाल onResume()) के लिए। Android तकनीकी संसाधनों से:

onSaveInstanceState () को Android द्वारा बुलाया जाता है यदि गतिविधि को रोका जा रहा है और फिर से शुरू होने से पहले उसे मार दिया जा सकता है! इसका मतलब यह है कि किसी भी स्थिति को उसी स्थिति में फिर से शुरू करने के लिए आवश्यक होना चाहिए जब गतिविधि फिर से शुरू हो। यह onCreate () विधि का प्रतिरूप है, और वास्तव में saveInstanceState बंडल onCreate () में पास हुआ वही बंडल है जिसे आप onSaveInstanceState () विधि में आउटस्टेट के रूप में बनाते हैं।

onPause () और onResume () भी मानार्थ तरीके हैं। onPause () को हमेशा कहा जाता है जब गतिविधि समाप्त हो जाती है, भले ही हमने उसे उकसाया हो (उदाहरण के लिए () कॉल के साथ)। हम इसका उपयोग वर्तमान नोट को डेटाबेस में वापस सहेजने के लिए करेंगे। अच्छा अभ्यास किसी भी संसाधनों को जारी करना है जो निष्क्रिय अवस्था में होने पर कम संसाधनों को लेने के लिए ऑनपॉज़ () के दौरान जारी किया जा सकता है।


40

onSaveInstanceState()जब गतिविधि पृष्ठभूमि में जाती है तो वास्तव में कहा जाता है।

डॉक्स से उद्धरण: "इस विधि को किसी गतिविधि को मारने से पहले बुलाया जाता है ताकि भविष्य में कुछ समय वापस आने पर यह अपने राज्य को बहाल कर सके।" स्रोत


37

बॉयलरप्लेट को कम करने में मदद करने के लिए मैं निम्नलिखित का उपयोग करता हूं 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 नाम के एक लाइब्रेरी प्रोजेक्ट से अनुकूलित किया गया था जो MIT लाइसेंस के तहत लाइसेंस प्राप्त है ।


34

इस बीच मैं सामान्य रूप से अधिक उपयोग नहीं करता हूं

Bundle savedInstanceState & Co

जीवन चक्र अधिकांश गतिविधियों के लिए बहुत जटिल है और आवश्यक नहीं है।

और Google स्वयं कहता है, यह विश्वसनीय भी नहीं है।

मेरा तरीका वरीयताओं में किसी भी परिवर्तन को तुरंत सहेजना है:

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

किसी तरह से SharedPreferences बंडलों की तरह काम करते हैं। और स्वाभाविक रूप से और पहले ऐसे मूल्यों को वरीयताओं से पढ़ा जाना चाहिए।

जटिल डेटा के मामले में आप वरीयताओं का उपयोग करने के बजाय SQLite का उपयोग कर सकते हैं।

इस अवधारणा को लागू करते समय, गतिविधि बस अंतिम बची हुई स्थिति का उपयोग करना जारी रखती है, चाहे वह बीच में रिबूट के साथ एक प्रारंभिक खुला था या बैक स्टैक के कारण फिर से खुल गया हो।


31

सीधे मूल प्रश्न का उत्तर देने के लिए। saveInstancestate शून्य है क्योंकि आपकी गतिविधि कभी भी दोबारा नहीं बनाई जा रही है।

आपकी गतिविधि केवल तभी एक राज्य बंडल के साथ बनाई जाएगी जब:

  • कॉन्फ़िगरेशन में परिवर्तन जैसे कि अभिविन्यास या फोन की भाषा को बदलना जो एक नई गतिविधि की आवश्यकता हो सकती है।
  • OS की गतिविधि को नष्ट करने के बाद आप बैकग्राउंड से ऐप पर लौटते हैं।

स्मृति के दबाव में या बाद में समय की विस्तारित अवधि के लिए पृष्ठभूमि में होने के बाद Android पृष्ठभूमि गतिविधियों को नष्ट कर देगा।

अपने हैलो वर्ल्ड उदाहरण का परीक्षण करते समय गतिविधि को छोड़ने और वापस जाने के कुछ तरीके हैं।

  • जब आप बैक बटन दबाते हैं तो गतिविधि समाप्त हो जाती है। ऐप को फिर से लॉन्च करना एक नया उदाहरण है। आप पृष्ठभूमि से फिर से शुरू नहीं कर रहे हैं।
  • जब आप होम बटन दबाते हैं या कार्य स्विचर का उपयोग करते हैं तो गतिविधि पृष्ठभूमि में जाएगी। जब आवेदन पर वापस नेविगेट करना होगा तभी कहा जाएगा जब गतिविधि को नष्ट कर दिया गया था।

ज्यादातर मामलों में अगर आप सिर्फ घर पर दबाव डाल रहे हैं और फिर ऐप को फिर से लॉन्च कर रहे हैं, तो गतिविधि को फिर से बनाने की आवश्यकता नहीं होगी। यह पहले से ही मेमोरी में मौजूद है इसलिए ऑनक्रीट () नहीं कहा जाएगा।

सेटिंग्स के तहत एक विकल्प है -> डेवलपर विकल्प जिसे "गतिविधियां न रखें" कहा जाता है। जब यह सक्षम हो जाता है तो एंड्रॉइड हमेशा गतिविधियों को नष्ट कर देगा और जब वे पृष्ठभूमि में होंगे तो उन्हें पुनः बनाएंगे। यह विकासशील होने पर सक्षम छोड़ने के लिए एक बढ़िया विकल्प है क्योंकि यह सबसे खराब स्थिति का अनुकरण करता है। (एक कम मेमोरी डिवाइस हर समय आपकी गतिविधियों को रीसाइक्लिंग करता है)।

अन्य उत्तर इस मायने में मूल्यवान हैं कि वे आपको राज्य स्टोर करने के सही तरीके सिखाते हैं, लेकिन मुझे नहीं लगा कि उन्होंने वास्तव में उत्तर दिया कि आपका कोड आपकी अपेक्षा के अनुरूप काम नहीं कर रहा था।


28

onSaveInstanceState(bundle)और onRestoreInstanceState(bundle)तरीकों डेटा हठ स्क्रीन (उन्मुखीकरण परिवर्तन) घूर्णन केवल थोड़ी देर के लिए उपयोगी होते हैं।
अनुप्रयोगों के बीच स्विच करते समय वे भी अच्छे नहीं हैं (क्योंकि onSaveInstanceState()विधि को कॉल किया जाता है onCreate(bundle)और onRestoreInstanceState(bundle)फिर से लागू नहीं किया जाता है।
अधिक दृढ़ता से उपयोग के लिए साझा प्राथमिकताएं इस लेख को पढ़ें


2
आपके मामले में onCreateऔर आपको onRestoreInstanceStateबुलाया नहीं जा रहा है क्योंकि Activityजब आप ऐप्स स्विच करते हैं तो यह बिल्कुल भी नष्ट नहीं होता है, इसलिए कुछ भी पुनर्स्थापित करने की आवश्यकता नहीं है। onSaveInstanceStateयदि गतिविधि बाद में नष्ट हो जाती है, तो एंड्रॉइड कॉल तब होता है (जो स्क्रीन को घुमाते समय 100% निश्चितता के साथ होता है क्योंकि संपूर्ण डिवाइस कॉन्फ़िगरेशन बदल गया है और गतिविधि को फिर से खरोंच से बनाया जाना चाहिए)।
विक्की चिजवानी

20

मेरी समस्या यह थी कि मुझे केवल आवेदन जीवनकाल के दौरान (यानी एक ही निष्पादन जिसमें एक ही ऐप के भीतर अन्य उप-गतिविधियाँ शुरू करने और डिवाइस आदि को घुमाने सहित एक एकल निष्पादन) की आवश्यकता थी। मैंने उपरोक्त उत्तरों के विभिन्न संयोजनों की कोशिश की, लेकिन मुझे वह नहीं मिला, जो मुझे सभी परिस्थितियों में चाहिए था। अंत में मेरे लिए जो काम किया गया था वह ऑनक्रिएट के दौरान saveInstanceState का संदर्भ प्राप्त करना था:

mySavedInstanceState=savedInstanceState;

और उपयोग करें कि मेरे चर की सामग्री प्राप्त करने के लिए जब मुझे इसकी आवश्यकता होती है, की तर्ज पर:

if (mySavedInstanceState !=null) {
   boolean myVariable = mySavedInstanceState.getBoolean("MyVariable");
}

मैं उपयोग करता हूं onSaveInstanceStateऔर onRestoreInstanceStateजैसा कि ऊपर सुझाव दिया गया है, लेकिन मुझे लगता है कि मैं परिवर्तन करने के लिए वैरिएबल को बचाने के लिए या वैकल्पिक रूप से अपने तरीके का उपयोग कर सकता हूं जब यह बदलता है (उदाहरण के लिए putBoolean)


19

यद्यपि स्वीकृत उत्तर सही है, फिर भी आइसपिक नामक एक पुस्तकालय का उपयोग करके एंड्रॉइड पर गतिविधि राज्य को बचाने के लिए एक तेज़ और आसान तरीका है । आइसपिक एक एनोटेशन प्रोसेसर है जो आपके लिए राज्य को बचाने और पुनर्स्थापित करने में उपयोग किए जाने वाले सभी बॉयलरप्लेट कोड का ख्याल रखता है।

आइसपिक के साथ ऐसा कुछ करना:

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");
  }
}

आइसपिक किसी भी वस्तु के साथ काम करेगा जो अपने राज्य को एक के साथ बचाता है Bundle


16

जब कोई गतिविधि बनाई जाती है तो यह ऑनक्रिएट () विधि कहलाती है।

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

saveInstanceState बंडल क्लास का एक ऑब्जेक्ट है जो पहली बार के लिए अशक्त है, लेकिन जब इसे दोबारा बनाया जाता है, तो इसमें मान शामिल होते हैं। गतिविधि की स्थिति को बचाने के लिए आपको onSaveInstanceState () को ओवरराइड करना होगा।

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

अपने मूल्यों को "outState" बंडल ऑब्जेक्ट में डालें जैसे outState.putString ("कुंजी", "वेलकम बैक") और सुपर कॉल करके सहेजें। जब गतिविधि नष्ट हो जाएगी तो यह स्थिति बंडल ऑब्जेक्ट में सहेज ली जाएगी और 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");
    }

15

इस बदलाव को लागू करने के लिए मूल रूप से दो तरीके हैं।

  1. का उपयोग कर onSaveInstanceState()औरonRestoreInstanceState()
  2. प्रकट में android:configChanges="orientation|screenSize"

मैं वास्तव में दूसरी विधि का उपयोग करने की अनुशंसा नहीं करता हूं। चूंकि मेरे एक अनुभव में यह चित्र से परिदृश्य तक और इसके विपरीत घूमते समय डिवाइस स्क्रीन के आधे हिस्से को काला कर रहा था।

ऊपर उल्लिखित पहली विधि का उपयोग करके, हम डेटा को तब बनाए रख सकते हैं जब अभिविन्यास बदल जाता है या कोई भी कॉन्फ़िगर परिवर्तन होता है। मैं एक ऐसा तरीका जानता हूं जिसमें आप किसी भी प्रकार के डेटा को सेव इनस्टांस स्टेट ऑब्जेक्ट के अंदर स्टोर कर सकते हैं।

उदाहरण: एक मामले पर विचार करें यदि आप जसन ऑब्जेक्ट को जारी रखना चाहते हैं। गेटर्स और सेटर्स के साथ एक मॉडल क्लास बनाएं।

class MyModel extends Serializable{
JSONObject obj;

setJsonObject(JsonObject obj)
{
this.obj=obj;
}

JSONObject getJsonObject()
return this.obj;
} 
}

अब onCreate और onSaveInstanceState मेथड में अपनी गतिविधि निम्नलिखित करें। यह कुछ इस तरह दिखेगा:

@override
onCreate(Bundle savedInstaceState){
MyModel data= (MyModel)savedInstaceState.getSerializable("yourkey")
JSONObject obj=data.getJsonObject();
//Here you have retained JSONObject and can use.
}


@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//Obj is some json object 
MyModel dataToSave= new MyModel();
dataToSave.setJsonObject(obj);
oustate.putSerializable("yourkey",dataToSave); 

}

11

यहाँ एक टिप्पणी से है स्टीव मोसली के जवाब (द्वारा ToolmakerSteve ) उस परिप्रेक्ष्य में रखता बातें (पूरे onSaveInstanceState बनाम onPause में, पूर्व लागत बनाम पश्चिम लागत गाथा)

@VVK - मैं आंशिक रूप से असहमत हूं। ऐप से बाहर निकलने के कुछ तरीके onSaveInstanceState (oSIS) को ट्रिगर नहीं करते हैं। यह oSIS की उपयोगिता को सीमित करता है। न्यूनतम ओएस संसाधनों के लिए इसका समर्थन, लेकिन अगर कोई ऐप उपयोगकर्ता को उस स्थिति में लौटना चाहता है जिसमें वे थे, भले ही ऐप कैसे बाहर निकल गया था, इसके बजाय एक निरंतर भंडारण दृष्टिकोण का उपयोग करना आवश्यक है। मैं बंडल के लिए जांच करने के लिए onCreate का उपयोग करता हूं, और यदि यह गायब है, तो लगातार भंडारण की जांच करें यह निर्णय लेने को केंद्रीकृत करता है। मैं एक क्रैश, या बैक बटन से बाहर निकलने या कस्टम मेनू आइटम से बाहर निकल सकता हूं, या स्क्रीन उपयोगकर्ता पर वापस जा सकता हूं बहुत दिनों बाद था। - टूलमेकरसेव 19 सितंबर को 10:38 बजे


10

कोटलिन कोड:

सहेजें:

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

यदि आप वैकल्पिक नहीं करना चाहते हैं तो डिफ़ॉल्ट मान जोड़ें


9

में संग्रहीत गतिविधि स्थिति डेटा प्राप्त करने के लिए onCreate(), पहले आपको SaveInstanceState(Bundle savedInstanceState)सिस्टम को ओवरराइडिंग विधि द्वारा saveInstanceState में डेटा सहेजना होगा ।

जब गतिविधि को नष्ट करने की SaveInstanceState(Bundle savedInstanceState)विधि को बुलाया जाता है और वहां आप उस डेटा को सहेजते हैं जिसे आप सहेजना चाहते हैं। और onCreate()जब गतिविधि फिर से शुरू हो, तो आपको वही मिलता है । (saveInstanceState wont null हो सकता है क्योंकि आपने गतिविधि नष्ट होने से पहले उसमें कुछ डेटा सहेजा है)


6

इस समस्या को हल करने के लिए सरल त्वरित IcePick का उपयोग कर रहा है

सबसे पहले, लाइब्रेरी को अंदर सेटअप करें app/build.gradle

repositories {
  maven {url "https://clojars.org/repo/"}
}
dependencies {
  compile 'frankiesardo:icepick:3.2.0'
  provided 'frankiesardo:icepick-processor:3.2.0'
}

अब, गतिविधि में स्थिति को कैसे बचाया जाए, इस उदाहरण को नीचे देखें

public class ExampleActivity extends Activity {
  @State String username; // This will be automatically saved and restored

  @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 CustomView extends View {
  @State int selectedPosition; // This will be automatically saved and restored

  @Override public Parcelable onSaveInstanceState() {
    return Icepick.saveInstanceState(this, super.onSaveInstanceState());
  }

  @Override public void onRestoreInstanceState(Parcelable state) {
    super.onRestoreInstanceState(Icepick.restoreInstanceState(this, state));
  }

  // You can put the calls to Icepick into a BaseCustomView and inherit from it
  // All Views extending this CustomView automatically have state saved/restored
}

1
@ralphspoon हां, यह फ्रेगमेंट और कस्टम व्यू के लिए काम करता है। कृपया उदाहरण कोड की जाँच करें। मैंने अपना उत्तर संपादित किया। मेरा सुझाव है कि अधिक कोड नमूना खोजने के लिए आप यहां आधिकारिक डॉक्स github.com/frankiesardo/icepick पर जाएं।
THANN Phearum

@ चेतनमेहरा आपका मतलब है कस्टम व्यू क्लास, है ना? यदि यह कस्टम दृश्य है, तो हम कस्टम व्यू के ऊपर दिए गए उदाहरण की तरह onSaveInstanceState और onRestoreInstanceState को ओवरराइड कर सकते हैं।
थान फियरम

मेरा मतलब है कि उदाहरण के लिए दृश्य वर्ग के अंदर वर्ग वस्तु: वर्ग CustomView फैली हुई है {@State ClassA a?} देखें या वर्ग CustomView फैली हुई देखें {@ राज्य इनर वर्ग {}}
चेतन मेहरा

@THANNPhearum क्या मुझे इसे एक और प्रश्न के रूप में पूछना चाहिए?
चेतन मेहरा

समझा। यदि हां, तो आपका ClassA Parcelable होना चाहिए। जैसा कि यह उल्लेख किया है कि यह गतिविधियों, टुकड़े या किसी भी वस्तु के लिए काम करता है जिसे अपने राज्य को एक बंडल पर क्रमबद्ध करने की आवश्यकता है
THANN Phearum

6

सुनिश्चित नहीं है कि मेरा समाधान है या नहीं, लेकिन मैं ViewModel स्थिति को बनाए रखने के लिए एक बाध्य सेवा का उपयोग करता हूं। चाहे आप इसे सेवा में मेमोरी में स्टोर करें या इसे SQLite डेटाबेस से बनाए रखें और पुनः प्राप्त करें यह आपकी आवश्यकताओं पर निर्भर करता है। यह किसी भी स्वाद की सेवाएं हैं, वे सेवाएं प्रदान करते हैं जैसे कि आवेदन की स्थिति बनाए रखना और सामान्य व्यापार तर्क।

मोबाइल उपकरणों पर निहित स्मृति और प्रसंस्करण बाधाओं के कारण, मैं एक वेब पेज के समान तरीके से एंड्रॉइड विचारों का इलाज करता हूं। पृष्ठ राज्य को बनाए नहीं रखता है, यह विशुद्ध रूप से एक प्रस्तुति परत घटक है जिसका एकमात्र उद्देश्य अनुप्रयोग स्थिति को प्रस्तुत करना और उपयोगकर्ता इनपुट को स्वीकार करना है। वेब ऐप आर्किटेक्चर में हाल के रुझान पुराने-पुराने मॉडल, व्यू, कंट्रोलर (एमवीसी) पैटर्न का उपयोग करते हैं, जहां पेज व्यू है, डोमेन डेटा मॉडल है, और नियंत्रक एक वेब सेवा के पीछे बैठता है। एंड्रॉइड में व्यू होने के साथ एक ही पैटर्न नियोजित किया जा सकता है, ठीक है ... दृश्य, मॉडल आपका डोमेन डेटा है, और नियंत्रक को एंड्रॉइड बाउंड सेवा के रूप में लागू किया गया है। जब भी आप नियंत्रक के साथ बातचीत करने के लिए एक दृश्य चाहते हैं, तो इसे स्टार्ट / रिज्यूम पर बांधें और स्टॉप / पॉज़ पर अनबाइंड करें।

यह दृष्टिकोण आपको चिंता डिजाइन सिद्धांत के पृथक्करण को लागू करने का अतिरिक्त बोनस देता है, जिसमें आप सभी व्यावसायिक तर्क को आपकी सेवा में स्थानांतरित कर सकते हैं जो कई विचारों में दोहराए गए तर्क को कम करता है और दृश्य को एक अन्य महत्वपूर्ण डिजाइन सिद्धांत, एकल जिम्मेदारी को लागू करने की अनुमति देता है।


5

Kotlin

आपको ओवरराइड करना onSaveInstanceStateऔर onRestoreInstanceStateस्टोर करना और अपने वैरिएबल को पुनः प्राप्त करना चाहिए, जिसे आप लगातार बने रहना चाहते हैं

जीवन चक्र ग्राफ

स्टोर चर

public override fun onSaveInstanceState(savedInstanceState: Bundle) {
    super.onSaveInstanceState(savedInstanceState)

    // prepare variables here
    savedInstanceState.putInt("kInt", 10)
    savedInstanceState.putBoolean("kBool", true)
    savedInstanceState.putDouble("kDouble", 4.5)
    savedInstanceState.putString("kString", "Hello Kotlin")
}

चर निकालें

public override fun onRestoreInstanceState(savedInstanceState: Bundle) {
    super.onRestoreInstanceState(savedInstanceState)

    val myInt = savedInstanceState.getInt("kInt")
    val myBoolean = savedInstanceState.getBoolean("kBool")
    val myDouble = savedInstanceState.getDouble("kDouble")
    val myString = savedInstanceState.getString("kString")
    // use variables here
}

2

अब एंड्रॉइड राज्य को बचाने के लिए ViewModels प्रदान करता है , आपको saveInstanceState के बजाय इसका उपयोग करने का प्रयास करना चाहिए।


3
यह सच नहीं है। प्रलेखन से: "सहेजे गए इंस्टेंस राज्य के विपरीत, सिस्टम-आरंभ की गई प्रक्रिया मृत्यु के दौरान ViewModels नष्ट हो जाते हैं। यही कारण है कि आपको OnSaveInstanceState () (या कुछ अन्य डिस्क दृढ़ता) के साथ संयोजन में ViewModel ऑब्जेक्ट्स का उपयोग करना चाहिए, saveInstanceState में पहचानकर्ताओं को चोरी करने में मदद करने के लिए। मॉडल सिस्टम की मृत्यु के बाद डेटा को पुनः लोड करते हैं। "
व्याचेस्लाव मार्टीनेंको

बस पृष्ठभूमि में परिवर्तन की अनुमति के साथ इस में भाग गया।
ब्रिल पप्पिन

मैं सहमत हूँ, डॉक्टर से "अगर आपको सिस्टम द्वारा शुरू की गई प्रक्रिया की मृत्यु को संभालने की आवश्यकता है, तो आप बैकअप के रूप में onSaveInstanceState () का उपयोग करना चाह सकते हैं।"
जहर

2

किसी भी तरीके को लागू किए बिना राज्यों को बचाने के लिए एंड्रॉइड बनाने का एक तरीका है। गतिविधि घोषणा में अपने घोषणापत्र में इस पंक्ति को जोड़ें:

android:configChanges="orientation|screenSize"

इसे ऐसा दिखना चाहिए:

<activity
    android:name=".activities.MyActivity"
    android:configChanges="orientation|screenSize">
</activity>

यहाँ आप इस संपत्ति के बारे में अधिक जानकारी पा सकते हैं।

यह मैन्युअल रूप से हैंडलिंग की तुलना में आपके लिए एंड्रॉइड को संभालने देने की सिफारिश की गई है।


2
इसका राज्य को बचाने से कोई लेना-देना नहीं है, आप केवल अभिविन्यास में परिवर्तन कर रहे हैं, ध्यान रखें कि आप ऐप को विभिन्न घटनाओं के लिए किसी भी समय फिर से शुरू और रोका जा सकता है और शुरू कर सकते हैं
लॉर्ड-राल्फ-एडोल्फ

1
यह उत्तर उन लोगों के लिए है जो अभिविन्यास में परिवर्तन होने पर राज्य को बचाना चाहते हैं और जटिल तरीके से समझ और कार्यान्वयन से बचना चाहते हैं
इग्नाइटेकोडर्स

काफी हद तक मैं आपकी बात देख रहा हूं, मुझे लगता है कि ज्यादातर लोग जो राज्य को बचाने के लिए संघर्ष करते हैं, वे टुकड़ों का उपयोग कर रहे हैं क्योंकि गतिविधियां वास्तव में यूआई घटकों की प्रतिमा को तब तक बचाती हैं जब तक उनके पास एक आईडी है, लेकिन टुकड़े अधिक विशेष हैं, मैंने एक बार टुकड़ों का उपयोग किया था लेकिन मैं कभी भी उपयोग नहीं करूंगा उनके पास फिर से सेव इंस्टेंस स्टैटिस था, जिससे निपटने के लिए एक दर्द था
लॉर्ड-राल्फ-एडोल्फ

यह काम करता है ... धन्यवाद
फैनडेज़

1

क्या बचाएं और क्या नहीं?

कभी आपने सोचा है कि EditTextओरिएंटेशन चेंज होते समय टेक्स्ट अपने आप क्यों बच जाता है? खैर, यह जवाब आपके लिए है।

जब किसी गतिविधि की एक आवृत्ति नष्ट हो जाती है और सिस्टम एक नया उदाहरण (उदाहरण के लिए, कॉन्फ़िगरेशन परिवर्तन) को पुन: बनाता है। यह पुराने गतिविधि राज्य ( उदाहरण राज्य ) के सहेजे गए डेटा के एक सेट का उपयोग करके इसे फिर से बनाने की कोशिश करता है ।

इंस्टेंस स्टेट किसी ऑब्जेक्ट में संग्रहीत की -वैल्यू पेयर का एक संग्रह है Bundle

डिफ़ॉल्ट रूप से सिस्टम उदाहरण के लिए बंडल में दृश्य ऑब्जेक्ट्स को बचाता है।

  • में पाठ EditText
  • एक ListViewआदि में स्क्रॉल स्थिति

यदि आपको उदाहरण के राज्य के एक भाग के रूप में सहेजे जाने के लिए अन्य चर की आवश्यकता है तो आपको ओवरड्राइड onSavedInstanceState(Bundle savedinstaneState) विधि चाहिए ।

उदाहरण के लिए, int currentScoreगेमएक्टिविटी में

डेटा की बचत करते समय onSavedInstanceState (बंडल saveinstaneState) के बारे में अधिक विवरण

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    // Save the user's current game state
    savedInstanceState.putInt(STATE_SCORE, mCurrentScore);

    // Always call the superclass so it can save the view hierarchy state
    super.onSaveInstanceState(savedInstanceState);
}

तो गलती से अगर आप कॉल super.onSaveInstanceState(savedInstanceState);करना भूल जाते हैं तो डिफॉल्ट व्यवहार काम नहीं करेगा यानी टेक्स्ट इन एडिट टेक्स्ट सेव नहीं करेगा।

गतिविधि राज्य को पुनर्स्थापित करने के लिए कौन सा चयन करना है?

 onCreate(Bundle savedInstanceState)

या

onRestoreInstanceState(Bundle savedInstanceState)

दोनों विधियों में एक ही बंडल ऑब्जेक्ट मिलता है, इसलिए यह वास्तव में कोई फर्क नहीं पड़ता कि आप अपने बहाल तर्क कहां लिखते हैं। अंतर केवल इतना है कि onCreate(Bundle savedInstanceState)विधि में आपको एक शून्य जांच देनी होगी जबकि बाद के मामले में इसकी आवश्यकता नहीं है। अन्य उत्तरों में पहले से ही कोड स्निपेट हैं। आप उनका उल्लेख कर सकते हैं।

OnRestoreInstanceState के बारे में अधिक जानकारी (बंडल saveinstaneState)

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
    // Always call the superclass so it can restore the view hierarchy
    super.onRestoreInstanceState(savedInstanceState);

    // Restore state members from the saved instance
    mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
}

हमेशा कॉल करें super.onRestoreInstanceState(savedInstanceState);ताकि सिस्टम डिफ़ॉल्ट रूप से दृश्य पदानुक्रम को पुनर्स्थापित करे

बक्शीश

onSaveInstanceState(Bundle savedInstanceState)प्रणाली द्वारा लाया ही जब उपयोगकर्ता गतिविधि के लिए वापस आने के लिए करना चाहता है है। उदाहरण के लिए, आप ऐप एक्स का उपयोग कर रहे हैं और अचानक आपको कॉल मिलता है। आप कॉलर ऐप पर जाते हैं और ऐप X पर वापस आते हैं। इस मामले में ऐपonSaveInstanceState(Bundle savedInstanceState) विधि लागू होगी।

लेकिन इस पर विचार करें यदि कोई उपयोगकर्ता बैक बटन दबाता है। यह माना जाता है कि उपयोगकर्ता गतिविधि में वापस आने का इरादा नहीं करता है, इसलिए इस मामले onSaveInstanceState(Bundle savedInstanceState)में सिस्टम द्वारा लागू नहीं किया जाएगा। डेटा सहेजते समय सभी बिंदुओं पर विचार करना चाहिए।

प्रासंगिक लिंक:

डिफ़ॉल्ट व्यवहार पर डेमो
Android आधिकारिक प्रलेखन


1

अब यह दृश्य मॉडल में 2 तरीके करने के लिए समझ में आता है। यदि आप पहली बार सहेजे गए उदाहरण के रूप में सहेजना चाहते हैं: आप इस तरह के दृश्य मॉडल में राज्य पैरामीटर जोड़ सकते हैं https://developer.android.com/topic/lbooks/Healthecture/viewmodel-savedstate#java

या आप दृश्य मॉडल में चर या ऑब्जेक्ट को सहेज सकते हैं, इस मामले में दृश्य मॉडल गतिविधि को नष्ट होने तक जीवन चक्र धारण करेगा।

public class HelloAndroidViewModel extends ViewModel {
   public Booelan firstInit = false;

    public HelloAndroidViewModel() {
        firstInit = false;
    }
    ...
}

public class HelloAndroid extends Activity {

  private TextView mTextView = null;
  HelloAndroidViewModel viewModel = ViewModelProviders.of(this).get(HelloAndroidViewModel.class);
  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mTextView = new TextView(this);

    //Because even if the state is deleted, the data in the viewmodel will be kept because the activity does not destroy
    if(!viewModel.firstInit){
        viewModel.firstInit = true
        mTextView.setText("Welcome to HelloAndroid!");
    }else{
       mTextView.setText("Welcome back.");
    }

    setContentView(mTextView);
  }
}

आप सही हैं, लेकिन यह लाइब्रेरी अभी भी जारी है इसलिए मुझे लगता है कि हमें इंतजार करना चाहिए ...
Zhar

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.