【アウトプット✍️】Android開発
下記をやる
Kotlin Coroutines
Coroutinesとは?
軽量のスレッドのようなもので、処理同士を並列に実行できる。非同期処理を実装するのに使用する。
Androidでの使用例
例えば、APIから商品の情報を取得してUIに反映するという処理。これを非同期処理にしないとレスポンスが返ってくるまでスレッドをブロックするのでANRが発生したりするのでやってはいけない。そして使用するのがCoroutine。
Coroutine Scopeとは?
コルーチンを実行できる有効範囲。Androidでは以下のScopeをよく使う
ViewModel Scope
ViewModelでコルーチン使うときのScopeでViewModelが破棄されると自動的にキャンセルされる。
LifecycleScope
FragmentやActivityなどandroidx.lifecycleを持っているものに使うScope。そのままlaunchしてコルーチンを開始すると、FragmentやActivityがON_DESTROYのイベントを受け取ったらコルーチンも自動でキャンセルされる。(Activity/Fragmentだと onDestroy)ただlaunchWhenResumedとかメソッドが用意されており、特定のライフサイクルの状態が始まったときに何か処理をしたいという需要に応えることができる。ただ最近ではrepeatOnLifecycle
を使用する方が一般的になっています。(バックグラウンドに移動した際に完全にCoroutinesをキャンセルしてくれるのでリソース削減の面から)
Dispathersとは?
Coroutineが実行されるスレッドを指定できる。Dispatchers.Mainはメインスレッドで実行できる。Dispachers.IOはネットワーク通信などIO操作を処理する際にバックグラウンドスレッドで実行できたりする。Dispachers.Defaultは、CPUに負荷がかかる処理を実行するときに使う。どでかリストの並び替えとか。
launchとAsyncの違い
両方とも子ルーチンをスタートさせる「Coroutine Builder」
launch
coroutine内の処理の結果を返さないコルーチンを開始する。戻り値はJob。「Fire and Forget」の処理はこちらを使う。launchのブロック内で発生した例外を処理しないとクラッシュする。
async
任意の単一の結果を返すコルーチンを開始する。戻り値はDeferredで任意の型のインスタンスを後で返すことができるオブジェクト。await関数を使用することで結果を受け取ることができる。3つAPIリクエストを並列で実行して、結果が返ってきたらローディングを消したいみたいなときに使える。syncはawaitを実行したら例外が発生してた場合、クラッシュする。
Coroutineの例外処理について
CoroutineExceptionHandlerか、try-catchを使用して例外を処理する。
関連
非同期処理
あるタスクを実行している最中に、その処理を止めることなく別のタスクを実行する処理
Coroutine Flowとは
コルーチンの1種で、ワンショット系のデータを処理というよりか、ストリーム系のデータを処理するときに使用する。例えば、データベースからリアルタイムで更新情報を受け取るような処理に使えたりする。RoomのDAOもFlowを返せたりする。RepositoryでFlowを公開しておくと、どこかでFlowの中身を更新すれば、collectしている場所で最新の値を取得できるようになる。
cold flow
- 誰かがcollectするまでflowの処理が動かない
- consumerごとに新しいフローを作成する
- 値をstoreできない
hot flow
- 誰かがcollectしなくてもflowの処理が動く
- 複数のconsumerが1つのフロー(Single Source of truth)
- 値をstoreすることができる
SharedFlow
hot flow.hot flowの特性にもあるように、複数のconsumerでデータや状態を共有することができる。例えば、複数のUIコンポーネントで、Single Source of Truthの原則を守りつつ共通のデータを使うことができる。sharedFlowを作成する方法の1つとしてMutableSharedFlowを使うことができる。
MutableSharedFlow
MutableなのでFlowの値を変更可能なSharedFlow。replayにnを指定した場合、subscribeした際、過去n回の値を受信することができる。extraBufferCapacityはemitされた値を格納しておくバッファのサイズを指定する。Subscriberの処理が長い場合、バッファに空きがない場合emitがsuspendされてしまう。バッファを指定しておくとemitがsuspendされずに済む。onBufflerOverflowにはバッファに空きがない状態でemitされた場合の挙動を設定できる。SUSPENDならsuspendする。DROP_LATESTならemitが破棄される。DROP_OLDESTならバッファのデータの中で一番古いデータを破棄し、空いたバッファに値を保持する。
参考
StateFlow
hot flow. 状態保持用の特別なsharedFlow(repeat=1, DROP_OLDEST)。初期値が必須で、subscribeすると直近の値が1件ながれてくる。つまりsubscribeしたら最新の値を取得できる。さらにバッファには一番最新でemitした値が保持される。また同じ値のemitは無視される。このような性質からAndroid開発ではUiStateなどに使用されることが多い(UIはアプリを起動した時点で何らかの画面を表示しなければならないので、初期値をもつStateFlowは相性がいい。さらに、値が変化した時だけUIを更新できるので表示更新の負荷を下げることができる)
関連
DAO
DBにアクセスしデータを取得などの機能を持ったオブジェクトで、DAOで定義するメソッドはSQLのクエリに自動でマッピングされる
@Jvm〇〇アノテーション
@JvmStatic
JavaからKotlinのCompanion objectブロックの定数やメソッドを参照する際に使用することが多い。このアノテーションをつけることで、JavaからJavaのstatic変数やメソッドを使用する要領で呼び出すことができる。
@JvmOverloads
JavaからKotlinのデフォルト値を持つクラス、または関数を呼び出せるようにするアノテーション。
@JvmField
Javaからget/setメソッド不要で直接プロパティとしてアクセスすることができる。
インライン関数
inline 関数
inline関数内のコードを呼び出し元に展開することができる。関数にラムダをたくさん渡す場合などに使用することでパフォーマンスが向上する可能性がある。(高階関数のパフォーマンス向上)
lateinit vs lazy
lateinit
宣言時に変数を初期化せず、後に初期化する。これによりnullで初期化してnullableなオブジェクトを作るみたいな必要がなくなる。
lazy
クラス生成時などにインスタンス生成のオーバーヘッドが大きい場合などに、lazyを使用すると、必要になったときに初期化する。(クラス生成時にかかるコストが減らせる)
比較表
lateinit | lazy | |
---|---|---|
var/valキーワード | var | val |
nullについて | non-null | non-null, nullable |
プリミティブ型 | 使えない | 使える |
用途 | インスタンス生成時にフィールドの値を確定できない場合に使用 | 重い処理を必要になるまで遅延させたい場合に使用 |
クラスの初期化
// プライマリコンストラクタ
class A(name: String) {
// セカンダリコンストラクタ
constructor(foo: String, bar: String): this(foo) {
// 初期化処理
}
init {
// 初期化処理
}
}
プライマリコンストラクター
- コンストラクタの引数を指定する
- 引数で初期化されるプロパティを定義する
セカンダリコンストラクター
- プライマリコンストラクター以外のコンストラクター
- Kotlinで複数のコンストラクタを持つクラスは一般的ではない。引数にデフォルト値を使えるので
init
初期化ブロック。クラスが生成された時に実行される初期化コードを記述する。初期化処理をかけないプライマリーコンストラクタと一緒に使われることが多い。引数をゴニョゴニョしたりなど。
==
と===
について
==
equals()
のシンタックスシュガー。equals()
の実装に依存する。
===
参照の等価性を比較する。オブジェクトが同じかどうかの比較をする。
const val とvalどう使い分ける?
基本的にはconstはつけられる場合はつけた方が良い。コンパイル時定数になるので一般的にオーバーヘッドは少なくなる。valで宣言するとgetterメソッドを介して値にアクセスする、const valで宣言すると直接値にアクセスできる。companion objectのブロック内など使えるところではconstを使うが吉
高階関数とlambda式
高階関数
関数の引数に関数をとる、もしくは関数を返す関数のこと。
lambda式
無名関数を簡潔に記述できるもの。Kotlinでは下記のような感じ。
test { value -> value * value} // lambda式
fun test(a: Int, f: (Int) -> Int) {
aa = f.invoke(a)
}
Companion object
クラス内に作成されるSingleton。クラスのインスタンスを作成しなくても、メソッドや変数にアクセスできるので、staticなフィールドやメソッドを定義する場合に使用する
What is Context? How is it used?
アプリケーションのグローバル情報へアクセスするためのインタフェースです。グローバルな情報とはファイルリソース、パーミッションなど。Contextとは抽象クラスであり、ActivityやApplicationのSuper Classになっている。
主にApplication ContextとActivity Contextがある。
Application Context
Application全体のライフサイクルに依存する。leakCanaryなどのライブラリの初期化でContextを使いたいなどに使用。
Activity Context
Activityのライフサイクルに依存する。そのためActivityが破棄される時にContextも破棄しないとメモリリークが発生するので注意。ありがちなアンチパターンとしては、ViewModel生成時にActivityのContextを渡してしまってメモリリークが発生したみたいな。AlertDialogなど出すときはActivity ContextでないとActivityのテーマが正しく当たらない場合がある。
その他
Intent生成時のContextはどうする?
正直どちらでも良い。なぜならパッケージ名(String)の取得にしか使われていないから。公式ではActivity Contextを使用している場合が多いのでActivityを使うが良いかも。
参考
What is AndroidManifest.xml?
Androidアプリケーションの情報をAndroidのビルドツール、OS、GooglePlayに提供するために必ず必要なファイル。ActivityやServiceなどのアプリコンポーネントの宣言、Intent filter、パーミッションなどの情報を記述する。
What is Application class?
Androidアプリが起動した際に最初にインスタンス化されるクラス。アプリ起動時に実行したい初期設定やライブラリの設定(Hiltのコード生成のトリガー)などをする。
What is Activity and its lifecycle?
(誤解を恐れて言うと)Androidアプリの画面を表す。ライフサイクルと呼ばれる開始から終了までの状態遷移をもつ。
lifecycleメソッド
onCreate
システムが最初にActivityを作成したときに呼び出される。Activityの初期化やUI要素のセットアップなど、一度だけ行う必要がある処理を実行します。例えばAndroidView時代はここでfindViewByIdみたいなことをしていた。savedInstanceStateを用いてInstanceStateの復元なども行う
onStart
Activityをユーザに見せる直前に呼び出される(Activityの遷移で元に戻った時も呼ばれる)。Activityが表示されてユーザーの操作を受け付ける状態になる際の準備処理を担当します。uiStateのcollectとかはこの状態になったら行う。
onResume
Activityが前面に表示されユーザと対話可能になる直前に呼び出される。別Activityに遷移などしない限りは、基本的にはこの状態にとどまる。
onPause
ユーザーがActivityと相互作用しなくなったが、画面上にはまだ表示されているときに呼び出される(例えば別のActivityに遷移するときなど)。カメラ機能の停止などバックグラウンドに行ったら停止した方が良い処理はここに書く。onPause() の実行は非常に短時間で終了し、保存操作を実行するのに十分な時間を確保できない場合があります。このため、アプリデータまたはユーザーデータの保存、ネットワーク呼び出し、データベース トランザクションの実行には onPause() を使用せずonStop()を使用する。
onStop
Activityがユーザから見えなくなったときに呼び出される。上記が起こるのは、アクティビティが破棄される場合と、別のアクティビティに隠される場合がある。onPause()での説明の通り、CPU に比較的高い負荷をかけるシャットダウン操作を実行するには、onStop() を使用する。
onDestroy
Activityが破棄(finish()や画面の回転など)される直前に呼び出される。onCreateで取得したリソースを解放するのが一般的
参考
Why do we need to call setContentView() in onCreate() of Activity class?
まずsetContentView()
は画面に表示するレイアウトファイル(xml)を指定するメソッド。これをonCreate()
で行わなければいけない理由は、onStart()やonResume()複数回呼ばれる可能性があるから。そのたびにsetContentView()
を呼ぶのは非効率。onStart()
やonResume()
は例えば別のActivityに遷移してバックキーで戻ってきたら呼ばれたりする。
最近ではComposeの登場によりこのメソッドは使わないことが多い印象。
What is onSavedInstanceState() and onRestoreInstanceState() in activity?
onSaveInstanceState()
Configuration changeなどが発生しActivityがkillされるときに、値を保存したいときに使うコールバックメソッド。Bundleに保存する。
onRestoreInstanceState()
onSaveInstanceStateで保存した値を復元するために使用される。onCreate()でも保存した値を取り出すこともできる。このメソッドが呼び出されるのは、アクティビティを再作成する場合のみ
What is Fragment and its lifecycle.
Activityで使用できる「サブアクティビティ」のようなもの。Activityに複数のFragmentを組み合わせてマルチペインUIを実現できたり、複数のActivityでFragmentを再利用できる。もともとはタブレットなどの大画面でのよりダイナミックで柔軟な UI デザインに対応するために導入された。
lifecycleメソッド
Activityと同様にライフサイクルが存在する。
onAttach
FragmentをActivityに追加するときに呼ばれる。
onCreateView
Fragmentが初めてUIを描画するタイミングで呼び出される。inflate()メソッドを用いてxmlのレイアウトファイルを読み込む処理を記述する。
What are "launchMode"?
Activityの起動方法について設定する。
前提
タスク
タスクは複数のアクティビティのまとまりのこと。タスクは一つのアプリ内のActivityをまとめたものとは限らない。アプリが異なっても、同じタスクに所属する場合がある。
アフィニティ
Androidアプリケーション内の異なるActivityがどのタスクに属するかを指定することができる。
standard
デフォルト。システムは常にターゲットのタスクにActivityの新しいインスタンスを作成し、そのインスタンスにIntentを渡す。
singleTop
Activityのインスタンスがターゲットのタスクの一番上に存在する場合、既にあるインスタンスにIntentを渡す。
singleTask
Activityのインスタンスが存在しない場合は、新しいタスクにActivityが生成されそこにintentが飛ばされる。存在する場合はそれ以外のActivityをfinish()した上で、そのonNewInstance()を呼び出しActivityがルートにくる。
What is the difference between a Fragment and an Activity? Explain the relationship between the two.
Activityはアプリの画面を管理するコンポーネント。FragmentはActivity内で再利用可能なUI部品を表すコンポーネントです。複数のFragmentを1つのActivity内で組み合わせて使用することもできる。(大画面端末など)
When should you use a Fragment rather than an Activity?
再利用するUIである場合やviewPagerを使用したいときなど。ただActivityの起動コストやJetpack Navigationの登場によりSingle Activity Multi Fragmentsのアーキテクチャが推奨されるようになった。
What is the difference between adding/replacing fragment in backstack?
add
ActivityにFragmentを追加するメソッド。画面遷移に add() を使用した場合、遷移前の Fragmentに重ねて表示されるため、別途remove()する必要あり。
remove
指定したFragmentを削除するときメソッド。 削除されたFragmentはActivityとの関連付けが解除されFragmentのライフサイクルのonDetach()まで呼ばれる(addToBackStack() されている場合には Activity との関連付け解除が行われず、 onDestroyView()まで呼ばれる)
replace
addとremoveを同時に行うメソッド。画面遷移をしたい場合に用いる。addToBackStack()を一緒に呼ぶことが多い
How would you communicate between two Fragments?
FragmentManagerのsetFragmentResult()
メソッドを使用して、keyとbundleを渡す。同じFragmentManagerのsetFragmentResultListener()
を使用して結果を受け取る。結果はSTAREDになるとコールバックを呼ぶ。
What is the purpose of addToBackStack() while commiting fragment transaction?
遷移元のFragmentバックスタックに積む。これによりバックキーを押したときに遷移元のFragmentに遷移できる。
(replace()
を呼んでaddToBackStack()を呼ぶと、遷移元のFragmentのライフサイクルはonDestryoViewまで呼ばれる)
What is View in Android?
UIコンポーネントの基本単位でButtonやImageViewなどコンポーネントもViewを継承して作られている。タップなどのイベント処理や継承による独自のViewの定義などもできる。
Difference between View.GONE and View.INVISIBLE?
View.GONE
Viewを非表示にするための定数。要素が非表示になると、その要素が占めていたスペースも消失します。つまり、非表示になったViewは他の要素に対して影響を及ぼさなくなる。
View.INVISIBLE
Viewを不可視にするための定数。要素は画面上に表示されていないが、その要素が占めているスペースは依然として確保されたまま。つまり、不可視になったViewは他の要素に対して影響を及ぼす可能性があります。
Can you a create custom view? How?
ViewやViewGroupを継承して、レイアウトファイルでレイアウトを組んだり、onDraw()
をoverrideしてcanvasでガリガリ図形を描いたりする。
What are ViewGroups and how they are different from the Views?
ViewはUI要素の基本構成要素でButton、EditText、CheckBoxなど。ViewGroupはViewやViewGroupを子に持つコンテナ。LinearLayouやConstraintLayoutなどがある。
What is a Canvas?
2Dグラフィックを描画するために用いられる。Viewを継承してCustom Viewを作成してonDraw()
とかでガリガリ図形を描画することができる。
What is a SurfaceView?
高パフォーマンスでアニメーション描画などを行うためのビューコンポーネントです。通常のViewとは異なりUIスレッドとは別のスレッドで描画を行うことができるため、滑らかなアニメーションを実現ことができます。
Tell about Constraint Layout
UI要素同士の相対的な位置を制約(コンストレイント)を使用して指定してViewやViewGroupを配置する。
What is the difference between ListView and RecyclerView?
How does RecyclerView work internally?
CompileSDK, TargetSDK, minSDK
アーキテクチャ