web-dev-qa-db-ja.com

インスタンスの保存状態を使用してAndroidアクティビティの状態を保存する方法

私はAndroid SDKプラットフォームに取り組んできましたが、アプリケーションの状態を保存する方法は少しわかりません。だから、 'こんにちは、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分ほどドキュメントを覗き見してきて、何も明白なことを見つけていません。

2415
Bernard

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.
}

Bundleは本質的にはNVP( "Name-Value Pair")マップを保存する方法です。そしてそれは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");
}

通常、このテクニックを使用してアプリケーションのインスタンス値(選択、未保存のテキストなど)を保存します。

2377
Reto Meier

savedInstanceStateは、現在のナビゲーションや選択情報など、現在のActivityのインスタンスに関連付けられた状態を保存するためのものです。AndroidがActivityを破棄して再作成した場合、元の状態に戻ることができます。 onCreate および onSaveInstanceState の資料を参照してください。

より長持ちするためには、SQLiteデータベース、ファイル、または設定を使用することを検討してください。 永続状態の保存 を参照してください。

396
Dave L.

http://developer.Android.comのアクティビティの状態に関するドキュメントによると、onSaveInstanceStateおよびonRestoreInstanceState永続データを使用するのが安全です)はNOT安全です/reference/Android/app/Activity.html

この文書は次のように述べています( 'Activity Lifecycle'セクションにあります)。

onPause() の代わりにの代わりに永続データをonSaveInstanceState(Bundle)に保存することが重要です。後者はライフサイクルコールバックの一部ではないため、ではないからです。 は、その文書の[]に記載されているすべての状況で呼び出されました。

つまり、onPause()onResume()に永続データの保存/復元コードを入れてください。

EDIT:さらに明確にするために、これはonSaveInstanceState()のドキュメントです。

このメソッドは、アクティビティが殺される前に呼び出されるので、将来いつか戻ってきたときにその状態を復元できます。 たとえば、アクティビティBがアクティビティAの前で開始され、ある時点でアクティビティAがリソースを取り戻すために殺された場合、アクティビティAにはを節約するチャンスがあります。ユーザーがアクティビティAに戻ったときにユーザーインターフェイスの状態をonCreate(Bundle)または onRestoreInstanceState(Bundle)で復元できるように、このメソッドによるユーザーインターフェイスの現在の状態。

383
Steve Moseley

私の同僚は、活動のライフサイクルと状態情報、状態情報の保存方法、状態BundleSharedPreferencesここを見てください への保存に関する説明を含む、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();
}
180

これはAndroid開発の古典的な「問題点」です。ここに2つの問題があります。

  • 少なくとも旧バージョンでは、開発中のアプリケーションスタック管理を非常に複雑にする微妙なAndroidフレームワークのバグがあります(それがいつ修正されたか/いつ/どのように完全にはわからない)。このバグについては後で説明します。
  • この問題を管理するための「通常の」または意図された方法は、それ自体、onPause/onResumeとonSaveInstanceState/onRestoreInstanceStateの二重性ではかなり複雑です。

これらすべてのスレッドに目を通すと、開発者がこれら2つの異なる問題について同時に話している時間が多いのではないかと思われます。

まず、「意図された」動作を明確にするために、onSaveInstanceとonRestoreInstanceは脆弱であり、一時的な状態に限られます。意図された用法(afaict)は、電話が回転したときのActivityの再現を処理することです(向きの変更)。言い換えれば、意図された用法はあなたの活動がまだ論理的に「上に」あるが、それでもシステムによって再インスタンス化されなければならないときです。保存されたBundleはプロセス/ memory/gcの外には保存されないので、あなたの活動がバックグラウンドに行くのであれば、これに頼ることはできません。はい、おそらくあなたの活動の記憶はバックグラウンドへのその旅行を乗り切ってGCを免れるでしょう、しかしこれは信頼できません(またそれは予測できません)。

そのため、アプリケーションの「起動」の間に意味のある「ユーザーの進捗状況」や状態が持続するというシナリオがある場合は、onPauseとonResumeを使用することをお勧めします。永続ストアを自分で選択して準備する必要があります。

しかし - これを全部複雑にする非常に紛らわしいバグがあります。詳細はこちら:

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

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

基本的に、アプリケーションがSingleTaskフラグを付けて起動され、その後ホーム画面またはランチャーメニューから起動された場合、その後の起動で新しいタスクが作成されます。事実上、アプリケーションの2つの異なるインスタンスがあります。同じスタックに生息しています...これは非常に奇妙に速くなります。これは、開発中に(つまりEclipseやIntellijから)アプリを起動したときに発生するように思われるため、開発者はこれに遭遇します。しかし、いくつかのApp Storeのアップデートメカニズムを通じても同様です(つまり、ユーザーにも影響します)。

私の主な問題はこのバグであり、意図されたフレームワークの振る舞いではないことに気づく前に、私は何時間もこれらのスレッドを通して戦いました。素晴らしい記事と workaround (更新:下記参照)この回答のユーザ@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;
    }
}
132
Mike Repass

onSaveInstanceStateは、システムがメモリを必要とし、アプリケーションを強制終了したときに呼び出されます。ユーザーがアプリケーションを閉じただけでは呼び出されません。そのため、アプリケーションの状態もonPauseに保存する必要があると思います。PreferencesSqliteなどの永続的なストレージに保存する必要があります

73
Fedor

どちらの方法も便利で有効であり、どちらもさまざまなシナリオに最適です。

  1. ユーザーはアプリケーションを終了して後日再び開くが、アプリケーションは最後のセッションからデータをリロードする必要がある - これはSQLiteを使用するなどの永続的なストレージアプローチを必要とする。
  2. ユーザーがアプリケーションを切り替えてから元に戻り、中断したところから再開したい場合 - バンドルデータ(アプリケーション状態データなど)をonSaveInstanceState()に保存して復元します。通常はonRestoreInstanceState()で十分です。

状態データを永続的な方法で保存すると、onResume()またはonCreate()(または実際にはすべてのライフサイクル呼び出し)で再ロードできます。これは望ましい動作である場合も、そうでない場合もあります。それをInstanceStateのバンドルに格納する場合、それは一時的なものであり、同じユーザーの「セッション」で使用するデータの格納にのみ適しています(私は「セッション」という用語を大まかに使用します)。

1つの方法が他の方法より優れているわけではありません。すべての問題と同様に、必要な動作を理解し、最も適切な方法を選択することが重要です。

62
David

私にとっては、状態を保存することはせいぜい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;
54
Mike A.

私は答えを見つけたと思います。私がしたことを簡単な言葉で説明しましょう。

アクティビティ1とアクティビティ2の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を表示できます。

これが答えだと私は信じています。これは私にとってはうまくいきます。間違っていれば訂正してください。

51
roy mathew

一時データ用のonSaveInstanceState()onCreate()/onRestoreInstanceState()に復元)、永続データ用のonPause()onResume()に復元) Androidの技術資料から:

onSaveInstanceState() アクティビティが停止されている場合にAndroidによって呼び出され、再開される前に強制終了される可能性があります。これは、アクティビティが再起動されたときに同じ状態に再初期化するために必要なすべての状態を格納する必要があることを意味します。これはonCreate()メソッドの対応物であり、実際にonCreate()に渡されるsavedInstanceState Bundleは、onSaveInstanceState()メソッドでoutStateとして構築したものと同じBundleです。

onPause() および onResume() も相補的なメソッドです。 onPause()は、アクティビティが終了したときに常に開始されていても、常に呼び出されます(たとえばfinish()呼び出し)。これを使って現在のメモをデータベースに保存します。 onPause()中に解放される可能性のあるリソースを解放することをお勧めします。パッシブ状態にあるときに占めるリソースは少なくなります。

37
Ixx

アクティビティがバックグラウンドになったとき、本当にonSaveInstance状態が落ちています

ドキュメントからの引用: "メソッドをそのようなバックグラウンド状態にする前にメソッドonSaveInstanceState(Bundle)が呼び出されます"

34
u-foka

ボイラープレートを減らすために、インスタンス状態を保存するためにinterfaceを読み書きするために次のclassBundleを使います。


まず、インスタンス変数に注釈を付けるために使用されるインターフェースを作成します。

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 license の下でライセンスされている AndroidAutowire という名前のライブラリプロジェクトから修正されました。

29
Jared Rummler

一方、私は一般的にこれ以上使用しないでください

Bundle savedInstanceState & Co

ライフサイクルはほとんどの活動にとって非常に複雑で必要ではありません。

そしてグーグルは自信を持っている、それも信頼できない。

私の方法は、設定に変更をすぐに保存することです。

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

ある意味ではSharedPreferencesはBundlesのように機能します。そして当然のことながら、最初はそのような値を設定から読み取る必要があります。

複雑なデータの場合は、設定を使う代わりにSQLiteを使うことができます。

この概念を適用すると、アクティビティは、最後に保存された状態をそのまま使用し続けます。これは、最初に開いた状態で、その間に再起動が行われたか、バックスタックが原因で再開されたものです。

29
stefan bachert

元の質問に直接答える。 Activityが再作成されることはないため、savedInstancestateはnullです。

次の場合にのみ、アクティビティはステートバンドルで再作成されます。

  • オリエンテーションや電話言語の変更など、構成の変更により、新しいアクティビティインスタンスを作成する必要が生じる場合があります。
  • OSがアクティビティを破棄した後は、バックグラウンドからアプリに戻ります。 

メモリが圧迫されているとき、または長期間バックグラウンドにいると、Androidはバックグラウンドアクティビティを破棄します。

あなたのハローワールドの例をテストするとき、アクティビティから出て戻るためのいくつかの方法があります。

  • 戻るボタンを押すと、アクティビティは終了します。アプリを再起動すると、まったく新しいインスタンスになります。あなたは背景からまったく再開していません。
  • ホームボタンを押すかタスクスイッチャーを使用すると、アクティビティはバックグラウンドになります。アプリケーションに戻るときにonCreateはActivityを破棄しなければならない場合にのみ呼び出されます。 

ほとんどの場合、自宅に戻ってアプリを再起動しただけでは、アクティビティを再作成する必要はありません。それはすでにメモリ内に存在するので、onCreate()は呼ばれません。

「設定」 - >「開発者向けオプション」の下には、「アクティビティを保持しない」というオプションがあります。 Androidが有効になっていると、アクティビティは常に破壊され、バックグラウンドになったときに再作成されます。これは、最悪のシナリオをシミュレートするため、開発時に有効にしておくのに最適なオプションです。 (あなたの活動を常にリサイクルしている低メモリデバイス)。

他の答えはそれらがあなたに状態を保存する正しい方法を教えるという点で価値がありますが、私は彼らがあなたのコードがあなたが期待したようにはたらいていなかった理由本当に答えなかった。

28
Jared Kells

onSaveInstanceState(bundle)メソッドとonRestoreInstanceState(bundle)メソッドは、画面を回転させている間(データの向きを変更する)単にデータを永続化するのに役立ちます。
onSaveInstanceState()メソッドは呼び出されますが、onCreate(bundle)onRestoreInstanceState(bundle)は再度呼び出されないため)これらはアプリケーション間の切り替え時にはさらに良くありません。
持続性を高めるには、共有設定を使用してください。 この記事を読んでください

24
Mahorad

私が抱えていた問題は、アプリケーションの存続期間(つまり、同じアプリケーション内で他のサブアクティビティを起動してデバイスを回転させるなどの1回の実行)の間だけ持続性が必要なことでした。私は上記の答えのさまざまな組み合わせを試しましたが、私がすべての状況で欲しいものを得ませんでした。結局、私にとってうまくいったのは、onCreateの間にsavedInstanceStateへの参照を取得することでした。

mySavedInstanceState=savedInstanceState;

次の行に沿って、必要に応じて変数の内容を取得するためにそれを使用します。

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

上記のようにonSaveInstanceStateonRestoreInstanceStateを使用していますが、変数の変更時に変数を保存するために私の方法を使用することもできます(例:putBooleanを使用)。

16
torwalker

受け入れられた答えは正しいですが、 Icepick と呼ばれるライブラリを使用してAndroid上でActivity状態を保存するためのより速くそしてより簡単な方法があります。 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で保存する任意のオブジェクトで動作します。

15
Kevin Cronly

アクティビティが作成されると、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
    }

outState.putString( "key"、 "Welcome Back")のように "outState" Bundleオブジェクトに値を入れて、superを呼び出して保存します。 onCreate()またはonRestoreInstanceState()で再作成した後。 onCreate()とonRestoreInstanceState()で受け取ったバンドルは同じです。

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

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

または

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

この変更を実装するには、基本的に2つの方法があります。

  1. onSaveInstanceState()onRestoreInstanceState()を使います。
  2. マニフェストAndroid:configChanges="orientation|screenSize"に。

私は本当に2番目の方法を使用することをお勧めしません。私の経験の1つでは、ポートレートからランドスケープ、またはその逆に回転すると、デバイス画面の半分が黒くなっていました。 

上記の最初の方法を使用すると、方向が変更されたときや設定が変更されたときにデータを永続化することができます。

例: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); 

}
12
Krishna

これは Steve Moseley の回答( ToolmakerSteve による)からのコメントです(全体としてonSaveInstanceState対onPause、east cost対west cost saga)。

@VVK - 部分的に同意しません。アプリを終了するいくつかの方法は onSaveInstanceState(oSIS)をトリガーしません。これはoSISの有用性を制限します。最小限のOSリソースでサポートする価値はありますが、アプリがどのように終了したとしても、アプリがユーザーを元の状態に戻すことを望む場合は、それは重要です。代わりに永続的ストレージアプローチを使用する必要があります。[ - ____] バンドルをチェックするためにonCreateを使用し、それが見つからない場合は 永続的ストレージをチェックします。 これは意思決定を集中化します。私はクラッシュから回復することができます、または戻るボタンの終了またはカスタムメニュー項目の終了、またはユーザーは何日も後にスクリーンに戻ることができます。 - ツールメーカーSteve Sep 19'15 at 10:38

8
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

Optionalsを使用したくない場合は、デフォルト値を追加してください。

7
Rafols

この問題を簡単に解決するには 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はカスタムViewsのインスタンス状態コードを生成することもできます。

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
}
5
THANN Phearum

私の解決策が眉をひそめているかどうかわからないが、私はViewModel状態を持続させるためにバウンドサービスを使用する。サービス内のメモリに保存するか、SQLiteデータベースから永続化して取得するかは、要件によって異なります。これはどのような種類のサービスでも実行できることであり、アプリケーションの状態の維持や抽象共通ビジネスロジックなどのサービスを提供します。 

モバイルデバイスに固有のメモリと処理の制約のために、私はWebページと同じようにAndroidのビューを扱います。ページは状態を維持しません。それは純粋にプレゼンテーション層コンポーネントであり、その唯一の目的はアプリケーションの状態を提示し、ユーザー入力を受け入れることです。 Webアプリケーションアーキテクチャの最近の傾向では、ページがView、ドメインデータがモデル、そしてコントローラがWebサービスの背後にある、古くからあるModel、View、Controller(MVC)パターンを使用しています。 Viewと同じパターンをAndroidで使用することもできます。View、モデルはドメインデータであり、ControllerはAndroidバウンドサービスとして実装されています。ビューがコントローラとやり取りするときはいつでも、開始/再開時にそれにバインドし、停止/一時停止時にバインド解除します。

このアプローチでは、すべてのアプリケーションビジネスロジックをサービスに移動して、複数のビューにまたがる重複ロジックを削減し、ビューでもう1つの重要な設計原則である単一責任を強制できるため、懸案事項を追加できます。

5
ComeIn

onCreate()に保存されているアクティビティ状態データを取得するには、まずSaveInstanceState(Bundle savedInstanceState)メソッドをオーバーライドしてsavedInstanceStateにデータを保存する必要があります。

アクティビティがSaveInstanceState(Bundle savedInstanceState)メソッドを破棄したときに、保存したいデータをそこに保存します。アクティビティが再開されてもonCreate()は同じになります(アクティビティが破棄される前にデータを保存しているため、savedInstanceStateはnullにはなりません)。

5
ascii_walker

今すぐAndroidは ViewModels 状態を保存するために提供します、あなたはsaveInstanceStateの代わりにそれを使用しようとするべきです。

2
M Abdul Sami

何を保存し、何を保存しないでください。

向きが変わってもなぜEditText内のテキストが自動的に保存されるのでしょうか。まあ、この答えはあなたのためです。

アクティビティのインスタンスが破棄され、システムが新しいインスタンスを再作成した場合(設定の変更など)古いアクティビティ状態( インスタンス状態 )の保存されたデータのセットを使用してそれを再作成しようとします。

インスタンス状態は、Bundleオブジェクトに格納された Key-Value のペアのコレクションです。

デフォルトでは、システムはビューオブジェクトをバンドルに保存します。

  • EditText内のテキスト
  • ListViewなどで位置をスクロールする.

インスタンス状態の一部として別の変数を保存する必要がある場合は、 _ override _ 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)

OR

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)はシステムによって呼び出されません。データを保存する際は、すべてのシナリオを考慮する必要があります。

関連リンク:

デフォルト動作のデモ
Androidの公式ドキュメント

0
Rohit Singh

コトリン

永続化したい変数を格納および取得するには、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
}
0

もっと良いアイデアがあります。これはonCreateを再度呼び出さずにデータを保存するのに適しています。向きが変わったときは、アクティビティから無効にすることができます。

あなたのマニフェストでは:

<activity Android:name=".MainActivity"
        Android:configChanges="orientation|screenSize">
0
Hossein Karami