etc./StackOverFlow

인스턴스 상태 저장을 사용하여 활동 상태를 저장하려면 어떻게 해야 합니까?

청렴결백한 만능 재주꾼 2021. 11. 10. 07:35
반응형

질문자 :Bernard


저는 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 분 정도 문서를 파고 들었고 명백한 것을 찾지 못했습니다.



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

일반적으로 이 기술을 사용하여 애플리케이션의 인스턴스 값(선택 항목, 저장되지 않은 텍스트 등)을 저장합니다.


Reto Meier

savedInstanceState 는 활동의 현재 인스턴스와 관련된 상태(예: 현재 탐색 또는 선택 정보)를 저장하기 위한 것이므로 Android가 활동을 파괴하고 다시 생성하면 이전과 같이 돌아올 수 있습니다. onCreateonSaveInstanceState 대한 설명서를 참조하십시오.

더 오래 지속되는 상태를 위해서는 SQLite 데이터베이스, 파일 또는 기본 설정을 사용하는 것이 좋습니다. 영구 상태 저장을 참조하십시오.


Dave L.

Activity 문서에 따르면 영구 데이터에 onSaveInstanceStateonRestoreInstanceState 를 사용하는 것은 안전하지 않습니다 .

문서 상태('활동 수명 주기' 섹션):

onSaveInstanceState(Bundle) ) 대신 onPause() 에 영구 데이터를 저장하는 것이 중요합니다. 나중에는 수명 주기 콜백의 일부가 아니므로 설명서에 설명된 대로 모든 상황에서 호출되지는 않기 때문입니다.

즉, 영구 데이터에 대한 저장/복원 코드를 onPause()onResume() !

자세한 설명을 위해 다음은 onSaveInstanceState() 문서입니다.

이 메서드는 활동이 종료되기 전에 호출되어 나중에 다시 돌아올 때 상태를 복원할 수 있습니다. 예를 들어 액티비티 B가 액티비티 A보다 먼저 실행되고 어떤 시점에서 액티비티 A가 리소스를 회수하기 위해 종료되는 경우 액티비티 A는 이 메서드를 통해 사용자 인터페이스의 현재 상태를 저장할 기회를 갖게 되어 사용자가 돌아올 때 활동 A에 대한 사용자 인터페이스의 상태는 onCreate(Bundle) 또는 onRestoreInstanceState(Bundle) 를 통해 복원할 수 있습니다.


Steve Moseley

BundleSharedPreferences 저장에 대한 설명을 포함하여 Android 기기의 애플리케이션 상태를 설명하는 기사를 작성했습니다. 여기 를 보십시오 .

이 기사에서는 세 가지 접근 방식을 다룹니다.

인스턴스 상태 번들을 사용하여 애플리케이션 수명(즉, 일시적으로) 동안 로컬 변수/UI 제어 데이터 저장

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

공유 기본 설정을 사용하여 애플리케이션 인스턴스 간에 로컬 변수/UI 제어 데이터를 영구적으로 저장합니다.

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

Martin Belcher - AtWrk

이것은 Android 개발의 고전적인 '고쳐'입니다. 여기에는 두 가지 문제가 있습니다.

  • 최소한 레거시 버전에서는 개발 중 애플리케이션 스택 관리를 크게 복잡하게 만드는 미묘한 Android 프레임워크 버그가 있습니다(수정 여부/시기/방법은 확실하지 않음). 이 버그에 대해서는 아래에서 설명하겠습니다.
  • 이 문제를 관리하는 '정상적인' 또는 의도된 방법은 그 자체로 onPause/onResume 및 onSaveInstanceState/onRestoreInstanceState의 이중성으로 인해 다소 복잡합니다.

이 모든 스레드를 탐색하면서 개발자가 이 두 가지 다른 문제에 대해 동시에 이야기하는 경우가 많다고 생각합니다. 따라서 "이것이 작동하지 않습니다"라는 모든 혼란과 보고가 있습니다.

먼저 '의도된' 동작을 명확히 하기 위해: onSaveInstance 및 onRestoreInstance는 취약하고 일시적인 상태에만 해당됩니다. 의도된 용도(내가 말할 수 있는 한)는 전화가 회전할 때(방향 변경) 활동 재생을 처리하는 것입니다. 다시 말해서, 의도된 사용법은 활동이 여전히 논리적으로 '최상위'이지만 시스템에서 다시 인스턴스화해야 하는 경우입니다. 저장된 번들은 process/memory/ GC 외부에서 유지되지 않으므로 활동이 백그라운드로 이동하는 경우 실제로 이에 의존할 수 없습니다. 예, 아마도 활동의 메모리는 백그라운드로 이동하고 GC를 탈출해도 살아남을 수 있지만 이것은 신뢰할 수 없으며 예측할 수도 없습니다.

따라서 애플리케이션의 '실행' 사이에 지속되어야 하는 의미 있는 '사용자 진행' 또는 상태가 있는 시나리오가 있는 경우 지침은 onPause 및 onResume을 사용하는 것입니다. 영구 저장소를 직접 선택하고 준비해야 합니다.

그러나 - 이 모든 것을 복잡하게 만드는 매우 혼란스러운 버그가 있습니다. 자세한 내용은 다음과 같습니다.

기본적으로 응용 프로그램이 SingleTask 플래그로 시작된 다음 나중에 홈 화면이나 실행기 메뉴에서 시작하면 후속 호출에서 새 작업이 생성됩니다. 앱의 두 가지 다른 인스턴스를 효과적으로 갖게 됩니다. 같은 스택에 살고 있습니다 ... 매우 빠르게 이상해집니다. 이것은 개발 중에 앱을 시작할 때 발생하는 것 같습니다(예: Eclipse 또는 IntelliJ에서 ). 따라서 개발자는 이 문제를 많이 접하게 됩니다. 그러나 일부 앱 스토어 업데이트 메커니즘을 통해서도 가능합니다(그래서 사용자에게도 영향을 미침).

나는 내 주요 문제가 의도한 프레임워크 동작이 아니라 이 버그라는 것을 깨닫기 전에 몇 시간 동안 이 스레드를 통해 싸웠습니다. 훌륭한 필력과 해결 방법 (업데이트: 아래 참조) 이 답변에서 @kaciula 사용자가 보낸 것 같습니다.

홈 키 누르기 동작

2013년 6월 업데이트 : 몇 달 후 마침내 '올바른' 솔루션을 찾았습니다. 상태 기반 startedApp 플래그를 직접 관리할 필요가 없습니다. 프레임워크에서 이를 감지하고 적절하게 구제할 수 있습니다. 나는 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; } }

Mike Repass

onSaveInstanceState 는 시스템에 메모리가 필요하고 애플리케이션을 종료할 때 호출됩니다. 사용자가 응용 프로그램을 닫을 때 호출되지 않습니다. onPause 저장해야 한다고 생각합니다.

Preferences 또는 SQLite와 같은 일부 영구 저장소에 저장해야 합니다.


Fedor

두 방법 모두 유용하고 유효하며 서로 다른 시나리오에 가장 적합합니다.

  1. 사용자는 응용 프로그램을 종료하고 나중에 다시 열지만 응용 프로그램은 마지막 세션에서 데이터를 다시 로드해야 합니다. 이를 위해서는 SQLite 사용과 같은 영구 저장소 접근 방식이 필요합니다.
  2. 사용자는 응용 프로그램을 전환한 다음 원본으로 돌아가서 중단한 부분을 선택하려고 합니다 onSaveInstanceState()onRestoreInstanceState() 에서 번들 데이터(예: 응용 프로그램 상태 데이터)를 저장하고 복원하는 것이 적절합니다.

상태 데이터를 지속적으로 저장하면 onResume() 또는 onCreate() (또는 실제로 모든 수명 주기 호출에서) 다시 로드할 수 있습니다. 이것은 원하는 동작일 수도 있고 아닐 수도 있습니다. InstanceState 의 번들에 저장하면 일시적이며 동일한 사용자 '세션'(저는 세션이라는 용어를 느슨하게 사용)에서 사용하기 위해 데이터를 저장하는 데만 적합하지만 '세션' 사이에는 저장하지 않습니다.

모든 것과 마찬가지로 한 가지 접근 방식이 다른 것보다 낫다는 것이 아니라 필요한 동작을 이해하고 가장 적절한 접근 방식을 선택하는 것이 중요합니다.


David

내가 걱정하는 한 상태를 저장하는 것은 최선의 문제입니다. 영구 데이터를 저장해야 하는 경우 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;

Mike A.

답을 찾은 것 같아요. 내가 한 일을 간단한 말로 말해 보겠습니다.

활동 1과 활동 2가 있고 활동 1에서 활동 2로 이동하고(활동 2에서 일부 작업을 수행함) 활동 1의 버튼을 클릭하여 다시 활동 1로 돌아갔다고 가정합니다. 이제 이 단계에서 나는 activity2로 돌아가고 싶었고 마지막으로 activity2를 떠났을 때와 같은 상태에서 내 activity2를 보고 싶습니다.

위의 시나리오에서 내가 한 것은 매니페스트에서 다음과 같이 몇 가지를 변경한 것입니다.

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

그리고 버튼 클릭 이벤트의 activity1에서 다음과 같이 했습니다.

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

그리고 버튼 클릭 이벤트의 activity2에서 다음과 같이 수행했습니다.

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

이제 Activity2에서 변경한 사항이 무엇이든 손실되지 않고 이전과 동일한 상태에서 activity2를 볼 수 있습니다.

나는 이것이 답이라고 믿으며 이것은 나를 위해 잘 작동합니다. 내가 틀렸다면 정정하십시오.


roy mathew

일시적 데이터의 경우 onSaveInstanceState() onCreate() / onRestoreInstanceState() 복원됨), onPause() 영구 데이터의 경우( onResume() 복원됨). Android 기술 리소스에서:

onSaveInstanceState() 는 활동이 중지되고 재개되기 전에 종료될 수 있는 경우 Android에 의해 호출됩니다! 즉, Activity가 다시 시작될 때 동일한 조건으로 다시 초기화하는 데 필요한 모든 상태를 저장해야 합니다. onCreate() 메소드의 대응물이며 실제로 onCreate()에 전달된 storedInstanceState 번들은 onSaveInstanceState() 메소드에서 outState로 구성한 번들과 동일합니다.

onPause()onResume() 도 보완 메서드입니다. onPause()는 우리가 (예를 들어 finish() 호출을 사용하여) 시작했더라도 Activity가 종료될 때 항상 호출됩니다. 이것을 사용하여 현재 메모를 데이터베이스에 다시 저장합니다. 수동 상태에 있을 때 리소스를 덜 차지하기 위해 onPause() 중에 해제할 수 있는 모든 리소스도 해제하는 것이 좋습니다.


User

실제로 onSaveInstanceState() 는 활동이 백그라운드로 갈 때 호출됩니다.

문서에서 인용: "이 메서드는 활동이 종료되기 전에 호출되어 나중에 다시 돌아올 때 상태를 복원할 수 있습니다." 원천


u-foka

상용구를 줄이는 데 도움이 되도록 다음 interfaceclass 를 사용하여 인스턴스 상태를 저장 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); } }

참고: 이 코드는 MIT 라이선스에 따라 라이선스가 부여된 AndroidAutowire 라는 라이브러리 프로젝트에서 수정되었습니다.


Jared Rummler

그동안 나는 일반적으로 더 이상 사용하지 않습니다

 Bundle savedInstanceState & Co

수명 주기는 대부분의 활동에 대해 너무 복잡하고 필요하지 않습니다.

그리고 Google은 자체적으로도 신뢰할 수 없다고 말합니다.

내 방법은 환경 설정에서 변경 사항을 즉시 저장하는 것입니다.

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

어떤 면에서 SharedPreferences는 번들과 유사하게 작동합니다. 그리고 자연스럽게 그리고 처음에는 그러한 가치를 선호도에서 읽어야 합니다.

복잡한 데이터의 경우 기본 설정을 사용하는 대신 SQLite를 사용할 수 있습니다.

이 개념을 적용할 때 활동은 재부팅 사이의 초기 열기 또는 백 스택으로 인한 다시 열기 여부에 관계없이 마지막으로 저장된 상태를 계속 사용합니다.


stefan bachert

원래 질문에 직접 대답하려면. 활동이 다시 생성되지 않기 때문에 storedInstancestate가 null입니다.

활동은 다음과 같은 경우에만 상태 번들로 다시 생성됩니다.

  • 새 활동 인스턴스를 만들어야 할 수 있는 방향 또는 전화 언어 변경과 같은 구성 변경.
  • OS가 활동을 제거한 후 백그라운드에서 앱으로 돌아갑니다.

Android는 메모리가 부족하거나 오랜 시간 동안 백그라운드 활동을 수행한 후 백그라운드 활동을 삭제합니다.

Hello World 예제를 테스트할 때 Activity를 종료하고 돌아오는 몇 가지 방법이 있습니다.

  • 뒤로 버튼을 누르면 활동이 완료됩니다. 앱을 다시 실행하는 것은 완전히 새로운 인스턴스입니다. 백그라운드에서 전혀 다시 시작하지 않습니다.
  • 홈 버튼을 누르거나 작업 전환기를 사용하면 활동이 백그라운드로 이동합니다. 애플리케이션으로 다시 이동할 때 onCreate는 Activity가 파괴되어야 하는 경우에만 호출됩니다.

대부분의 경우 홈을 누른 다음 앱을 다시 실행하면 활동을 다시 만들 필요가 없습니다. 메모리에 이미 존재하므로 onCreate()가 호출되지 않습니다.

설정 -> 개발자 옵션 아래에 "활동을 유지하지 않음"이라는 옵션이 있습니다. 활성화되면 Android는 항상 활동을 삭제하고 백그라운드에서 활동을 다시 생성합니다. 이것은 최악의 시나리오를 시뮬레이션하기 때문에 개발할 때 활성화된 상태로 두는 좋은 옵션입니다. ( 항상 활동을 재활용하는 메모리 부족 장치 ).

다른 답변은 상태를 저장하는 올바른 방법을 가르친다는 점에서 가치가 있지만 코드가 예상한 방식으로 작동하지 않는 이유에 대해 실제로 답변했다고 생각하지 않았습니다.


Jared Kells

onSaveInstanceState(bundle)onRestoreInstanceState(bundle) 메서드는 화면을 회전하는 동안(방향 변경) 데이터 지속성에 유용합니다.
onSaveInstanceState() 메서드가 호출되지만 onCreate(bundle)onRestoreInstanceState(bundle) 가 다시 호출되지 않기 때문에 응용 프로그램 간에 전환하는 동안에도 좋지 않습니다.
지속성을 높이려면 공유 기본 설정을 사용하십시오. 이 기사를 읽으십시오


Mahorad

내 문제는 애플리케이션 수명 동안에만 지속성이 필요하다는 것입니다(즉, 동일한 앱 내에서 다른 하위 활동 시작 및 장치 회전 등을 포함하는 단일 실행). 위 답변의 다양한 조합을 시도했지만 모든 상황에서 원하는 것을 얻지 못했습니다. 결국 나를 위해 일한 것은 onCreate 동안 storedInstanceState에 대한 참조를 얻는 것이 었습니다.

 mySavedInstanceState=savedInstanceState;

그리고 그것을 사용하여 필요할 때 내 변수의 내용을 다음과 같이 얻습니다.

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

onSaveInstanceStateonRestoreInstanceState 를 사용 putBoolean 사용) 내 메서드를 사용하여 변수를 저장할 수도 있습니다.


torwalker

허용된 답변이 정확하지만 Icepick 이라는 라이브러리를 사용하여 Android에서 활동 상태를 저장하는 더 빠르고 쉬운 방법이 있습니다. 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"); } }

Bundle 상태를 저장하는 모든 객체와 함께 작동합니다.


Kevin Cronly

액티비티가 생성되면 onCreate() 메서드가 호출됩니다.

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

SavedInstanceState는 Bundle 클래스의 객체로 처음에는 null이지만 다시 생성할 때 값을 포함합니다. Activity의 상태를 저장하려면 onSaveInstanceState()를 재정의해야 합니다.

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

outState.putString("key","Welcome Back")과 같은 "outState" 번들 객체에 값을 넣고 super를 호출하여 저장합니다. 활동이 소멸되면 해당 상태가 Bundle 객체에 저장되고 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"); }

Mansuu....

기본적으로 이 변경을 구현하는 두 가지 방법이 있습니다.

  1. onSaveInstanceState()onRestoreInstanceState() .
  2. 매니페스트에서 android:configChanges="orientation|screenSize" .

두 번째 방법을 사용하는 것은 정말 권장하지 않습니다. 내 경험 중 하나에서 세로에서 가로로 또는 그 반대로 회전하는 동안 장치 화면의 절반이 검은색으로 바뀌었기 때문입니다.

위에서 언급한 첫 번째 방법을 사용하여 방향이 변경되거나 구성 변경이 발생할 때 데이터를 유지할 수 있습니다. 나는 당신이 storedInstance 상태 객체 안에 모든 유형의 데이터를 저장할 수 있는 방법을 알고 있습니다.

예: Json 객체를 유지하려는 경우를 고려하십시오. getter 및 setter를 사용하여 모델 클래스를 만듭니다.

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

Krishna

다음은 관점을 고려한 Steve Moseley 의 답변( ToolmakerSteve 작성 )입니다(전체 onSaveInstanceState 대 onPause, 동쪽 비용 대 서쪽 비용 사가).

@VVK - 부분적으로 동의하지 않습니다. 앱을 종료하는 일부 방법은 onSaveInstanceState(oSIS)를 트리거하지 않습니다. 이것은 oSIS의 유용성을 제한합니다. 최소한의 OS 리소스로 지원할 가치가 있지만 앱이 사용자를 원래 상태로 되돌리려면 앱이 어떻게 종료되었는지에 관계없이 대신 영구 저장소 접근 방식을 사용해야 합니다. 나는 onCreate를 사용하여 번들을 확인하고 누락된 경우 영구 저장소를 확인합니다. 이것은 의사 결정을 중앙 집중화합니다. 충돌, 뒤로 버튼 종료 또는 사용자 정의 메뉴 항목 종료에서 복구하거나 며칠 후에 사용자가 있었던 화면으로 돌아갈 수 있습니다. – ToolmakerSteve 9월 19 '15 at 10:38


samis

코틀린 코드:

저장:

 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

선택 사항을 원하지 않으면 기본값을 추가하십시오.


Rafols

onCreate() 저장된 활동 상태 데이터를 가져오려면 SaveInstanceState(Bundle savedInstanceState) 메서드를 재정의하여 storedInstanceState에 데이터를 저장해야 합니다.

Activity destroy SaveInstanceState(Bundle savedInstanceState) 메소드가 호출되면 저장하려는 데이터를 저장합니다. onCreate() 에서 동일한 결과를 얻습니다. (활동이 파괴되기 전에 일부 데이터를 저장했기 때문에 savedInstanceState는 null이 되지 않습니다)


iamabhaykmr

이 문제를 해결하는 간단한 빠른 방법은 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' }

이제 Activity에서 상태를 저장하는 방법을 아래에서 이 예를 확인해 보겠습니다.

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

활동, 조각 또는 번들에서 상태를 직렬화해야 하는 모든 개체(예: 박격포의 ViewPresenters)에 대해 작동합니다.

Icepick은 사용자 정의 보기에 대한 인스턴스 상태 코드를 생성할 수도 있습니다.

 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 }

THANN Phearum

내 솔루션이 눈살을 찌푸리게 하는지 확실하지 않지만 ViewModel 상태를 유지하기 위해 바인딩된 서비스를 사용합니다. 서비스의 메모리에 저장하거나 SQLite 데이터베이스에서 유지하고 검색할지 여부는 요구 사항에 따라 다릅니다. 이것은 모든 종류의 서비스가 하는 일이며 애플리케이션 상태 유지 및 추상 공통 비즈니스 논리와 같은 서비스를 제공합니다.

모바일 장치 고유의 메모리 및 처리 제약으로 인해 Android 보기를 웹 페이지와 유사한 방식으로 처리합니다. 페이지는 상태를 유지하지 않으며, 애플리케이션 상태를 표시하고 사용자 입력을 수락하는 것이 유일한 목적인 순전히 프레젠테이션 계층 구성 요소입니다. 웹 앱 아키텍처의 최근 추세는 페이지가 보기이고 도메인 데이터가 모델이며 컨트롤러가 웹 서비스 뒤에 있는 오래된 MVC(모델, 보기, 컨트롤러) 패턴 사용을 사용합니다. View가 있는 Android에서 동일한 패턴을 사용할 수 있습니다. 음... View, 모델은 도메인 데이터이고 Controller는 Android 바운드 서비스로 구현됩니다. 뷰가 컨트롤러와 상호 작용하기를 원할 때마다 시작/재개 시 뷰에 바인딩하고 중지/일시 중지 시 바인딩 해제합니다.

이 접근 방식은 모든 애플리케이션 비즈니스 논리를 서비스로 이동할 수 있다는 점에서 관심 분리 설계 원칙을 적용하는 추가 보너스를 제공하여 여러 보기에서 중복 논리를 줄이고 보기에서 또 다른 중요한 디자인 원칙인 단일 책임을 시행할 수 있도록 합니다.


ComeIn

코틀린

지속하려는 변수를 저장하고 검색하려면 onSaveInstanceStateonRestoreInstanceState 를 재정의해야 합니다.

라이프 사이클 그래프

변수 저장

 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 }

Sazzad Hissain Khan

이제 Android는 상태를 저장하기 위한 ViewModel 을 제공하므로 saveInstanceState 대신 이를 사용해 보세요.


M Abdul Sami

어떤 방법도 구현하지 않고 Android가 상태를 저장하도록 하는 방법이 있습니다. 활동 선언의 매니페스트에 다음 줄을 추가하기만 하면 됩니다.

 android:configChanges="orientation|screenSize"

다음과 같이 표시되어야 합니다.

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

여기에서 이 속성에 대한 자세한 정보를 찾을 수 있습니다.

수동으로 처리하는 것보다 Android에서 처리하도록 하는 것이 좋습니다.


IgniteCoders

저장해야 할 것과 저장하지 말아야 할 것은 무엇입니까?

방향이 변경되는 동안 EditText 의 텍스트가 자동으로 저장되는 이유가 궁금하신가요? 글쎄,이 대답은 당신을위한 것입니다.

활동의 인스턴스가 파괴되고 시스템이 새 인스턴스를 다시 생성하는 경우(예: 구성 변경). 이전 활동 상태(인스턴스 상태 )의 저장된 데이터 세트를 사용하여 재생성을 시도합니다.

인스턴스 상태는 Bundle 객체에 저장된 키-값 쌍의 모음입니다.

기본적으로 시스템은 예를 들어 보기 개체를 번들에 저장합니다.

  • EditText 텍스트
  • ListView 등의 스크롤 위치

당신이 인스턴스 상태의 일부로 저장하는 또 다른 변수를해야하는 경우는 오버라이드 (override) 할 필요가 onSavedInstanceState(Bundle savedinstaneState) 방법을.

예를 들어, GameActivity의 int currentScore

데이터를 저장하는 동안 onSavedInstanceState(Bundle storedinstaneState)에 대한 자세한 내용

 @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); 기본 동작은 작동하지 않습니다. 즉, EditText의 텍스트는 저장되지 않습니다.

활동 상태를 복원하기 위해 무엇을 선택해야 합니까?

 onCreate(Bundle savedInstanceState)

또는

 onRestoreInstanceState(Bundle savedInstanceState)

두 방법 모두 동일한 Bundle 개체를 가져오므로 복원 논리를 작성하는 위치는 중요하지 않습니다. 유일한 차이점은 onCreate(Bundle savedInstanceState) 메서드에서 null 검사를 제공해야 하지만 후자의 경우에는 필요하지 않다는 것입니다. 다른 답변에는 이미 코드 조각이 있습니다. 당신은 그들을 참조할 수 있습니다.

onRestoreInstanceState(Bundle storedinstaneState)에 대한 자세한 내용

 @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) 는 사용자가 활동으로 돌아오려는 경우에만 시스템에 의해 호출됩니다. 예를 들어, App X를 사용하고 있는데 갑자기 전화가 옵니다. 호출자 앱으로 이동하고 앱 X로 돌아옵니다. 이 경우 onSaveInstanceState(Bundle savedInstanceState) 메서드가 호출됩니다.

그러나 사용자가 뒤로 버튼을 누르면 이것을 고려하십시오. 사용자가 Activity로 돌아갈 의도가 없다고 가정하므로 이 경우 onSaveInstanceState(Bundle savedInstanceState) 는 시스템에서 호출되지 않습니다. 포인트는 데이터를 저장하는 동안 모든 시나리오를 고려해야 한다는 것입니다.

관련 링크:

기본 동작에 대한 데모
안드로이드 공식 문서 .


Rohit Singh

이제 뷰 모델에서 2가지 방법을 수행하는 것이 합리적입니다. 첫 번째 인스턴스를 저장된 인스턴스로 저장하려면 다음과 같이 뷰 모델에 상태 매개변수를 추가할 수 있습니다. https://developer.android.com/topic/libraries/architecture/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); } }

Umut ADALI

Android ViewModel 및 SavedStateHandle을 사용하여 직렬화 가능한 데이터 유지

 public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater()); binding.setViewModel(new ViewModelProvider(this).get(ViewModel.class)); binding.setLifecycleOwner(this); setContentView(binding.getRoot()); } public static class ViewModel extends AndroidViewModel { //This field SURVIVE the background process reclaim/killing & the configuration change public final SavedStateHandle savedStateHandle; //This field NOT SURVIVE the background process reclaim/killing but SURVIVE the configuration change public final MutableLiveData<String> inputText2 = new MutableLiveData<>(); public ViewModel(@NonNull Application application, SavedStateHandle savedStateHandle) { super(application); this.savedStateHandle = savedStateHandle; } } }

레이아웃 파일에서

 <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="viewModel" type="com.xxx.viewmodelsavedstatetest.MainActivity.ViewModel" /> </data> <LinearLayout xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:autofillHints="" android:hint="This field SURVIVE the background process reclaim/killing &amp; the configuration change" android:text='@={(String)viewModel.savedStateHandle.getLiveData("activity_main/inputText", "")}' /> <SeekBar android:layout_width="match_parent" android:layout_height="wrap_content" android:max="100" android:progress='@={(Integer)viewModel.savedStateHandle.getLiveData("activity_main/progress", 50)}' /> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="This field SURVIVE the background process reclaim/killing &amp; the configuration change" android:text='@={(String)viewModel.savedStateHandle.getLiveData("activity_main/inputText", "")}' /> <SeekBar android:layout_width="match_parent" android:layout_height="wrap_content" android:max="100" android:progress='@={(Integer)viewModel.savedStateHandle.getLiveData("activity_main/progress", 50)}' /> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="This field NOT SURVIVE the background process reclaim/killing but SURVIVE the configuration change" android:text='@={viewModel.inputText2}' /> </LinearLayout> </layout>

시험:

 1. start the test activity 2. press home key to go home 3. adb shell kill <the test activity process> 4. open recent app list and restart the test activity

Yessy

출처 : http:www.stackoverflow.com/questions/151777/how-can-i-save-an-activity-state-using-the-save-instance-state

반응형