android - 破棄 - savedinstancestate null




インスタンスの状態を保存してAndroidのアクティビティの状態を保存する (18)

あなたのプロジェクトにLiveData(Androidアーキテクチャコンポーネント)を追加する

次の依存関係を追加する

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

LiveDataはオブザーバを取り込み、STARTEDまたはRESUMED状態のときにのみデータ変更について通知します。LiveDataの利点は、アクティビティがSTARTEDまたはRESUMED以外の状態になったときに、オブザーバでonChangedメソッドを呼び出さないことです。

private TextView mTextView;
private MutableLiveData<String> mMutableLiveData;

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

}

私は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やそのようなものをオーバーライドするのとonPauseシンプルだと確信していますが、私は30分ほどドキュメントを離れています。


developer.android.com/reference/android/app/Activity.htmlアクティビティの状態に関するドキュメントに従って、永続データの onSaveInstanceStateonRestoreInstanceStateを使用するのは安全ではないことに注意してください。

ドキュメントには、(アクティビティのライフサイクルのセクションで)次のように記載されています。

onSaveInstanceState(Bundle) onPause()ではなくonPause()に永続データを保存することが重要です。これは、ライフサイクルコールバックの一部ではないため、そのドキュメントに記載されているすべての状況で呼び出されることはないためです。

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

編集 :より明確にするために、ここonSaveInstanceState()ドキュメント:

このメソッドは、アクティビティが強制終了される前に呼び出されます。これにより、将来何らかの時刻に戻ったときに状態を復元できるようになります。 たとえば、アクティビティAの前でアクティビティBが起動され、アクティビティAがリソースを再利用するために殺された場合、アクティビティAはこのメソッドを介してユーザインターフェイスの現在の状態を保存し、アクティビティAには、 onCreate(Bundle)またはonRestoreInstanceState(Bundle)使用してユーザーインターフェイスの状態を復元できます。


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


savedInstanceStateは、現在のアクティビティのインスタンス(現在のナビゲーションや選択情報など)に関連付けられた状態を保存するためのものです。したがって、Androidがアクティビティを破棄して再作成した場合、元の状態に戻ることができます。 onCreateonSaveInstanceStateドキュメントを参照してください。

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


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

  1. とを使っonSaveInstanceState()onRestoreInstanceState()
  2. マニフェスト内android:configChanges="orientation|screenSize"

私は本当に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); 

}

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

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

これらすべてのスレッドをブラウズすると、開発者はこれらの2つの異なる問題について同時に話し合っていると思います。したがって、「これは私にとってはうまくいかない」という混乱と報告はすべてあります。

まず、「意図された」振る舞いを明確にするために、onSaveInstanceとonRestoreInstanceは脆弱で、過渡状態の場合のみです。 意図された使用法(afaict)は、電話機を回転させたときの活動レクリエーション(向きの変更)を処理することです。 言い換えれば、あなたのアクティビティが依然として論理的に「上に」あるが、それでもシステムによって再インスタンス化されなければならないことが、意図される使用である。 保存されたBundleはプロセス/メモリ/ 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フラグで起動され、その後、ホーム画面またはランチャーメニューから起動すると、その次の呼び出しでNEWタスクが作成されます...実際には、アプリケーションの2つの異なるインスタンス同じスタックに住んでいる...非常に奇妙な非常に高速になる。 これは、開発中に(EclipseやIntellijから)アプリを起動するときに発生すると思われます。開発者はこれを多く実行します。 また、アプリストアの更新メカニズムの一部でも(ユーザーにも影響を与えます)

私は主な問題が意図されたフレームワークの振る舞いではなく、このバグであることに気付く前に、これらのスレッドを何時間も戦っていました。 素晴らしい書き写しと 回避策 (UPDATE:下記参照)は、この回答のユーザー@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;
    }
}

アクティビティがバックグラウンドになったときに本当にonSaveInstance状態のonSaveInstance

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


アクティビティが作成されると、onCreate()メソッドが呼び出されます。

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

savedInstanceStateは、初めてBundleクラスのオブジェクトですが、再作成時に値を含みます。アクティビティの状態を保存するには、onSaveInstanceState()をオーバーライドする必要があります。

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

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

一時データ( onCreate() / onRestoreInstanceState() )、 onPause() 、永続データ( onResume()リストアされたonResume() Androidの技術リソースから:

onSaveInstanceState()は、アクティビティが停止されている場合にAndroidから呼び出され、再開する前に強制終了される可能性があります。 つまり、アクティビティが再開されたときに同じ条件に再初期化するのに必要な状態を保存する必要があります。 これはonCreate()メソッドに対応しており、onCreate()に渡されたsavedInstanceStateバンドルは、onSaveInstanceState()メソッドでoutStateとして構築するバンドルと同じバンドルです。

onPause()onResume()はまた、補完的なメソッドです。 onPause()は、アクティビティが終了したときに呼び出されます(たとえfinish()コールなど)。 これを使用して、現在のメモをデータベースに保存し直します。 良い方法は、onPause()の間に解放できるリソースをすべて解放して、受動的な状態になってもリソースを少なくすることです。


私が懸念している限り、状態を保存することはせいぜい厄介です。 永続データを保存する必要がある場合は、 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;

私は一般的にはもう使用しません

Bundle savedInstanceState & Co

ライブサイクルは、ほとんどのアクティビティが複雑すぎる必要はありません。 そして、Google自体は、それは信頼性がありません。

私のやり方は、環境設定に変更をすぐに保存することです

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

SharedPreferencesは何らかの形でBundlesのように動作します。そして当然のことながら、当初はそのような値は好みから赤色でなければなりません。

複雑なデータの場合は、環境設定を使用する代わりにSqliteを使用することができます。

このコンセプトを適用すると、アクティビティは、間に再起動して最初に開いたか、バックスタックによって再オープンしたかにかかわらず、最後に保存された状態をそのまま使用し続けます。


私は答えを見つけたと思う。 簡単な言葉で私が何をしたか教えてください:

アクティビティ1とアクティビティ2の2つのアクティビティがあり、アクティビティ1からアクティビティ2への移動中(アクティビティ2でいくつかの作業を行っています)、アクティビティ1のボタンをクリックしてアクティビティ1に戻ります。 今この段階で私はアクティビティ2に戻りたいと思っていました。私は最後にアクティビティ2を残したときと同じ状態で自分のアクティビティ2を見たいと思っています。

上記のシナリオで私がしたことは、マニフェストでこのようないくつかの変更を加えたことです:

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

そして、ボタンクリックイベントのアクティビティ2では、私はこのようにしました:

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

今、何が起こるかは、アクティビティ2で行った変更が失われることはなく、以前に残したのと同じ状態でアクティビティ2を見ることができるということです。

私はこれが答えであり、これは私にとってうまくいくと信じています。 私が間違っているなら、私を訂正してください。


ここからのコメントであるスティーブ・モーズリー(での答えToolmakerSteve(onPause、西コスト佐賀VS東費用対全体onSaveInstanceStateで)大局的に物事を置きます)

@VVK - 私は部分的に反対です。アプリを終了するいくつかの方法はonSaveInstanceState(oSIS)を起動しません。これにより、oSISの有用性が制限されます。たとえアプリケーションがどのように終了したとしても、永続的なストレージアプローチを使用する必要があります。私はバンドルをチェックするためにonCreateを使い、見つからない場合は 永続ストレージをチェックします。これは意思決定を集中化させます。クラッシュや復帰ボタンの終了やカスタムメニュー項目の終了から復旧したり、数日後に画面に戻ることができます。 - ToolmakerSteve Sep 19、15:10:3​​8


この問題を簡単に解決するには、Icepickを使用してい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
}

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

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


元の質問に直接答える。アクティビティが決して再作成されないため、savedInstancestateはnullです。

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

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

Androidはメモリ不足のときや、長時間バックグラウンドにいるときにバックグラウンドの活動を破壊します。

あなたのこんにちは世界の例をテストする際には、いくつかの方法があります。

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

ほとんどの場合、あなたが家を押してからアプリをもう一度起動すると、アクティビティは再作成する必要はありません。onCreate()は呼び出されないように、すでにメモリに存在しています。

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

他の答えは、状態を保存する正しい方法を教えるという点で貴重ですが、あなたのコードが期待どおりに動作していなかった理由は本当に答えられたとは思いませんでした。


定型を減らすために、私は以下を使用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);
    }

}

注:このコードは、MITライセンスでライセンスされているAndroidAutowireという名前のライブラリプロジェクトから変更されました。


私のソリューションが悩んでいるかどうかはわかりませんが、私はViewModel状態を維持するためにバインドされたサービスを使用しています。サービス内のメモリに格納するか、SqlLiteデータベースから永続化して取得するかは、要件によって異なります。これはどんなフレーバーのサービスでも、アプリケーション状態の維持や共通のビジネスロジックの抽象化などのサービスを提供します。

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

このアプローチは、アプリケーションのビジネスロジックをすべてあなたのサービスに移すことができ、複数のビュー間で重複したロジックを減らし、ビューに別の重要な設計原則、単一責任を適用できるようにするという、分離の懸念の設計原則を強化するという追加の特典を与えます。







application-state