はじめに
こんにちは!東京都市大学大学院 総合理工学研究科 修士1年の平井佑樹です。大学ではリアルタイムOSにおけるタスクスケジューリングの研究について行っているのですが、2025年2月に実施されたCA Tech Dojoにご縁があり、そこからAndroidアプリ開発を始め、同年の8月にCA Tech Jobのインターンシップとして、ABEMAにてAndroidアプリ開発に1ヶ月間参加しました。僕自身、Androidアプリ開発に関する経験はまだまだ無い状態だったので、このインターンシップでは基本的なところで多くの学びを得ることができました。この記事では1ヶ月を通して得た学びについて共有したいと思います。
ABEMAとは
今回インターンシップで参加した事業部「ABEMA」は「新しい未来のテレビ」を目指し、好きな時間に好きな場所で視聴できるよう、インターネットを通して様々なコンテンツをお届けするサービスになっています。幅広いプラットフォームに向けて展開されており、ブラウザやアプリを使用してパソコンやスマートフォンから視聴できるほか、タブレットや対応テレビ、ゲーム機からの視聴もできるようになっています。2023年4月時点で9600万ダウンロードを突破しており、たくさんの方に利用いただいているサービスになっています。
取り組んだタスクについて
今回、僕はAndroidアプリ開発のチームにて、iOSとAndroidの処理の共通化に関するタスクに取り組みました。具体的には、既にある設計を元に実装を行うとともに、新たな箇所の設計も作成しました。
大規模なコードから自分に必要な情報を見つける
ABEMAは2016年にサービスが開始されたアプリで、非常に歴史が長いプロダクトになっています。そのため、とにかくコードの量が大規模になっており、自分のタスクに関連したコードがどこにあるのかを調べるのがとにかく大変でした。そのような中で、自分に必要な情報を見つけるためにしていたことは、アプリに表示されている内容からとにかく検索をすることでした。アプリに表示されているテキストから検索をかけると、strings.xmlに格納されたテキストが引っかかります。そのテキストと紐づいているキーを元に、更に検索をかけます。そのテキストを使用している画面のコードが出てくるので、そこから更にその画面を参照していそうな箇所を探していきます。そのようにして、自分のタスクにおいて必要な情報を見つけ出していくことができました。
ブレークポイントをとにかく活用
大規模なプロジェクトにおいて、自分のタスクに関係のある処理がどのように動いているのかを把握するのも大変な作業の1つでした。初めて見るプログラムにおいて、何がどのようになって、ABEMAのようなアプリが動作するのかを把握することは、コードを読んでいくだけでは流石に厳しいところがありました。そこで、とにかく活用したものがブレークポイントでした。ブレークポイントはデバッガと組み合わせて使うことのできるツールで、プログラムのある時点で処理を一時停止し、変数の値などを確認することができます。先述した検索して関係のありそうな処理の部分にひたすらブレークポイントを設置していき、実際にアプリを動かしてどの処理が最初に動いて、変数の値はどうなっていて、その後はどのような処理が動いているのかを、ステップ・バイ・ステップで確認していくことで、データの流れや、処理の順序などを整理することができました。
Android Studioにおいて、ブレークポイントを設定するには設定したい行の行番号をクリックすることで、設定することができます。

29行目にブレークポイントを設定する例
実際に止まるとこんな感じの画面が表示され、変数やどのようにこの関数が呼び出されたかの情報が表示されたりします。変数の値が表示される他、どの関数から呼び出されたかを示すスタックトレースを確認できたり、任意の関数を呼び出した時の結果を取得することもできます。

ブレークポイントで一時停止した画面例
また、ブレークポイントには条件を設定することができます。ブレークポイントを設定する際に左ではなく右クリックをすることで、メニューが出てきて、「Add Conditional Breakpoint…」を選択することで、ブレークポイントの条件を設定できます。

行番号を右クリックしたときに出るメニュー

ブレークポイントに条件を設定する画面
この機能を組み合わせることで、特定の条件下での変数の値を確認したり、既に確認済みの条件をフィルターすることができ、デバッグやコードの理解のために更に役に立ちました。
依存性注入(DI)について
依存性注入(Dependency Injection = DI)は今回のインターンで初めて実践した概念でした。一見とても難しそうに思えるのですが、実践してみると意外とシンプルな概念です。
アプリの開発をする際、様々なクラスを作ることになります。それらのクラスの中では、変数として、別のクラスのインスタンスを必要とすることが頻繁にあります。Android Developersのドキュメントでは、この例えをCarクラスとEngineクラスを用いて表しています。車(Car)は必ずエンジン(Engine)を必要とします。
ではここで、Carクラスのインスタンスを作成する際、Engineクラスのインスタンスはどのように持ってくればよいか考えてみます。これにはいくつかパターンがあり、1つ目は「Carクラス内でEngineクラスのインスタンスを作成する」方法です。1番シンプルですが、Carクラスが増えるたびに、Engineクラスのインスタンスがどんどん作られていってしまう側面があったり、テスト時に仮のデータを入れたインスタンスをCarクラスに持たせにくいデメリットがあります。2つ目は「Contextや別のクラスのメソッド等を用いて取得する」方法です。既に取得方法がAndroid API等で用意されている物であればこれも楽かもしれませんが、色んな箇所でこの実装をしていると、使用しているAPIの仕様に依存する箇所が様々な箇所で生まれることとなり、メンテナンスの面で良くない場合があると思います。3つ目は「パラメータとして受け取る」方法です。この方法であれば、Engineインスタンスを外部でまとめて作成したり、外部のAPIに依存する処理を別の場所にまとめることができます。この「Carクラスが依存するEngineオブジェクトをどのように注入するか」が依存性注入の話になります。手動でCarインスタンス外でEngineインスタンスを作成し、Carのコンストラクタやメソッドに渡すこともできます。これは手動で依存性注入を行うというパターンになります。ただ、プロジェクトが大きくなっていくにつれて、手動の依存性注入を行い続けていると、似たような処理を様々なクラスに対して繰り返し書くことになったり、複雑なプロジェクト構造に対応するのが困難になる問題があります。そこで、このような依存関係の作成やインスタンスの提供の自動化をするライブラリが用意されています。AndroidアプリではよくHiltが使われます。ここでは、簡単にHiltを使ってみたいと思います。まずは、Applicationクラスを継承しているクラスに、@HiltAndroidAppのアノテーションを付与します。

PlayGroundApplicationに@HiltAndroidAppアノテーションを付与
次に、アプリの開始となるクラスに@AndroidEntryPointのアノテーションを付与します。

MainActivityに@AndroidEntryPointアノテーションを付与
ここまでできたら、データのクラスを定義します。今回はCarクラスとEngineクラスを定義したいと思います。今回はHiltを使うことに集中するため、Engineクラスは名前(String)を持ち、CarクラスはEngineクラスのインスタンスを持つだけのデータ構造にします。

Carクラス
Engineクラス
シンプルなデータ構造を持ったクラスができました。これらのクラスを直接インスタンス化して使うこともできるのですが、今回はインスタンス化から自動でできるようにしましょう。
こんな感じでMainActivityにCarクラスのインスタンスを格納できる変数を用意します。

MainActivityにCarの変数を用意
@Injectという見慣れないアノテーションが付いています。このアノテーションを付けておくことで、Hiltが変数にインスタンスを自動で注入してくれます。ただ、今の2つのクラスはコンストラクタに引数を必要とします。このままではHiltがどのようにインスタンスを生成すればよいか分からないので、インスタンスの生成方法については指定する必要があります。次のようなオブジェクトを作成することで指定することができます。

Carのインスタンス作成方法を指定するModule

Engineのインスタンス作成方法を指定するModule
色々アノテーションが追加されています。まず、@ModuleアノテーションはこのオブジェクトがHiltでインスタンス作成をどのようにすればよいか説明するオブジェクトであることを示します。@InstallInアノテーションでは、そのモジュールのスコープ(有効範囲)を表します。メソッドについている@Providesアノテーションはこのメソッドが具体的なインスタンス作成方法であることを表します。@Singletonアノテーションはアプリ全体で1つのみインスタンスが作られるべきであることを表します。このようにすることで、アプリのビルドが行われる際に必要なクラスが追加で自動生成され、アプリを実行するときに@Injectアノテーションで指定された変数に自動的にインスタンスが挿入されるようになります。この方法を用いることで、大規模で複雑なプロジェクトにおいても、誤って複数のインスタンスを生成することを防いだり、依存関係を整理しやすくすることができる他、テスト用に仮のデータを持ったインスタンスを挿入することもできるようになります。
ちなみに、Android StudioではprovideメソッドやInjectの対象となっている変数の行頭に表示されているアイコンをクリックすることで、provideメソッドがどこにインスタンスを提供しているのかや、インスタンスがどのprovideメソッドから注入されてくるのかを調べることができます。
品質を維持するためのアーキテクチャ
ABEMAのような多くのユーザーを抱え、かつ歴史の長いプロジェクトの場合、品質を維持するためのアーキテクチャも必要になってきます。特に重要だと感じた内容が「責務の適度な分離と統一」でした。役割が別の場合は、責務が分かれるようにアーキテクチャを作り、役割が同じであったり、一元化されているべきな場合は責務を統一するというものです。ABEMAでも当然品質を維持するために責務の適度な分離や統一が実施されていました。
ただ、ABEMAの具体的なアーキテクチャはここでは書くことができないので、Android Developersのサイトで公開されている、アプリ アーキテクチャ ガイドについて触れたいと思います。このガイドでも、最も重要な原則は関心の分離とされています。ここでの「関心」とはプログラムの責任と何をしたいのかを表すものです。僕も含め、アプリ開発初心者の頃によくやってしまいがちなのは、1つのファイル・クラスにまとめて書いてしまいがちです。特にAndroidアプリの表示内容を制御するクラス Activity や Fragment にはよくまとめて書かれがちです。ただ、これらには、UI やオペレーティングシステムとのやり取りを処理するロジックのみを含めるのが適切です。
このことを踏まえ、アプリの推奨アーキテクチャを確認します。

レイヤーの概要図
Android Developersで推奨されているアーキテクチャでは、少なくとも2つのレイヤーが必要であるとされています。1つが画面にアプリデータを表示する「UIレイヤー(別名:プレゼンテーションレイヤー)」で、もう1つはアプリのビジネスロジックを含み、アプリデータを公開する「データレイヤー」になります。また、必要に応じて「ドメインレイヤー」と呼ばれる複雑なビジネスロジックや複数の機能から参照される単純なビジネスロジックを担うレイヤーを追加することもあります。

UIレイヤーの概念図
UIレイヤーはアプリデータを画面に表示することと、ユーザー操作やネットワークのレスポンス等による外部入力によってデータが変更されるたびに、変更を反映する役割になっています。UIレイヤーは2つの要素で構成され、データを画面にレンダリングするUI要素と、データを保持してUIに公開し、ロジックを処理する状態ホルダーの要素があります。画面にレンダリングするUI要素はJetpack Composeといったものが最近ではよく使用されます。ロジックを処理する状態ホルダーにはViewModelクラスなどが該当します。

データレイヤーの概念図
データレイヤーには、アプリの機能を実現するためのビジネスロジックが含まれる他、アプリがデータを作成、保存、変更する方法を決定するルールが含まれます。具体的には、データを提供するRepositoryクラスと呼ばれるものがデータレイヤーに含まれ、Repositoryクラスは0個以上のデータソースを元に、アプリが必要とするデータを提供します。他にもRepositoryクラスでは、データの変更を一元管理したり、複数のデータソース間の競合を解決したり、アプリの他の部分からのデータソースを抽象化したり、ビジネスロジックを担う役割を持ちます。また、データレイヤーにはデータソースとなる、データソースクラスも格納されますが、各データソースクラスはファイルやネットワーク ソース、ローカルデータベースなど、1つのデータソースのみを処理する役割を担い、データの操作のためのアプリとシステムの橋渡しをします。
長くなってしまいましたが、このように、大規模なプロジェクトにおいても、品質を維持するためのアーキテクチャにおいて、「責務の適度な分離と統一」が重要であり、そのためによく用いられるアーキテクチャがあることが分かりました。
おわりに
CA Tech Jobのインターンを通して、多くの実践的な技術を学ぶことができた他、仕事に向き合う姿勢や、自身のキャリアをどのように考えるかについても学ぶことができました。また、社員の皆さんとの交流を通して、CyberAgentの雰囲気を知ることができました。自身にとって初めての長期インターンで、不安なところもありましたが、たくさんの方のサポートのお陰で、充実したインターンを過ごすことができました。また、ユーザーの方により良いサービスを届けるために開発業務に取り組む楽しさを感じることができました。
最後に、トレーナーの方、担当人事の方、ABEMAの皆さんや一緒にお話してくださった皆さん、本当にありがとうございました!
