26
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Android #2Advent Calendar 2019

Day 21

Androidエンジニアにコンバートして学んだ、厳選Tips集

Last updated at Posted at 2019-12-20

Android#2 Advent Calendar 2019 21日目の記事です:christmas_tree:

どうも、iOSエンジニア→Androidエンジニアにコンバートして丸一年が経った@orimomoです。

思えば、ある日突然Androidエンジニア(それも、社内唯一の…)になってから色々ありました。
Android Studioとはなんぞやというところから始まり、iOSとの違いに苦しみ、チョットデキルようになったと思ったらライフサイクルの罠にハマり、あれよあれよという間にAndroid10がリリースされて対応に追われ…。

そんな辛くも楽しい一年を振り返り、実際にハマったり、ベテランエンジニアにレビューで指摘を受けたことを中心にTips集としてまとめてみました:slight_smile:

これから本格的にAndroid開発を始める方にとって、少しでもお役に立てば嬉しいです。

ライフサイクルについてのTips

皆さんご存知、この図です。
「iOSのライフサイクルと同じようなものでは?」と思っていた私が躓いた点を挙げておきます。
(ここではFragmentのライフサイクルに絞って話をします)
fragment_lifecycle.png
フラグメント  |  Android デベロッパー  |  Android Developers

■ライフサイクルメソッドは対になっている

  • onAttach()onDetach()
  • onCreate()onDestroy()
  • onCreateView()onDestroyView()

のように、ライフサイクルメソッドの中には対になっているものがあります。(裏を返せば対になっていないものもあるのですが、ここでは省きます)

これはつまり、onCreate()でした設定はonDestroy()でキャンセルしないといけないし、
onCreateView()で設定した値はonDestroyView()でnullを詰めたりして破棄しないといけない、ということです。

間違えて対になっていないメソッドの中で後処理したりすると、「呼ばれない」などの意図しない不具合が発生するので注意が必要です。

これに関しては下記記事がわかりやすいので一読されることをオススメします。
あの日挫折した Android 初心者へ戻るためのショートカット 〜ライフサイクル編〜 - Qiita

■画面遷移でライフサイクルメソッドが呼ばれる順番に注意

画面Aと画面Bがあったとして、A→Bに遷移するとき、ライフサイクルメソッドが呼ばれる順番はどうなるでしょうか?

画面Aで全てのライフサイクルメソッドが呼ばれた後に、画面Bのライフサイクルが始まってくれると一番わかりやすいのですが、
実際は、画面Aのライフサイクルメソッドがまだ残っているうちに、画面Bのライフサイクルがスタートしてしまう場合があります。

私が遭遇したのは、遷移前の画面のonDestroyView()よりも、遷移後の画面のonViewCreated()のほうが先に呼ばれてしまうことにより、インスタンスが正常に破棄されないという不具合でした。
この手の不具合はコードレビューでは見つかりづらいので、ログ出力などを使い、意図したタイミングでメソッドに入ってるかを確認しながら実装するのが良いと思います。

レイアウトについてのTips

■ConstraintLayoutで階層を浅くする

RelativeLayoutの中にRelativeLayoutがあって、さらにその中にLinearLayoutがあって…というようなネストの深いレイアウトは、ビューの描画に時間がかかるため、ユーザーに優しいレイアウトとは言えません。

イマイチな例
<RelativeLayout>
  <ImageView />
  <TextView />
  <RelativeLayout>
    <TextView />
    <LinearLayout>
      <TextView />
      <RelativeLayout>
        <EditText />
      </RelativeLayout>
    </LinearLayout>
  </RelativeLayout>
</RelativeLayout>

2016年のGoogle I/Oで発表された**ConstraintLayoutを使うことでレイアウトの階層をフラットにすることができるので、可能な限りConstraintLayoutを使う**などして、ネストが深くならないように工夫できると良さそうです。

ConstraintLayoutを使うことのメリットやAndroidの描画処理については下記記事がわかりやすかったです。
Google Developers Japan: ConstraintLayout がもたらすパフォーマンスのメリットを理解する

ConstraintLayout内のビューでは、match_parentではなく0dpを使う

ConstraintLayoutでは、従来使われていたmatch_parentが廃止されたため、代わりに**「制約に合致(Match Constraints)」を意味する0dpを指定する必要があります。**

悪い例
<androidx.constraintlayout.widget.ConstraintLayout
    android:id="@+id/parent_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    
    <!--layout_widthのmatch_parentを、0dpに変える必要がある-->
    <View
        android:id="@+id/view"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:layout_marginTop="10dp"
        android:background="@color/white"

下記の通り、公式ドキュメントにもしっかり記載されています。
スクリーンショット 2019-12-15 12.17.18.png
ちなみにmatch_parentを使っても動くは動くのですが、下記記事にあるように想定外の挙動をする場合があるので、素直に使わないのが吉です。
ConstraintLayoutでMATCH_PARENT利用時の想定外の挙動 - Qiita

ConstraintLayoutはiOSのAuto Layoutに似ており、雰囲気で使ってしまいがちだからこそ注意が必要だなと感じています。

LiveDataについてのTips

■LiveDataをobserveするときは、 thisではなくviewLifecycleOwnerを使う

下記コードのように、FragmentでLiveDataをobserveするときにthisを渡す(Fragment自身をLifecycleOwnerとして設定する)のにはリスクがあります。

悪い例
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    viewModel.liveData.observe(this, Observer {
       // 処理
    })
}

このObserverが破棄されるタイミングは、主にFragmentのonDestroy()が呼ばれたタイミングです。
しかし、上に貼ったライフサイクルの図を再度見てもらうと分かる通り、
FragmentのonDestroy()が呼ばれずに、複数回onCreateView()が呼ばれる(→Fragmentのインスタンスは生き残り、Viewだけが破棄される)ことがあり、そうすると前のObserverが破棄されずに残ってしまいます。
結果、アクティブなObseverの数がどんどん増えていくことになり、メモリリークや意図しない不具合の原因になります。

それを防ぐために、FragmentにはView用のLifecycle、viewLifecycleOwnerが用意されているので、それをLifecycleOwnerに設定してあげると良いです。

良い例
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    viewModel.liveData.observe(viewLifecycleOwner, Observer {
       // 処理
    })
}

これにより、FragmentのViewが破棄されると毎回LiveDataも破棄されるようになるため、複数のObserverが登録されるのを回避できます。

この問題については色々な記事が出ていますが、個人的には下記2つの記事がわかりやすかったです。
Android Architecture Componentsで犯しがちな5つの間違い【翻訳】|ふぉれ$Cooking Programmer|note
FragmentとgetViewLifecycleの話 - stsnブログ

おわりに

今ではAndroid開発と、スマートに書けるKotlinが大好きなので、引き続き深めていきたい気持ちです。
今回書ききれなかったTipsもまだまだあるので、また別の機会に紹介できたらと思います。

来年もモバイル開発、やっていくぞ:star2:

26
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?