インスタンス状態の保存を使用して Android アクティビティ状態を保存するにはどうすればよいですか?
-
02-07-2019 - |
質問
私は 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(" Name-Value Pair")マップを保存する方法であり、 onCreate()
および onRestoreInstanceState()<に渡されます。 / code>このような値を抽出する場所:
@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");
}
通常、この手法を使用して、アプリケーションのインスタンス値(選択、未保存のテキストなど)を保存します。
他のヒント
savedInstanceState
は、アクティビティの現在のインスタンスに関連付けられた状態(現在のナビゲーションや選択情報など)を保存するためのものです。これにより、Androidがアクティビティを破棄して再作成した場合に、そのまま戻ることができます前だった。 onCreate のドキュメントをご覧ください。 code>
および onSaveInstanceState
より長期間の状態については、SQLiteデータベース、ファイル、または設定の使用を検討してください。 永続状態の保存を参照してください。
onSaveInstanceState
および onRestoreInstanceState
永続データに使用するのは NOT 安全であることに注意してください、 http://developer.androidのアクティビティ状態に関するドキュメントによる。 com / reference / android / app / Activity.html 。
ドキュメントの状態(「アクティビティライフサイクル」セクション):
保存することが重要であることに注意してください 代わりに
onPause()
の永続データonSaveInstanceState(Bundle)
の 後者はの一部ではないため ライフサイクルコールバック。 説明されているようにあらゆる状況で呼び出されます そのドキュメントで。
つまり、永続データの保存/復元コードを onPause()
と onResume()
に入れてください!
編集:さらに明確にするために、 onSaveInstanceState()
のドキュメントがあります:
このメソッドは、アクティビティが強制終了される前に呼び出されるため、 その状態を復元できる将来のある時点で戻ってきます。にとって たとえば、アクティビティAの前でアクティビティBが起動され、 ポイントアクティビティAはリソースを回収するために殺され、アクティビティAは これを介してユーザーインターフェイスの現在の状態を保存する機会 ユーザーがアクティビティAに戻ったときに、 ユーザーインターフェースは、
onCreate(Bundle)
またはonRestoreInstanceState(Bundle)
。
私の同僚は、アクティビティのライフサイクルと状態情報の説明、状態情報の保存方法、状態への保存 Bundle
および SharedPreferences
など、Androidデバイス上のアプリケーションの状態を説明する記事を書きましたおよびこちらをご覧ください。
この記事では、3つのアプローチについて説明します。
インスタンス状態バンドルを使用して、アプリケーションの有効期間(つまり、一時的に)のローカル変数/ 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();
}
これは、Android開発の古典的な「落とし穴」です。ここには2つの問題があります:
- 少なくともレガシーバージョンでは、開発中のアプリケーションスタック管理を大幅に複雑化する微妙なAndroid Frameworkのバグがあります(修正されたかどうか、いつ、どのように修正されたかは完全にはわかりません)。このバグについては後述します。
- この問題を管理する「通常の」または意図された方法は、それ自体、onPause / onResumeとonSaveInstanceState / onRestoreInstanceStateの二重性によりかなり複雑です
これらすべてのスレッドを閲覧していると、多くの場合、開発者がこれら2つの異なる問題について同時に話しているのではないかと思われます...したがって、「これは私には役に立たない」という混乱と報告がすべてあります。
最初に、「意図された」動作を明確にするために:onSaveInstanceとonRestoreInstanceは壊れやすく、一時的な状態のためだけです。意図された使用法(afaict)は、電話機が回転したとき(方向の変更)にアクティビティのレクリエーションを処理することです。言い換えれば、意図された使用法は、アクティビティが依然として論理的に「上」にあるが、システムによって再インスタンス化される必要がある場合です。保存されたバンドルは、プロセス/メモリ/ gcの外部に永続化されないため、アクティビティがバックグラウンドに移行する場合、これに実際に依存することはできません。はい、おそらくアクティビティのメモリはバックグラウンドへの旅行を生き延び、GCを回避しますが、これは信頼できません(予測もできません)。
つまり、アプリケーションの「起動」間で持続する意味のある「ユーザーの進捗」または状態があるシナリオがある場合、ガイダンスはonPauseとonResumeを使用することです。永続ストアを選択して準備する必要があります。
BUT-これをすべて複雑にする非常に紛らわしいバグがあります。詳細はこちら:
http://code.google.com/p/android/ issues / detail?id = 2373
http://code.google.com/p/android/ issues / detail?id = 5277
基本的に、アプリケーションがSingleTaskフラグを使用して起動され、その後ホーム画面またはランチャーメニューから起動された場合、その後の呼び出しで新しいタスクが作成されます...事実上、2つの異なるインスタンスがあります同じスタックに存在するアプリの...非常に奇妙に非常に速くなります。これは、開発中にアプリを起動すると(つまり、EclipseまたはIntellijから)発生するため、開発者はこれに頻繁に遭遇します。ただし、アプリストアの更新メカニズムの一部も使用します(したがって、ユーザーにも影響します)。
主な問題が意図したフレームワークの動作ではなく、このバグであることに気付くまで、これらのスレッドを何時間も戦いました。この回答では、@ kaciulaユーザーからの優れた記述と workaround (更新:以下を参照)のようです。
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;
}
}
onSaveInstanceState
は、システムがメモリを必要とし、アプリケーションを強制終了したときに呼び出されます。ユーザーがアプリケーションを閉じただけでは呼び出されません。したがって、アプリケーションの状態も onPause
に保存する必要があると思います。 Preferences
や Sqlite
どちらの方法も有用かつ有効であり、両方とも異なるシナリオに最適です:
- ユーザーはアプリケーションを終了し、後日再度開きますが、アプリケーションは最後のセッションからデータをリロードする必要があります&#8211;これには、SQLiteを使用するなどの永続的なストレージアプローチが必要です。
- ユーザーがアプリケーションを切り替えた後、元に戻り、中断した場所から再開したい-
onSaveInstanceState()
およびonRestoreInstanceState()
は通常適切です。
永続的な方法で状態データを保存する場合、 onResume()
または onCreate()
(または実際にはライフサイクルコール)で再読み込みできます。これは望ましい動作である場合とそうでない場合があります。 InstanceState
のバンドルに保存する場合、一時的なものであり、同じユーザー&#8216;セッション&#8217;で使用するデータの保存にのみ適しています。 (セッションという用語は大まかに使用します)。ただし、&#8216;セッション&#8217;の間ではありません。
すべてと同様に、1つのアプローチが他のアプローチより優れているわけではなく、必要な動作を理解し、最も適切なアプローチを選択することが重要です。
状態を保存することは、私が懸念する限りでは最高です。永続データを保存する必要がある場合は、 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;
答えを見つけたと思います。私がやったことを簡単な言葉で教えてください:
2つのアクティビティ、activity1とactivity2があり、activity1からactivity2にナビゲートし(activity2でいくつかの作業を行った)、再びactivity1のボタンをクリックしてアクティビティ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を表示できることです。
これが答えだと思うし、これは私にとってはうまくいく。間違っている場合は修正してください。
onSaveInstanceState()
( onCreate()
/ onRestoreInstanceState()
で復元)、 onPause()永続データのcode>(
onResume()
で復元)。
Android技術リソースから:
onSaveInstanceState()は、アクティビティが停止されている場合にAndroidによって呼び出され、再開される前に強制終了される可能性があります。これは、アクティビティの再起動時に同じ条件に再初期化するために必要な状態を保存する必要があることを意味します。これはonCreate()メソッドに対応するものであり、実際、onCreate()に渡されるsavedInstanceState Bundleは、onSaveInstanceState()メソッドでoutStateとして構築するのと同じBundleです。
onPause()および onResume()も無料の方法です。 onPause()は、Activityが終了したときに、たとえ終了した場合でも(たとえばfinish()呼び出しで)常に呼び出されます。これを使用して、現在のメモをデータベースに保存します。 onPause()の間に解放できるリソースも解放することをお勧めします。これにより、パッシブ状態のときに使用するリソースが少なくなります。
アクティビティがバックグラウンドになったときに実際に onSaveInstance
が呼び出される状態
ドキュメントからの引用:
&quot; onSaveInstanceState(Bundle)
メソッドは、アクティビティをそのようなバックグラウンド状態にする前に呼び出されます
ボイラープレートを減らすために、次を使用します 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);
}
}
注記: このコードは、という名前のライブラリ プロジェクトから改変されたものです。 Androidオートワイヤー に基づいてライセンスされている MITライセンス.
一方、私は一般的にこれ以上使用しません
Bundle savedInstanceState & Co
ライフサイクルは、ほとんどのアクティビティにとって複雑すぎて不要です。
そして、Googleはそれ自身を述べています、それは信頼性さえありません。
私の方法は、設定に変更をすぐに保存することです:
SharedPreferences p;
p.edit().put(..).commit()
何らかの方法でSharedPreferencesはBundlesのように機能します。 そして当然、最初はそのような値を設定から読み取る必要があります。
複雑なデータの場合、設定を使用する代わりにSQLiteを使用できます。
この概念を適用すると、アクティビティは、再起動を伴う最初のオープンであるか、バックスタックによる再オープンであるかに関係なく、最後に保存された状態を使用し続けます。
元の質問に直接答える。アクティビティは再作成されないため、savedInstancestateはnullです。
アクティビティは、次の場合に状態バンドルで再作成されます。
- 新しいアクティビティインスタンスの作成が必要になる可能性がある、向きや電話言語の変更などの構成の変更。
- OSがアクティビティを破棄した後、バックグラウンドからアプリに戻ります。
Androidは、メモリが不足している場合、または長時間バックグラウンドにいた後、バックグラウンドアクティビティを破棄します。
Hello Worldの例をテストする場合、アクティビティから離れてアクティビティに戻る方法がいくつかあります。
- 戻るボタンを押すと、アクティビティが終了します。アプリの再起動はまったく新しいインスタンスです。バックグラウンドから再開することはまったくありません。
- ホームボタンを押すか、タスクスイッチャーを使用すると、アクティビティがバックグラウンドになります。アプリケーションに戻るときに、アクティビティを破棄する必要がある場合にのみonCreateが呼び出されます。
ほとんどの場合、ホームを押してからアプリを再度起動するだけであれば、アクティビティを再作成する必要はありません。これは既にメモリに存在するため、onCreate()は呼び出されません。
[設定]の下にオプションがあります-&gt; 「アクティビティを保持しない」という開発者オプション。有効にすると、Androidは常にアクティビティを破棄し、バックグラウンドになったときに再作成します。これは、最悪のシナリオをシミュレートするため、開発時に有効のままにしておくのに最適なオプションです。 (常にメモリを節約しているデバイスがアクティビティをリサイクルします)。
他の回答は、状態を保存する正しい方法を教えてくれるという点で価値がありますが、コードが期待どおりに機能しなかった理由を本当に答えたとは思いませんでした。
onSaveInstanceState(bundle)
および onRestoreInstanceState(bundle)
メソッドは、画面を回転させている間だけでデータの永続化に役立ちます(向きの変更)。
アプリケーション間の切り替え中はあまり良くありません( onSaveInstanceState()
メソッドが呼び出されますが、 onCreate(bundle)
および onRestoreInstanceState(bundle)
は再度呼び出されません。
永続性を高めるには、共有設定を使用します。 この記事を読む
私の問題は、アプリケーションの存続期間(つまり、同じアプリ内で他のサブアクティビティを開始したり、デバイスを回転させたりするなどの単一の実行)の間だけ持続性が必要だったことです。上記の答えをさまざまに組み合わせてみましたが、すべての状況で必要なものが得られませんでした。最終的には、onCreate中にsavedInstanceStateへの参照を取得することができました。
mySavedInstanceState=savedInstanceState;
そしてそれを使用して、必要なときに変数の内容を取得します:
if (mySavedInstanceState !=null) {
boolean myVariable = mySavedInstanceState.getBoolean("MyVariable");
}
上記で提案したように onSaveInstanceState
および onRestoreInstanceState
を使用しますが、変数が変更されたときに変数を保存する方法も使用できます(たとえば、 putBoolean
)
受け入れられた答えは正しいものの、 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
で保存するオブジェクトで動作します。
アクティビティが作成されると、onCreate()メソッドが呼び出されます。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
savedInstanceStateはBundleクラスのオブジェクトであり、初めてnullになりますが、再作成時には値が含まれています。アクティビティの状態を保存するには、onSaveInstanceState()をオーバーライドする必要があります。
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putString("key","Welcome Back")
super.onSaveInstanceState(outState); //save state
}
&quot; outState&quot;に値を入力しますoutState.putString(&quot; key&quot;、&quot; Welcome Back&quot;)のようなオブジェクトをバンドルし、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");
}
この変更を実装するには、基本的に2つの方法があります。
-
onSaveInstanceState()
およびonRestoreInstanceState()
を使用します。 - マニフェスト
android:configChanges =&quot; orientation | screenSize&quot;
で。
2番目の方法を使用することは本当にお勧めしません。私の経験の1つで、ポートレートからランドスケープへ、またはその逆に回転しているときに、デバイス画面の半分が黒くなっていました。
上記の最初の方法を使用すると、向きが変更された場合や設定が変更された場合にデータを保持できます。 savedInstance状態オブジェクト内にあらゆるタイプのデータを保存できる方法を知っています。
例:Jsonオブジェクトを永続化する場合を考えます。 ゲッターとセッターを含むモデルクラスを作成します。
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);
}
これは Steve Moseley の回答( ToolmakerSteve による)からのコメントです。これは、物事を(onSaveInstanceState対onPause全体で、東コスト対西コストサガ全体で) )
@VVK-私は部分的に同意しません。アプリを終了するいくつかの方法はトリガーしません onSaveInstanceState(oSIS)。これにより、oSISの有用性が制限されます。その 最小限のOSリソースでサポートする価値があるが、アプリが必要な場合 アプリの状態に関係なく、ユーザーを元の状態に戻す 終了した場合は、代わりに永続ストレージのアプローチを使用する必要があります。 onCreateを使用してバンドルを確認し、欠落している場合は確認します 永続ストレージ。これにより、意思決定が一元化されます。私は出来ます クラッシュからの回復、戻るボタンの終了、カスタムメニュー項目の終了、または 数日後にユーザーが画面に戻ることができました。 &#8211;ツールメーカースティーブ9月 19 '15 at 10:38
Kotlinコード:
保存:
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
オプションを使用したくない場合はデフォルト値を追加します
onCreate()
に保存されているアクティビティ状態データを取得するには、まず SaveInstanceState(Bundle savedInstanceState)
メソッドをオーバーライドして、savedInstanceStateにデータを保存する必要があります。
アクティビティdestroy SaveInstanceState(Bundle savedInstanceState)
メソッドが呼び出され、そこに保存するデータを保存します。そして、アクティビティの再開時に onCreate()
で同じ結果になります(アクティビティが破棄される前にデータを保存しているため、savedInstanceStateはnullになりません)
この問題を簡単に解決するには、 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);
}
}
アクティビティ、フラグメント、またはバンドルで状態をシリアル化する必要があるオブジェクト(たとえば、モルタルの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
}
自分のソリューションが眉をひそめているかどうかはわかりませんが、バインドされたサービスを使用してViewModelの状態を保持します。サービスのメモリに保存するか、SQLiteデータベースから永続化して取得するかは、要件によって異なります。これはあらゆるフレーバーのサービスが行うことであり、アプリケーションの状態の維持や一般的なビジネスロジックの抽象化などのサービスを提供します。
モバイルデバイスに固有のメモリと処理の制約のため、AndroidビューはWebページと同様の方法で扱います。ページは状態を維持せず、純粋にプレゼンテーション層コンポーネントであり、その唯一の目的はアプリケーションの状態を提示し、ユーザー入力を受け入れることです。 Webアプリアーキテクチャの最近の傾向は、ページがビューであり、ドメインデータがモデルであり、コントローラーがWebサービスの背後にある古いモデル、ビュー、コントローラー(MVC)パターンの使用を採用しています。同じパターンをAndroidで使用することもできます。Viewは、つまりビューです。モデルはドメインデータであり、ControllerはAndroidバウンドサービスとして実装されます。ビューをコントローラーとやり取りしたいときはいつでも、開始/再開時にビューにバインドし、停止/一時停止時にバインドを解除します。
このアプローチは、すべてのアプリケーションビジネスロジックをサービスに移動できるという懸念の分離設計原則を実施するという追加のボーナスを提供します。単一の責任。
現在、Androidは状態を保存するための ViewModels を提供しています。 saveInstanceStateの代わりに使用します。
保存するものと保存しないもの
方向の変更中に EditText
のテキストが自動的に保存される理由を疑問に思ったことはありませんか?さて、この答えはあなたのためです。
アクティビティのインスタンスが破棄され、システムが新しいインスタンスを再作成したとき(構成の変更など)。古いアクティビティ状態(インスタンス状態)の保存されたデータのセットを使用して、それを再作成しようとします。
インスタンスの状態は、 Bundle
オブジェクトに保存された key-value ペアのコレクションです。
デフォルトでは、たとえば、システムはバンドルにViewオブジェクトを保存します。
-
EditText
のテキスト
-
ListView
などでのスクロール位置
インスタンス状態の一部として別の変数を保存する必要がある場合は、 onSavedInstanceState(Bundle savedinstaneState)メソッドをオーバーライドする必要があります。
たとえば、GameActivityの int currentScore
データの保存中のonSavedInstanceState(Bundle savedinstaneState)の詳細
@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 savedinstaneState)の詳細
@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)
メソッドが呼び出されます。
ただし、ユーザーが戻るボタンを押した場合は、これを考慮してください。ユーザーはアクティビティに戻ることを意図していないと想定されるため、この場合、 onSaveInstanceState(Bundle savedInstanceState)
はシステムによって呼び出されません。
ポイントは、データを保存する際にすべてのシナリオを考慮する必要があることです。
関連リンク:
コトリン
永続化する変数を保存および取得するには、 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
}
より良いアイデアがあります。これは、onCreateを再度呼び出さずにデータを保存する方が良いでしょう。向きが変わったときにアクティビティから無効にすることができます。
マニフェスト内:
<activity android:name=".MainActivity"
android:configChanges="orientation|screenSize">
編集: このソリューションは、Androidがメモリ不足のためプロセスを強制終了した場合は機能しません。データを確実に保持する必要がある場合は、この場合SavedInstanceStateを使用する必要があります。そうでない場合は、この方法を使用することをお勧めします。クールで使いやすい!