虚苦心観察ブログ

ブログ管理者である虚苦心が私利私欲に基づいて書いているブログです。主にガジェットのレビューだったり、画像処理のことだったりを記事にしています。

Androidアプリ開発で陥りやすいバグやあまり知られてないけど有益な実装方法をまとめる。

忘れることが多いのでちょくちょくメモしていく。

  • ダウンロードは別スレッドで行う。メインスレッドを止めてしまうためANRが発生する可能性が高い。(activity,service)
  • 画像表示は縮小した画像で行う。Android8から大きすぎる画像を表示しようとすると例外が発生する場合がある。また縮小することで大幅な高速化が可能。ただし、縮小する方法は考えること。単純な実装だとむしろ重くなってしまう場合がある。
  • recyclerviewの各ViewHolderには直接listenerを実装せずにリストの各要素にそれぞれリスナーを保持する
  • ViewHolderで画像表示やネットワーク通信をするときはViewHolderが再利用されるときに画像の解放やネットワーク通信のキャンセルをすること。
  • mvpパターンはandroidフレームワークとロジックを分離するための雛形。
  • ネットワーク通信は通信内容の処理のほかに通信失敗の処理も必須。ネットワーク通信が失敗する可能性は0ではない。
  • FcmIdRecieverServiceはDeprecated。FirebaseMessageRecieverServiceでFCMトークンの更新処理を行うこと。
  • PendingIntentに設定したTaskBackStackが反映されない場合はアプリを再インストールすると治ることがある。アプリのキャッシュやデータを削除しても治らない。(このことからわかるようにAndroidにはアプリから触れることのできないアプリにかかわる領域がある)
  • Retrofit2のマルチパートでファイルとその他をアップロードする場合はPartとPartMapだけしか使えない。PartMapはBodyにハッシュを指定する感覚で使える。
  • KotlinからRetrofit2でPartMapを使う場合、RequestBodyがワイルドカードで表現されてしまう場合がある。その時はPartMapのValueに@JvmSuppressWildcardsを付ける。参照
  • EditTextのimeOptionsにactionSearchを指定してはいけない。ソフトウェアキーボードからのエンターキーのイベントはsetOnEditorActionListenerで設定できるが、ハードウェアキーボードからのイベントは処理できずアプリが落ちてしまう。単純に検索窓として使いたいならSupport LibraryのSearchViewを使おう。継承することで見た目をカスタマイズすることもできる。どうしてもEditTextを使いたい場合はimeOptionsにactionDoneを設定すること。見た目は検索と異なってしまうが、落ちることはない。
  • RecyclerViewのOnScrollListenerに依存した初期化処理を書いてはいけない。タイミングによってイベントが発火しないときがある。なんとかして初期化処理は能動的に実行すること
  • RecyclerViewのスクロール位置復元は明示的に行う必要がある。RecyclerViewのスクロール位置を保存したいに書かれていることをonSaveInstanceStateで保存し、onRestoreInstanceStateで復元する。
  • RecyclerViewの無限スクロールにはこれがよい。無限スクロールの状態をリセットし、一旦停止した無限スクロールを再開することもできる。使い方もしっかり書かれている。
  • アスペクト比を固定するにはConstraintLayoutが使える。stackoverflow よくあるアスペクト比固定のImaveViewもImageViewを拡張せずに使うことができる。
  • デバッグ時はアプリの起動と再起動を開発者オプションの「アクティビティを保持しない」の有効無効を切り替えて確認すること。アクティビティが一時的に破棄され、復元された場合のチェックが抜けていることがよくある。
  • Maps SDK for Androidで地図をLite Modeで表示するにはViewのサイズが完全に固定されている必要がある。固定されない場合、地図が表示されない、タップしたときだけ地図が表示される、など表示崩れが発生する。サンプルコードのLayoutをそのまま使ってもうまく動作しないことがある。その時は、Viewのサイズが変わらないよう、不要な要素を排除、もしくはサイズが変わらないように変更しよう。
  • Activity間のデータやイベントのやり取りはやってはいけない。短時間での使用ではモンダイなさそうでも、長期間利用した場合にAndroidのライフサイクルと合わず、おかしな動作をしてしまう可能性が高い。
  • 逆の考えでActivityの実装を制限してデータやイベントのやり取りを可能にすることはできるかもしれないが、普通ではない実装であるが故に普通の実装をしようとしたときに後悔することになりかねない。
  • FLAG_ACTIVITY_REORDER_TO_FRONTを使って複数のActivityを切り替えて表示すると、オーバービュー画面(アプリの使用履歴が見れる画面)で最後に表示されたActivityのスクリーンショットが表示されず、どこかのタイミングで表示されたActivityの画面になってしまうことがある。Android 8のNexus 5Xで確認。改修方法はわからず。
  • RecyclerView and java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder のようなエラーが出た場合はAdapterの更新でnotifyItemRangeChangedが呼び出されているところをチェックしよう。StackOverFlowで説明されているエラーとは異なる。このエラーはsupport library のバージョンが23.1.1で発生していたもので、すでに修正済みである。ではなぜ同様のエラーが発生しているかというと、おそらくRecyclerView.AdapterのnotifyItemRangeChangedを呼び出していながら表示要素が減っていることが原因だ。notifyItemRangeChangedは表示要素が増えた場合は対応できるが、減った場合は対応できていないようだ。
  • Activity間での変更通知はLocalBroadCastManagerを使うことができる。しかし、これは本来的な使い方ではない。本来はActivityとServiceの通信で使うものである。よく検討し、ライフサイクルを考慮した実装をしなければただのバグの温床にしかならない。あるデータの変更通知を行いたいのであれば、データベースに変更日時を記録すれば、データの取得日時がそれよりも後か前かで更新すべきかどうか判断できる。もしくはデータをServiceなどで一括管理するなどが必要だろう。
  • 通知からCustomChromeTabを直接起動するとBackStackBuilderで構築したバックスタックが反映されず、CustomChromeTabが終了するとアプリも終了してしまう場合がある。Android API Level 25のバーチャルマシンで確認した。21、23、25、27と確認してのことである。解決方法はIntent.ACTIVITY_FLAG_NO_HISTORYで起動した何もしないActivityからCustomChromeTabを起動する。これでAPI LEVEL 25のバーチャルマシンでも問題なくBackstackが構築された。
  • IntentはParcelを継承しているのでそのままIntentにputExtraで保存することができる。これを利用すると何もしないACTIVITY_FLAG_NO_HISTORYで起動するActivityにIntentをExtraとして渡すことでRedirectのような動作を実現することができる。