Featured image of post [Android] フレームワークのソースコードを正しく読む

[Android] フレームワークのソースコードを正しく読む

ComponentActivity が多数の interface を implements しているのを見て「こう書いて良いのだ」と結論づけるのは誤読である

要点

  • ComponentActivity が多数の interface を implements しているのを見て「こう書いて良い」と結論づけるのは誤読である。
  • ComponentActivity の interface は全て Activity の本質的な責務 であり、is-a が成立している。
  • フレームワークがそれらを引き受けているのは、アプリケーション側が余計な interface を背負わなくて済むようにするため である。

ComponentActivity のコード

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
 * Base class for activities that enables composition of higher level components.
 *
 * Rather than all functionality being built directly into this class, only the minimal set of
 * lower level building blocks are included. Higher level components can then be used as needed
 * without enforcing a deep Activity class hierarchy or strong coupling between components.
 */
open class ComponentActivity() :
    androidx.core.app.ComponentActivity(),
    ContextAware,
    LifecycleOwner,
    ViewModelStoreOwner,
    HasDefaultViewModelProviderFactory,
    SavedStateRegistryOwner,
    OnBackPressedDispatcherOwner,
    NavigationEventDispatcherOwner,
    ActivityResultRegistryOwner,
    ActivityResultCaller,
    OnConfigurationChangedProvider,
    OnTrimMemoryProvider,
    OnNewIntentProvider,
    OnMultiWindowModeChangedProvider,
    OnPictureInPictureModeChangedProvider,
    OnUserLeaveHintProvider,
    MenuHost,
    FullyDrawnReporterOwner {

Android のフレームワークコードには、多数の interface を implements しているクラスが存在する。これを見て「こういう書き方は OK なのだ」と結論づけるのは誤読である。このコードから読み取るべきことは、むしろ逆である。

doc comment が設計意図を語っている

Rather than all functionality being built directly into this class, only the minimal set of lower level building blocks are included. Higher level components can then be used as needed without enforcing a deep Activity class hierarchy or strong coupling between components.

(すべての機能をこのクラスに直接組み込むのではなく、最小限の低レベルな構成要素のみを含める。高レベルのコンポーネントは、深い Activity クラス階層やコンポーネント間の強い結合を強制することなく、必要に応じて使用できる)

Google 自身が「深い Activity クラス階層やコンポーネント間の強い結合を避けたい」と明言している。このクラスの設計意図自体が、「Activity に何でも載せるな」という主張を裏付けている。

is-a が成立する interface と成立しない interface

ComponentActivity の interface は全て is-a が成立している。

1
2
3
ComponentActivity : LifecycleOwner          → Activity はライフサイクルの所有者である ✓
ComponentActivity : ViewModelStoreOwner     → Activity は ViewModel ストアの所有者である ✓
ComponentActivity : SavedStateRegistryOwner → Activity は状態保存レジストリの所有者である ✓

これらは全て Activity の本質的な責務 である。Activity がライフサイクルを持ち、ViewModel を管理し、状態を保存する — これは「代行」ではなく「本質」である。

一方で、アプリケーションコードにおける以下の記述はどうか。

1
2
MainActivity : OnClickListener  → Activity はクリックリスナーである ✗
MainActivity : TextWatcher      → Activity はテキスト監視者である ✗

クリック処理やテキスト監視は Activity の本質ではなく、たまたま代行しているだけ である。Activity の責務外であり、分離すべきである。

is-a 関係が成立していても継承が正しいとは限らない。interface の多重継承にも設計原則を適用すべき理由を具体例で示す
cover.webp

フレームワークコードとアプリケーションコードは立場が違う

観点 ComponentActivity(フレームワーク) MainActivity(アプリケーション)
目的 アプリコードがこれらを implements しなくて済むように、基盤として引き受ける ビジネスロジックと UI を組み立てる
設計判断 多くのレビューを経た意図的な設計 個々の開発者の判断
変更頻度 安定(API 互換性を保つ) 頻繁(仕様変更のたびに変わる)

ComponentActivity が LifecycleOwner 等を引き受けているのは、アプリケーション側の Activity がそれらを implements しなくて済むようにするため である。

つまり、このコードから読み取るべきメッセージは、

「フレームワークがここで引き受けたから、アプリコードでは余計な interface を背負うな」

ということである。

まとめ

  • ComponentActivity の interface は全て Activity の本質的な責務であり、is-a が成立している。
  • アプリケーションコードで OnClickListener 等を implements するのは、Activity の責務外の振る舞いを is-a で取り込む行為である。
  • フレームワークのコードは「こう書いて良い」の根拠ではなく、「何をアプリコードから分離したかったか」を読み取る対象である。
Hugo で構築されています。
テーマ StackJimmy によって設計されています。