かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Xamarin AndroidでActivityにライフサイクルを確認してみた

過去記事

Activityのライフサイクル

Activityのライフサイクルについて説明します。Activityのライフサイクルで呼び出されるメソッドは以下の7つがあります。があります。

  • OnCreateメソッド: Activityが作成されたときに呼び出される。
  • OnStartメソッド: アクティビティがユーザーに表示される直前に呼び出される。
  • OnResumeメソッド: アクティビティがユーザーとの操作が開始される直前に呼び出される。
  • OnPauseメソッド: 別のアクティビティを表示する直前に呼び出される
  • OnStopメソッド: アクティビティがユーザーから見えなくなると表示される
  • OnDestroyメソッド: アクティビティが破棄される前に呼び出される
  • OnRestartメソッド: アクティビティが停止したあと再開する直前に呼び出される

通常は、OnCreateメソッドで初期化処理を行い、OnPauseメソッドで未保存の永続化データを保存すると良いでしょう。別のActivityが表示されると、もともと表示されていたActivityは、通常OnPause→OnStopが呼び出されて次の表示が来るまで待ちますが、メモリが圧迫されたりするとActivityが破棄されたりします。そうなると、次に戻ってきたときはOnCreateからやり直しになります。その時、EditTextなどの入力途中のデータなどは、IDが振られていると、自動的に復元されます。その他のユーザーがActivityのフィールドなどに保持していたデータは何もしないとクリアされてしまいます。これに対応するためには、OnSaveInstanceStateメソッドをオーバーライドして、Bundleにデータを保存します。OnCreateの引数で渡されるBundleがnullじゃないときは、保存されたデータがあるということなので、データの復元をBundleから行います。 この動作を確認するためのプログラムを以下に示します。MainActivityとNextActivityを持っただけのシンプルなプロジェクトで、以下のようなコードを記述します。

using Android.App;
using Android.Content;
using Android.OS;
using Android.Util;
using Android.Views;
using Java.Interop;
using System;

namespace ActivityLifecycle
{
    [Activity(Label = "ActivityLifecycle", MainLauncher = true, Icon = "@drawable/icon")]
    public class MainActivity : Activity
    {
        private string Id { get; set; }

        protected override void OnSaveInstanceState(Bundle outState)
        {
            base.OnSaveInstanceState(outState);
            Log.Debug(nameof(MainActivity), $"{nameof(OnSaveInstanceState)}: {this.Id}");
            outState.PutString(nameof(Id), this.Id);
        }

        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);

            // Set our view from the "main" layout resource
            this.SetContentView(Resource.Layout.Main);

            if (bundle == null)
            {
                this.Id = Guid.NewGuid().ToString();
            }
            else
            {
                this.Id = bundle.GetString(nameof(Id));
            }
            Log.Debug(nameof(MainActivity), $"{nameof(OnCreate)}: {this.Id}");
        }

        protected override void OnStart()
        {
            base.OnStart();
            Log.Debug(nameof(MainActivity), $"{nameof(OnStart)}: {this.Id}");
        }

        protected override void OnResume()
        {
            base.OnResume();
            Log.Debug(nameof(MainActivity), $"{nameof(OnResume)}: {this.Id}");
        }

        protected override void OnRestart()
        {
            base.OnRestart();
            Log.Debug(nameof(MainActivity), $"{nameof(OnRestart)}: {this.Id}");
        }

        protected override void OnPause()
        {
            base.OnPause();
            Log.Debug(nameof(MainActivity), $"{nameof(OnPause)}: {this.Id}");
        }

        protected override void OnStop()
        {
            base.OnStop();
            Log.Debug(nameof(MainActivity), $"{nameof(OnStop)}: {this.Id}");
        }

        protected override void OnDestroy()
        {
            base.OnDestroy();
            Log.Debug(nameof(MainActivity), $"{nameof(OnDestroy)}: {this.Id}");
        }

        [Export(nameof(MyButtonClick))]
        public void MyButtonClick(View v)
        {
            this.StartActivity(new Intent(this, typeof(NextActivity)));
        }
    }
}

ポイントは、OnSaveInstanceStateメソッドとOnCreateメソッドになります。それ以外はボタンが押されたときの画面遷移の処理と、各ライフサイクルメソッドが呼び出されたかログを出力するためのものになります。アプリケーションを実行してMainActivityを表示するとログは以下のようなものが表示されます。

08-20 15:36:59.111 D/MainActivity( 2702): OnCreate: 6986edf6-5759-46a9-98be-4f9185ad23de
08-20 15:36:59.116 D/MainActivity( 2702): OnStart: 6986edf6-5759-46a9-98be-4f9185ad23de
08-20 15:36:59.117 D/MainActivity( 2702): OnResume: 6986edf6-5759-46a9-98be-4f9185ad23de

そして、NextActivityへ遷移すると以下のように表示されます。

08-20 15:37:51.231 D/MainActivity( 2702): OnPause: 6986edf6-5759-46a9-98be-4f9185ad23de
08-20 15:37:52.000 D/MainActivity( 2702): OnSaveInstanceState: 6986edf6-5759-46a9-98be-4f9185ad23de
08-20 15:37:52.001 D/MainActivity( 2702): OnStop: 6986edf6-5759-46a9-98be-4f9185ad23de

そして、戻るボタンでMainActivityに戻ると以下のように表示されます。

08-20 15:39:41.493 D/MainActivity( 2702): OnRestart: 6986edf6-5759-46a9-98be-4f9185ad23de
08-20 15:39:41.493 D/MainActivity( 2702): OnStart: 6986edf6-5759-46a9-98be-4f9185ad23de
08-20 15:39:41.493 D/MainActivity( 2702): OnResume: 6986edf6-5759-46a9-98be-4f9185ad23de

OnDestroyメソッドが呼び出されていないので、Idは保持されたままなのは当然ですよね。次に、実機の開発者オプションで「アクティビティを保持しない」を選択して、疑似的にメモリ不足などでActivityが破棄された時の動作をエミュレートしてみたいと思います。実行してMainActivityが表示されると以下のようなログが出ます。

08-20 15:42:38.221 D/MainActivity(31579): OnCreate: dbc9cedb-a67a-4e61-b6b1-911925fbd001
08-20 15:42:38.223 D/MainActivity(31579): OnStart: dbc9cedb-a67a-4e61-b6b1-911925fbd001
08-20 15:42:38.225 D/MainActivity(31579): OnResume: dbc9cedb-a67a-4e61-b6b1-911925fbd001

そして、NextActivityへ遷移を行います。

08-20 15:43:10.217 D/MainActivity(31579): OnPause: dbc9cedb-a67a-4e61-b6b1-911925fbd001
08-20 15:43:10.667 D/MainActivity(31579): OnSaveInstanceState: dbc9cedb-a67a-4e61-b6b1-911925fbd001
08-20 15:43:10.673 D/MainActivity(31579): OnStop: dbc9cedb-a67a-4e61-b6b1-911925fbd001
08-20 15:43:10.689 D/MainActivity(31579): OnDestroy: dbc9cedb-a67a-4e61-b6b1-911925fbd001

OnDestroyメソッドが呼び出されてMainActivityが破棄されたことが確認できます。この状態で戻るボタンを押してMainActivityに戻ると以下のように表示されます。

08-20 15:44:12.607 D/MainActivity(31579): OnCreate: dbc9cedb-a67a-4e61-b6b1-911925fbd001
08-20 15:44:12.609 D/MainActivity(31579): OnStart: dbc9cedb-a67a-4e61-b6b1-911925fbd001
08-20 15:44:12.624 D/MainActivity(31579): OnResume: dbc9cedb-a67a-4e61-b6b1-911925fbd001

一度MainActivityが破棄されたにも関わらず、Idの値が保持されていることが確認できます。繰り返しますが、画面に置かれた部品でIDが振られたものは自動的に値が保持されるため気にする必要はありませんが、今回のように自分でActivityに一時的に保持しているものは自前で保存と復元を行う必要があります。