Activityのライフサイクルを管理する
アクティビティのライフサイクルについて、具体的にアプリの画面だとどんな状態になっているのかスクショを用いて説明している記事とか見かけないので、自分なりに解釈してみました。
アクティビティの状態は大きく分けると3つに分かれるようです。
再開状態
一時停止状態
停止状態
再開状態
別名「実行中」 つまり、アプリがフォアグランド(一番前)にいて、ユーザからのフォーカスが当たっている状態 つまり、
この状態かなと
一時停止状態
他のアクティビティがフォアグラウンドに出ているが、アクティビティが生きている状態。 他のアクティビティは画面全体を覆っているわけではなく、一部が表示されている感じ。 イメージでいうと、
こんな感じ? 厳密にはアクティビティではなく、DialogFragmentなので違いますが、あくまでイメージとして。 バックのアクティビティは生きていますが、極端にメモリが足りなくなった時は破棄されます。
停止状態
他のアクティビティがフォアグランドに来たので、現在のアクティビティがバックグラウンドに隠れて見えない状態です。(若干表現はおかしいかも?)
例えば、
さがすアクティビティから上部の検索バーを押すと、
検索アクティビティに遷移します。
これは、さがすアクティビティがバックグラウンドにいき、フォアグラウンドに検索アクティビティがきた状態です。
この時、さがすアクティビティは停止状態となっています。
別のアクティビティでメモリが必要となった場合は、さがすアクティビティはシステムに強制終了される可能性があります。
以上が、大きく分けて、アクティビティの3つの状態です。
その上で、よく見かけるアクティビティのライフサイクル図を見てみます。
コールバックがいくつもありますが、結局は先ほどの3つの状態のいずれかで呼び出されることを念頭に置けば、さほどややこしくはなさそうです。
次回は各コールバックがどういう時に呼び出されるのか整理していきます。
AndroidのActivityについて整理してみる
Androidのコンポーネントとして、基本中の基本であるActivity
開発している中で必ずと言っていいほど触るし、目にしない日はありません。
しかし、身近にあるからこそ灯台もと暗し、という訳ではないが見落としがちになります。
今回、そんなわかってそうでわかっていないActivityについて整理してみます。 今回はActivityの概要とバックスタックについて考えます。
Activityとは
developer guideの言葉を借りるならば、電話をかける、メールをするといった、アプリケーションを使ってユーザがしたい操作をユーザができるようにするためのUIを提供するためのアプリケーションコンポーネント。
大抵のアプリはこのActivityを複数持っていて、それらは緩やかな繋がりを持っています。(密結合になってはいけない)
ユーザがアプリを起動した時にまず表示されるActivityはMainActivityです。
そこから派生して、各Activityを起動していきます。
バックスタック
画面上から見えなくなったActivityはバックスタックと呼ばれるスタックに積まれていきます。
図では、MainActivityにSubActivityが積まれていますが、これはMainActivityからSubActivityを起動した状態です。 起動時にSubActivityがプッシュされて、画面上からはSubActivityが見えています。
バックボタンを押した時などは、SubActivityがバックスタックからポップされて、MainActivityが再び画面上に配置されます。 この辺はブラウザの仕組みに近いかなと。
今回はここまで
次回はライフサイクルについて整理してみます。
脳を休める
最近、頭がずっとボーっとしている
体力が回復してないのかなー?と思って、温泉に行ってみたり、マッサージを受けてみたりしているが、疲れている。
考えるのが難しい。考えようとするのだけど、頭がボーっとしている。
「疲れ」というものを分析してみる。
たぶん、「肉体的」疲れと「精神的」疲れがあって、「肉体的」には健康なんだと思う。
実際、湯船には毎日つかるようにしているし、マッサージにも行っている。何なら、睡眠の質をあげるためにベッドまで買い替えた。
となると、「精神的」疲れだが、これには大いに心当たりがある。
悩みは腐るほどあるからだ。
アドラー心理学的には、人間の悩みは人間関係に帰着するらしいけれど、確かに自分の悩みはだいたい人間関係である。
生活中、つねにいろいろ考えてしまって、脳を常に使っている気はしている。余計に。
脳を休ませるにはどうしたらいいのか?軽くググってみると、マインドフルネスという単語を見つけた。
細かいことまで調べていないが、要は瞑想のことらしい。(正しいかは不明)
確かに、意図的に考えることをやめる時間を設けるのはいいかもしれない。 無心になる訓練をしよう。
ということで、毎日5分ほど瞑想してみることにします。
脳を休ませたいねえ。
AndroidのDataBindingをtwo way にしたいとき
公式のドキュメントだとパッと見つけられないのでメモ
通常のDataBinding ViewModelの値を更新 → Viewも更新
TwoWayの場合 ViewModelの値を更新 → Viewも更新 Viewを更新 → ViewModelの値を更新
というように、ViewModel、View双方にObserveし合って、更新することができます。 ぐっとコードの記述量が減り、便利です。
BaseObservableを継承したViewModelを実装
public class ProfileViewModel extends BaseObservable { private int id; private String name; private boolean hasDetails; public int getId() { return id; } public String getName() { return name; } public boolean isHasDetails() { return hasDetails; } public void setId(int id) { this.id = id; } public void setName(String name) { this.name = name; } public void setHasDetails(boolean hasDetails) { this.hasDetails = hasDetails; } }
ViewModelクラスはJavaBeansのプロパティ設定に則って、setter/getterが getプロパティ名 setプロパティ名
なので、 booleanの場合は isプロパティ名 setプロパティ名
です。
@={viewModel.name}
通常であれば、
<EditText android:height="wrap_content" android:width="wrap_content" android:text="@{viewModel.name}"/>
とするところを、
<EditText android:height="wrap_content" android:width="wrap_content" android:text="@={viewModel.name}"/>
のように、@の後ろに=を足してあげることで、EditTextに入力したStringがviewModelのsetName(String name)の引数nameに渡されます。 しかし、このままでは、viewModelのnameが変更されたことがViewに反映されません。
<TextView android:height="wrap_content" android:width="wrap_content" android:text="@{viewModel.name}"/> <EditText android:height="wrap_content" android:width="wrap_content" android:text="@={viewModel.name}"/>
のように、nameが別のViewに使われていた場合、そちらに変更が伝わりません。 そこで、@Bindableアノテーションを使います。
@Bindable
変更を通知してもらいたいところに、@Bindableアノテーションを付けます。
@BIndable public String getName() { return name; }
@Bindableアノテーションを付けたフィールドは、BRクラスというクラスのフィールドとして定義されます。 そして、
public void setName(String name) { this.name = name; notifyPropertyChanged(BR.name); }
のように、変更されたときに、変更をnotifyPropertyChanged()メソッドによって伝えることができ、Viewに変更が伝えられます。 なお、notifyPropertyChanged(BR._all) を指定することで、@Bindableアノテーションを付けたフィールド全部に変更を伝えることができます。
オブジェクトのやり取りにもの思う
Androidの話になりますが、例えばAPIからのレスポンスをJavaのentityとして保持していて、 Intentを飛ばして受け取る先でもそのentityを使いたい、という場面があるとします。
その際、考えられるのは
- Intentを受け取る先で再びAPIを叩く
- entityをIntentのpuExtra系メソッドでなんとか受け渡しする
- メモリキャッシュなど、キャッシュしておいてIntent受け取り先でそのキャッシュからentity復元
あたりかなと思います。(他にあったらごめんなさい)
ナンセンスなのは1です。値が必要なたびにAPIを叩いていたらロードでユーザを待たせることになりますし、APIサーバへのアクセスを頻繁にさせることで電池の消耗も激しくします。(Ream Academyにそんな話があった気が... Becoming a Better Battery Citizen)
2のIntentで受け渡しするのはいくつか方法があります。
- Parcelableとしてentityを実装
- Gsonを使ってjson変換したものをStringとして受け渡し
ちなみに、うちのプロダクトコードではParcelableとして実装されたentityが受け渡されています。
うちのプロダクトのコードでよく見る例
public class Profile implements Parcelable { private int id; private String name; protected Profile(Parcel in) { id = in.readInt(); name = in.readString(); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(id); dest.writeString(name); } @SuppressWarnings("unused") public static final Parcelable.Creator<Profile> CREATOR = new Parcelable.Creator<Profile>() { @Override public CustomerProfile createFromParcel(Parcel in) { return new Profile(in); } @Override public Profile[] newArray(int size) { return new Profile[size]; } } }
entityとなるオブジェクトをParcelableとして実装しています。 Parcelableインタフェースを実装することで、そのオブジェクトをParcelとして扱うことができます。 (Parcelは世の中色々ありますが、Androidにおいてはこちらを参照 https://developer.android.com/reference/android/os/Parcel.html)
Intentには、putExtra系のメソッドがあり、Intentにデータを渡すことができます。 その中に、putExtra(String name, Parcelable value) があり、オブジェクトを直接渡すことはできませんが、Parcelableとして実装したオブジェクトを渡すことができます。
しかし、これには難点があり、なんといっても実装が面倒臭い。(コードジェネレータ・プラグインやアノテーションライブラリでなんとかする方法もあるが)
事実、この形のコードは今置き換えられているところです。
次に、Gson変換してStringとして受け渡しする方法もあります。
Gsonは、Googleの開発、保守しているライブラリで、シンプルなインタフェースでPOJOからJsonへのシリアライズ、またでシリアライズを行うことができます。 シリアライズされたJsonは、Stringとして扱うことができます。
public class Profile { private static final Gson GSON = new GsonBuilder().create(); @SerializedName("user_id") private int id; @SerializedName("user_name") private String name; public static Profile fromJson(String json) { if (json == null) return null; return GSON.fromJson(json, Profile.class); } public String toJson() { return GSON.toJson(this); } }
先ほどと比べてだいぶすっきりしました。 Intentを投げる側ではputExtra(String key, String value)で投げて、受け取り側は受け取った後でGson.fromJsonででシリアライズしてあげればいいだけです。
Intentのデータとして受け渡しする難点
しかし、Parcelableとして実装するにしろ、Gsonでシリアライズするにしろ、難点がいくつかあります。
- アクティビティの結合が密になる。
- オブジェクトのデータがでかくなると、クラッシュの危険が高くなる。
まず、必要なIntentのパラメータが増えるとアクティビティ感の結合が密になります。 例えば、MainActivityからProfileActivityに遷移する際、ProfileActivityが前のアクティビティからのProfileを期待している場合、 Profileを持っていないアクティビティからの遷移はできなくなります。 ProfileActivityの振る舞いは、遷移元のActivityに依存します。
また、オブジェクトがListで、アルバムなどの大量のデータを保持する場合、Intentはそんなに大きなデータを持つよう想定されていないため、クラッシュします。(実体験)
Intentのパラメータはuser_idなどのインデックスに留めておきたいものです。
キャッシュをしよう
そこで、 3. メモリキャッシュなど、キャッシュしておいてIntent受け取り先でそのキャッシュからentity復元 が今の所望ましい形なのかなと考えています。
アーキテクチャとしてrepositoryを用意し、Activityは何も考えずにrepositoryからデータを取ってくるメソッドを叩く。 repositoryはキャッシュの有無に応じて、RemotoDataSourceから取ってくるか、LocalDataSourceからとってくるか選択する。 repositoryのクライアントはデータの出どころは知る必要なく、データを取得することができる。
この方法がアクティビティ間の結合を疎にできて、ユーザがロード時間にストレスを感じなくて済み、開発者は実装に時間を取られずに済む方法かな?と最近は思っています。
実際、プロダクトにまだrepositoryを完全に導入できていなくて、ParcelableになっているところをGsonで簡素化してごまかしている節があるので、早いところ整理したいものです。
REALFORCE買いました
REALFORCEの87UB-5587買いました。
キーボードに対してこだわりはなかったのですが、 入社してから先輩エンジニアたちが割とこだわっていたこと また、ビルド中にマシンが熱くなって手を置いているのが辛いこと から購入しました。
静電容量無接点方式、良い。 この手に馴染む感じ、最高です。
HHKBと悩んだのですが、UNIX配列に馴染みがなかったため、こちらにしました。 日本語配列にすればいいじゃんかと思うかもしれませんが、US配列に慣れていきたいため、US配列を基準に選びました。
そして、マシンがMacなため、Macでつかえるよう設定しました。
Karabiner-Elements
Karabiner-Elementsを使うことで、キーバインドをカスタマイズすることができます。
https://pqrs.org/osx/karabiner/
Devicesにて、REALFORCEを選択します。
Virtual Keyboardにて、自分の配列を選択。
そしてこのSimple Modification にて、キーバインドの割り当てをすることができます。 直感的なUIなのでなんとなくでわかるかなと。
真っ先に自分はCaps LockをControlに変更しました。
このブログ書きながら感触に酔いしれています。 うーん、良き。