Activity再生成時のデータの保存・復元(Fragment#setRetainInstance)
- ネットワークからデータ取得(画像とか)
- データ取得完了したら画面にデータ反映
こういうパターンってよくありますよね。このときに画面回転等のコンフィグ変更(縦横切替とか)が行われると 「Activity のインスタンスが破棄->再生成」 されるので、何も考えてないと保存データも一緒に破棄されてしまいます。なので、内部に保存しているデータを 「保存/復元」 する必要があります。
自分、コンフィグ変更で Activity再生成 とかされると正直めんどくさいので、基本的に "android:configChanges" の属性指定で Activity再生成抑止 して データの保存/復元 とか考えない方向でいいんじゃね。とか思ってたんです(すみません)。
でもよくよく調べてたら Activity再生成 を回避できないケース がありまして。例えば、「フォント切替」。
こいつは configChanges のパラメータにないので、厳密にいうとやっぱり Activity再生成 は避けて通れない。悲しいです。というわけで、Activity再生成時の内部データの保存/復元方法 はどうやるのが正解なのだろう、と調べてみると 2.x、3.x〜系 でだいぶ変わっていました。自分への備忘録の意味も込めてまとめてみます。
Activity再生成時の保存/復元処理
2.x系(API Level 10まで)
- サイズ小さめのデータの場合(serializeされる)
- Activity#onSaveInstanceState で保存処理
- Activity#onCreate、onRestoreInstanceState で復元処理
- サイズ大きめのデータの場合(serializeされない)
- Activity#onRetainNonConfigurationInstance で保存処理
- 再生成後に任意のタイミングで Activity#getLastNonConfigurationInstance 呼び出して復元処理
3.x、4.x系(API Level 11〜)
- サイズ小さめのデータの場合(serializeされる) ※Activity単位(2.x系と一緒)
- Activity#onSaveInstanceState で保存処理
- Activity#onCreate、onRestoreInstanceState で復元処理
- サイズ小さめのデータの場合(serializeされる) ※Fragment単位
- Fragment#onSaveInstanceState で保存処理
- Fragment#onCreate で復元処理
- サイズ大きめのデータの場合
- Fragment#setRetainInstance(true) を指定して Fragment が破棄されないようにし、そこへデータ保存
- 任意のタイミングで↑の Fragment からデータ取得して復元処理
サイズ小さめデータの 保存/復元 を行う際、Activity#onSaveInstanceState〜 については 2.x系 と特に変わらないのですが、Fragment単位でも同様のことができるようになってます。
サイズ大きめデータの 保存/復元 を行う際、3.x系〜は Activity#onRetainNonConfigurationInstance が非推奨になってます。代わりに Fragment#setRetainInstance(true) を使用する方法が推奨されています。
Fragment#setRetainInstance(true) を呼び出すことでどうなるかというと、
- Activity再生成時に Fragment#onDestroy、onCreate が呼ばれなくなる。
- Activity再生成時には Fragment#onDetach、onAttach が呼び出されるだけ。
- Fragmentインスタンスが破棄されないので Fragmentインスタンス内にデータをそのまま保持しておくことが可能。
ということみたいです。つまり、でかいデータについては、
Fragmentインスタンス自体を破棄されないようにしとく。 で、Fragmentインスタンス内部でそのまま保持っておく。
ってことですかね!
参考
- Activity#onRetainNonConfigurationInstance
- Android UI Cookbook for 4.0
- 「2.9 ビューを持たないフラグメントで定期処理をする」
DialogFragment使用時に画面の回転で例外発生(InstantiationException)
DialogFragment を private なインナークラスで定義してたら、
画面の回転(Activity再生成) でアプリが落ちてしまった。
出力された例外はこれ。
Caused by: android.app.Fragment$InstantiationException: Unable to instantiate fragment ErrorCaseDialogFragment: make sure class name exists, is public, and has an empty constructor that is public
Fragment が private なインナークラスなので、
外部からコンストラクタ呼び出せなくて例外になった様子。
使用したコードはこんなかんじ。
ErrorCaseDialogFragment のクラス定義を
private static ErrorCaseDialogFragment ↓ public static ErrorCaseDialogFragment
こう修正したら普通に動くけども。
こうするくらいならFragmentを外部ファイルにした方がいいですよねw
というわけで。
結論
Activity再生成時に自動生成するFragmentは、
空のpublicコンストラクタが外部から呼び出せるように、
privateなインナークラスではなく publicなクラス にする。
Android UI Cookbook for 4.0 輪講会 vol.1
Androidの会福岡支部メンバー数人で輪講会をはじめました。
今回のお題は通称 yanzm本2 と言われている、
Android UI Cookbook for 4.0 ICSアプリ開発術 です。
実はちょっと前に Effective Java 第2版 の輪講をやってみたのですが、
それがまたなかなかおもしろくて。
- 「人に伝えること」を前提にして熟読+理解
- 実際に説明
- 議論
をするとこれがまた異常に充実感があるというか。
みんなでやいやい言いながら興味のあることを勉強するのはやっぱり楽しいですね。
「おい、勉強くらい本読んで1人でやれよ」と言われたらそれまでなんだけどもw
というわけで、こんな感じで輪講会を進めてます。
進め方
- 各自予習必須
- 説明担当の人が本を音読しながら説明
- サンプルアプリ動かしながら内容確認
- 説明してるときに気になった点、疑問点などを皆で議論
- 次回どこまでやるか+次回の説明担当決め(説明担当は毎回ローテーション)
第1回(2012/3/26)
- 第1章 Android4.0(ICS)
- 説明担当 id:shikajiro
メンバー
- @yuuto
- @shikajiro
- @kenz_firespeed
- @gatpai
- 自分
それでは、議論や話題になった点をずらずらと列挙してみます。
1.1.1 UI上のバー
- なんで [履歴ボタン] がわざわざ新規追加されたん?
- 今まで通り [HOME]長押し でいいんじゃね?
- 「長押しをやめる」というGoogle側の方針?
- [メニュー用コントロール] って押しにくい。なんで右下?右上?
- 謎。
1.1.2 システムUIの状態
- ↓の使い分けは?
- SYSTEM_UI_FLAG_LOW_PROFILE(システムナビゲーションバー控えめ)
- SYSTEM_UI_FLAG_HIDE_NAVIGATION(システムナビゲーション非表示)
- 画面タッチしたときに画面伸縮が気になるか気にならないかがポイント?
- 例えば「頻繁に画面タッチするようなアプリ(ゲームとか)」は 前者 の設定よね。
- 前者だと システムナビバー領域は常に固定なので画面伸縮は発生しない。
- 後者だと システムナビバー領域が表示/非表示切替される(画面伸縮)。
- 電子書籍リーダとか動画プレイヤーは前者推奨されているが、後者もあり??
- 「なるべく大画面で見たい」という欲求もあるし。
- ページめくり、早送り等の操作したいときだけナビバー領域動的表示。
コラム Activityの再生成抑止(android:configChanges)
- そういえば orientation、screenSize 以外にも keyboard、keyboardHidden、fontScale な属性あるよね。
- ガラスマはパカパカだったりスライダーだったりで、Activity再生成イベントいろいろ飛んでくるよね。
1.2.1 48dp単位のレイアウト
- 「48dp=約9mm」 って 某iOSアプリのガイドラインで見たことあるねw
- エレメント間隔は 「4dp もしくは 8dp単位」 が推奨(48dpの約数なので統一感出る)
1.2.2 タイポグラフィ
- テキストサイズに 「sp指定」 ってICS以前からできなかったっけ?
- テキストサイズ=10sp は 10dpにシステム全体のテキスト拡大率を掛けた大きさ
1.3.1 Device.Defaultテーマ
- Theme.DeviceDefault は各メーカがシステムdefaultとしてカスタマイズしてるテーマ。
- ICSデフォルトを使いたいなら 「Theme.Holo」 を明示的に指定しよう。
- 指定しとかないと端末ごとに微妙に色見が異なる、などが起こる可能性も。
1.4.1 GridLayoutとSpace
- GridLayoutはtableタグのようなレイアウト向け
- table構造を LinearLayout の入れ子で作るくらいならこっちを使おう
- view階層が浅くなるのでパフォーマンスよくなるはず
- ただし、静的レイアウト向け
- 子view幅高さ、要素数が動的に変わるようなレイアウトだと普通のViewGroup系よりコストかかる
1.5 ハンドセット/タブレット両対応
- supports-screensタグ
- Market上で画面サイズごとにDL可否フィルタリングできたり。
- あくまで Marketでのフィルタリング用(Market介さない野良apkだと普通に実行可能)
1.6 Multi-paneレイアウト
- 縦横切替したときの見え方に注意
- 縦表示時に2つのパネル表示してたら、横にしても2つのままにする
- このとき、縦/横で各パネルの幅を変更して見え方を調整
1.7 スワイプビュー
- リスト画面(10件)から開いた詳細画面(要素1)でスワイプすると次の詳細画面(要素2)を表示するとか
とりあえず今回はここまで。
あとは、ABC2012Spring に参加した 支部長(@yuuto) からABC総括とか。
@yanzmさん のustとスライドって公開されないのかなー。
行けなかったのが悔やまれる。。
というわけで、参加メンバーの皆様お疲れ様でした。
次回は 第2章フラグメント。アツい。
今度は自分が説明担当なので予習頑張ります。
AsyncTask の挙動(AsyncTask#executeOnExecutor) その2
前回のエントリで AsyncTask#execute の挙動がAPI Levelによって違う、とわかりました。
では、どこで切り替えているのか。
てなことを twitter上 でぎゃーぎゃー言ってたら
@zaki50さん から情報をいただきました!
(@zaki50さん、ありがとうございます!)
参考
日本Androidの会 - API LEVEL 11以上のAsyncTaskの振る舞いについて
というわけで ICSの ActivityThread.java を読んでみました。
ソースの該当箇所はココ。
// android.app.ActivityThread private void handleBindApplication(AppBindData data) { // 抜粋 // If the app is Honeycomb MR1 or earlier, switch its AsyncTask // implementation to use the pool executor. Normally, we use the // serialized executor as the default. This has to happen in the // main thread so the main looper is set right. if (data.appInfo.targetSdkVersion <= 12) { AsyncTask.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } }
思いっきり書いてあります。
ココで切り替えてたのですねー。。
このソース内、他にも Honeycomb以前だったら判定とか StrictMode設定だったりとか。
いろんなことやってるのがチラチラ見えたのでもっと細かく読んでみようと思います。
恥ずかしい話、ActivityThread は Looperの起動 くらいしか思っていなかったですw
AsyncTask の挙動が異なる(AsyncTask#executeOnExecutor)
AsyncTask の挙動が Gingerbread と ICS で違う、という話を聞いて調べてみた。
複数AsyncTask実行=バックグラウンドで並行処理 と考えてると、実は並行処理になってなくて処理が遅い、ってこと。
結論を言うと、ソースをここだけ修正すればよさそう。
修正方法
- AsyncTask#execute を AsyncTask#executeOnExecutor に変更する
- AsyncTask#executeOnExecutor の引数に AsyncTask.THREAD_POOL_EXECUTOR を指定する
1行で済みそうな修正。よかったw
とはいえ気になったので、AsyncTask#execute の挙動の違いについてまとめてみる。
↓はSDKのバージョンを変えて検証した結果。
検証結果
複数AsyncTask の AsyncTask#execute を実行した場合
- API Level
1012まで- 複数バックグラウンド処理がパラレルで実行される
- API Level
1113以降- 複数バックグラウンド処理がシリアルで実行される
3.2 から変わった様子。
ちなみに検証はこんなかんじで。
検証方法
では、GB相当の AsyncTask と同等の動きをさせるにはどうしたらいいか。
AsyncTask#execute のJavadoc見るとこう書いてある。
After HONEYCOMB, it is planned to change this back to a single thread to avoid common application errors caused by parallel execution. If you truly want parallel execution, you can use the executeOnExecutor(Executor, Params...) version of this method with THREAD_POOL_EXECUTOR; however, see commentary there for warnings on its use.
Honeycomb以降では 複数のAsyncTackのバックグラウンド処理 をパラで実行したいなら
AsyncTask#executeOnExecutor + THREAD_POOL_EXECUTOR を使用してください、と。
今回使用したサンプル。
(ボタン押したら AsyncTask#execute か AsyncTask#executeOnExecutor を実行するだけ)
実行結果(emulator(API Level14))
AsyncTask#execute 01-25 02:58:37.899: D/AsyncTaskTest(613): [doInBackground]start 78 01-25 02:58:39.950: D/AsyncTaskTest(613): [doInBackground]end 78 01-25 02:58:39.950: D/AsyncTaskTest(613): [doInBackground]start 79 01-25 02:58:41.970: D/AsyncTaskTest(613): [doInBackground]end 79
AsyncTask#executeOnExecutor 01-25 02:59:11.889: D/AsyncTaskTest(613): [doInBackground]start 80 01-25 02:59:11.929: D/AsyncTaskTest(613): [doInBackground]start 81 01-25 02:59:13.919: D/AsyncTaskTest(613): [doInBackground]end 80 01-25 02:59:13.989: D/AsyncTaskTest(613): [doInBackground]end 81
AsyncTask#executeOnExecutor の方はちゃんとパラで動いてる。
参考
@findup さんのエントリ。
[http://www.swingingblue.net/mt/archives/003629.html:title=きままな日記帳 - GingerbreadとICSでのAsyncTaskの挙動の違い