Featured image of post Androidアプリ開発時のメモ

Androidアプリ開発時のメモ

目次

概要

  • Android Developer Accountを放置していたらPlay Console 要件に合うように更新する通知が来た
  • 趣味として、久しぶりにAndorid開発を行い、アカウントを維持する事に決めた
  • 最近のAndroid開発ではJetpack ComposeとKotlinで開発するのが主流らしいので色々メモを残す

Androidの全般

スレッド

メインスレッドとは

  • メインスレッドは、UIスレッドとも呼ばれる

  • アプリケーションの起動時に作成される最初のスレッド

  • ユーザーインターフェース(UI)の描画と更新を担当する

  • 主な役割

    • UIコンポーネントの描画と更新
    • ユーザー入力(タッチ、クリックなど)のイベント処理
    • Activityのライフサイクルメソッドの実行
  • 重要性

    • アプリケーションの応答性を維持するために重要
    • メインスレッドがブロックされると、アプリがフリーズしたように見える(ANR: Application Not Responding)

バックグラウンドスレッド

  • アプリケーションのメインスレッド(UIスレッド)とは別に実行されるスレッドのこと
  • Service Componentは、デフォルトでバックグラウンドスレッドではなくメインスレッドで動くので注意
  • ThreadHandlerThreadAsyncTask(現在は非推奨)Executors、あるいはCoroutineなどを利用する必要がある

バックグラウンドスレッドは、以下のような処理に利用される。

  • ネットワーク通信
  • データベース操作
  • ファイルI/O
  • 長時間かかる計算処理

ベストプラクティス

  • Applicationでシングルトンに管理するべき
    • ViewModel, 常に動くforeground serviceなど
    • 画面に関係ないのに、Activity頻繁にbindなどをして、別コンポーネントの作成と破棄を繰り返すと状態が複雑化する
    • 普通にUI以外のコンポーネントはApplicationでシングルトンにしした方がいい
    • つまり、できるだけ破棄再作成されないようなシンプルなStateにした方がいいですよね?
  • 長時間実行される処理をメインスレッドで行うべきではない
    • ネットワーク操作やデータベース処理などの時間のかかる操作は避ける
      • ちなみに、WebのJSのFetchは非同期処理
    • 時間のかかる操作は別のスレッドで実行する(AsyncTask, Coroutines, WorkManagerなど)
  • UIの更新はメインスレッドで行う
    • なぜなら、Android のUI コンポーネントはスレッドセーフではないから
    • 複数のスレッドから同時にUIを操作すると、競合状態や予期せぬ動作が発生する可能性があるから
    • そのため、runOnUiThread()Handler.post()メソッドを使用して、バックグラウンドスレッドからメインスレッドに処理を戻す
    • Android システムは、MainThread 以外からのUI操作を検出し、例外をスローする(CalledFromWrongThreadException)。

メインスレッドでの処理とバックグラウンド処理の例。

 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
27
28
29
30
31
32
33
34
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // メインスレッドでの処理(UIの更新)
        updateUI()

        // バックグラウンドスレッドでの処理
        Thread {
            // 時間のかかる処理
            val result = performLongRunningTask()

            // メインスレッドに戻ってUIを更新
            runOnUiThread {
                displayResult(result)
            }
        }.start()
    }

    private fun updateUI() {
        // UIの更新処理
    }

    private fun performLongRunningTask(): String {
        // 時間のかかる処理
        Thread.sleep(2000) // シミュレーション
        return "処理結果"
    }

    private fun displayResult(result: String) {
        // 結果をUIに表示
    }
}

もしくは下のような感じ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
new AsyncTask<Void, Void, String>() {
    @Override
    protected String doInBackground(Void... voids) {
        return makeApiCall("https://api.example.com/data");
    }

    @Override
    protected void onPostExecute(String result) {
        // UIの更新
    }
}.execute();

lifecycleScope.launch {
    val result = withContext(Dispatchers.IO) {
        makeApiCall("https://api.example.com/data")
    }
    // UIの更新
}

ANR(Application Not Responding)

  • ANRは、Application Not Respondingの略

![ANRのダイアログ(image-8.png)

ANRの発生条件は以下の2つ。

  • 入力イベント(キーの押下や画面タッチなどのイベント)に対する応答が 5 秒以内にない
  • BroadcastReceiverの実行が 10 秒以内に終了しない

スレッド vs. コルーチン

スレッドとコルーチンの図

  • Process > Thread > Coroutineの階層
  • KotlinはStructured Coroutineを採用しているのでCoroutineはさらに階層構造を持つ
  • AndroidはMain Thread(UI Thread)とそれ以外のBackground Threadがある
    • ちなみに、AndroidのServiceは、MainThreadで実行される

Thread vs. Coroutine

コルーチンのメリット

  • Kotlinの場合は階層構造のcoroutine構造を持つ事によるグループ処理が可能
  • また、Threadと違い、一時停止と再開、取り消しなどが可能となる

スレッドの例

Android でスレッドを使用する例。

  • スレッド関数を使用して新しいスレッドを作成し、指定されたタスクを非同期で実行する
  • ただし、競合状態、デッドロック、スレッド同期などの問題により、スレッドを直接操作することが困難な場合がある
1
2
3
4
5
6
7
8
import kotlin.concurrent.thread 

fun  performTask () { 
    thread { 
        // ここで時間のかかるタスクを実行
        // このコードは別のスレッドで実行
    } 
}

コルーチンの例

  • コルーチンは、並行処理をより構造化して効率的に処理する方法を提供する
1
2
3
4
5
6
7
8
import kotlinx.coroutines.* 

fun  performTask () { 
    GlobalScope.launch { 
        // ここで時間のかかるタスクを実行
        // このコードはコルーチンで実行
    } 
}

画像をDLして表示する例

スレッドの例

1
2
3
4
5
6
7
8
fun loadImage() {
    thread { // ここでbackground threadで動かしている
        val image = downloadImageFromServer()
        runOnUiThread { // メインスレッドに戻している
            imageView.setImageBitmap(image)
        }
    }
}

Coroutinesの例

  • withContextを使用してIO Dispatchersに切り替えてイメージをダウンロードしている
  • その後Mainディスパッチャーに戻って UI を更新
1
2
3
4
5
6
7
8
suspend fun loadImage() {
    withContext(Dispatchers.IO) { // background threadで動かしている
        val image = downloadImageFromServer()
        withContext(Dispatchers.Main) { // main threadで動かしている
            imageView.setImageBitmap(image)
        }
    }
}

versionCodeとversionName

  • versionCode
    • 整数で設定。
    • ユーザーには表示されない。
    • リリースのたびにIncrementする必要がある
  • versionName
    • 文字列で設定。
    • 例えば、1.1とか2.1.4のような値が設定できる
    • ユーザーに表示される

APK vs. AAB

  • AABファイルは、Google Playが最適化されたAPKを生成するためのパッケージフォーマット
  • ユーザーがアプリをインストールする際に、Google Playはデバイスに最適なAPKを生成して配布する
  • そのため、例えば、Google DriveからインストールしたかったらAPKを選ぶ

Androidアプリの設計思想

  • コンポーネントベース
    • Androidアプリは主に4つの基本コンポーネント(Activity、Service、BroadcastReceiver、ContentProvider)から構成されている
  • イベント駆動
    • ユーザー操作やシステムイベントに応じて動作する
  • インテント通信
    • コンポーネント間やアプリ間の通信にはIntentシステムを使用する
  • ライフサイクル管理
    • 各コンポーネントには明確なライフサイクルがある
    • それらはシステムによって管理される
  • UIスレッドとバックグラウンド処理
    • メインスレッド(UIスレッド)とバックグラウンドスレッドの分離を推奨している

アイコンの生成

アイコンの作成方法は次の手順。

Menu > File > new > Vector Asset

アイコンは次の2種類のclipartを用意する。

  • ic_launcher
    • size: 24dp x 24dp
    • アプリ全般用
  • ic_notification
    • size: 48dp x 48dp
    • 通知バー用のアイコン

Store用のアイコンは次を使う。

  • Storeでのアプリのアイコン
    • AndroidStudioProjects\XXX\app\src\main\ic_launcher-playstore.png
  • フィーチャーグラフィック
  • スクリーンショット
    • EmulatorでSSを撮る

Lifecycle

Lifecycleとは

  • Androidの各コンポーネント(Activity、Fragment、Service等)は異なるライフサイクルを持つ
  • ただし、課題が多いため、AndroidはLifecycleOwnerやViewModel、LiveDataなどのJetpack Componentsを推奨している

Lifecycleの違いによる問題は以下

  • 複雑性の増加
    • 各コンポーネントのライフサイクルが異なるため、開発者は各々の挙動を理解し、適切に処理する必要がある
    • 複数のコンポーネントを組み合わせて使用する際、それぞれのライフサイクルの相互作用を考慮する必要がある
  • メモリーリーク
    • ライフサイクルの理解が不十分だと、不適切なタイミングでリソースの解放を忘れ、メモリリークを引き起こす可能性がある
    • 例:Activityが破棄されても、長時間実行されるバックグラウンドタスクがActivityへの参照を保持し続ける場合など
  • リソース管理
    • カメラやGPS等のシステムリソースの使用開始/終了タイミングを、各コンポーネントのライフサイクルに合わせて適切に管理する必要がある

Componentのライフスパン

  • Application
    • プロセスと同等
  • Activity, Service, Foreground Service
    • メインスレッド
  • AsyncTask, Kotlin Coroutines, HandlerThread
    • バックグラウンドスレッド

Lifecycleのデバッグ

tagを決めて全メソッドに下を入れてtagでグルーピングしてデバッグするのが一番良い。

1
2
3
4
5
6
7
class Activity: ComponentActivity() {
    override fun onStart() {
        super.onStart()
        Log.d("myLog", "${javaClass.simpleName}::${object{}.javaClass.enclosingMethod?.name}")
        // 処理
    }
}
  • ChatGPTでログをシーケンス図にするのが良い
  • 以下はそのプロンプト
 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
27
28
29
30
# 指令

- このAndroidのLogcatをマーマメイドのシーケンス図にしてください
- クラスに関係ない行は無視してください
- アクターを追加してください
- コンポーネントは`MainActivity->>MainActivity`のようにself messageのみで作ってください。

# 例

## 例1

### 入力

2024-07-16 20:13:50.873  2946-2946  myLog                   org.m1ke.xxx                   D  MyApplication::onCreate

### 出力

MainActivity->>MainActivity: onCreate

## 例2

### 入力

2024-07-16 20:13:51.205  2946-2946  myLog                   org.m1ke.xxx                   D  MainActivity::onResume::viewModel::timerFinished::observe, language=ja

### 出力

MainActivity->>MainActivity: onResume::viewMoodel::timerFinished::observe, language= ja


Andorid Component

代表的なコンポーネント一覧

Androidの代表的なコンポーネントは以下の通り。

  • Activity
    • ユーザーインターフェースを提供する単一の画面
    • ユーザーと直接対話する部分を担当
  • Service
    • バックグラウンドで動作するコンポーネント
    • ユーザーインターフェースを持たず、長時間実行されるタスクや他のコンポーネントと通信を行うタスクに使用される
  • Broadcast Receiver
    • システムや他のアプリからのブロードキャストメッセージを受信する
    • ブロードキャストは、システムイベントやアプリからのカスタムイベントを通知するために使用される
    • システムイベントは、例えば、バッテリーが低下やWi-Fiの接続など
  • Content Provider
    • アプリ間でデータを共有するための仕組みを提供する
    • 例えば、連絡先アプリやメディアファイルへのアクセスなどがこれに該当する
  • Fragment
    • アクティビティの一部として動作するUIコンポーネント
    • アクティビティ内での再利用可能なUI部分を作成するために使用される
  • ViewModel
    • アクティビティやフラグメントにおけるUIデータの管理を担当する
    • ライフサイクルを考慮して設計されている
    • 画面回転や他のライフサイクルイベントによってUIコントローラが再作成されても、データを保持する
    • これにより、データの保存と復元のためのコードが簡素化される
  • Application
    • アプリケーションの全体的な状態を管理するためのベースクラス
    • アプリケーション全体の初期化処理やグローバルな状態を保持するために使用される
    • Applicationクラスを拡張し、必要に応じてカスタム初期化コードを追加できる
  • LiveData
    • ライフサイクルに依存したデータホルダーで、データが変更されると自動的に観察者に通知する
    • ViewModelと一緒に使用されることが多く、UIのデータ更新を簡素化する
  • Navigation
    • アプリ内の画面遷移を管理するためのコンポーネント
    • ナビゲーショングラフを使用して、アプリのフローを定義し、アクションや遷移を視覚的に設計することができる
  • Repository
    • データ操作を行うクラスで、データソースとViewModelの間の抽象層として機能する
    • クリーンアーキテクチャの一部として使用されることが多い

Activity

Activityとは

  • Activityは、AndroidアプリケーションのUIを持つ構成要素
  • レイアウトファイル(XML)と関連付けられる
  • 明確に定義されたライフサイクルを持ち、画面回転などの設定変更時に再作成される
  • システムはActivityをバックスタックで管理し、ユーザーのナビゲーション履歴を維持する

Activityの例

1
2
3
4
5
6
7
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // UIの初期化やイベントリスナーの設定など
    }
}

マニュフェスト

1
2
3
4
5
6
<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

ライフサイクルの状態遷移図

Activityのライフサイクル

Activityのライフサイクル

アプリ起動

Androidのホーム画面からアプリのアイコンをクリックするとアプリが起動し、最初のアクティビティが画面上に表示される。

アプリ1

「アクティビティが開始」されると次の3つのメソッドが実行される。

  1. onCreate()
  2. onStart()
  3. onResume()

その後、「実行中」の状態になる。

アクティビティの実行中

別のアクティビティを開始

同じアプリの別のアクティビティが開始されようとしたり、別のアプリのアクティビティが開始されようとすると、元のアクティビティは他のアクティビティによって見えなくなる。

別アクティビティの表示

この時、別のアクティビティが画面に表示される前に「onPause()」メソッドが呼び出さる。

元のアクティビティの非表示になるフロー

  • 元のアクティビティが再び画面に表示される場合
    • 右のルートを通って「onResume()」メソッドが呼ばれた上で「実行中」の状態に戻る
  • 新しいアクティビティが画面に表示され元のアクティビティが見えなくなった場合
    • 下のルートを通って「onStop()」メソッドが呼ばれて「停止中」の状態となる

再び表示される時

アクティビティは他のアクティビティの裏に隠れて表示されていない状態でも終了してはいない(停止の状態のまま)。

ユーザーによって非表示の状態から再び画面に表示されようとした場合次の3つのメソッドを実行した後に「実行中」になる。

  1. onRestart()
  2. onStart()
  3. onResume()

再び表示のフロー

ただし、停止中でもアクティビティは終了はしていないが、Androidのメモリが足りなくと、メモリを確保するために実行中でないアクティビティが強制的に終了となる場合がある。

キル時のフロー

この場合、アクティビティはいったん終了する。この状態からユーザーが再びアクティビティを表示させようとした場合は「onCreate()」メソッドから改めて呼び出されることになる。

アクティビティの終了

アクティビティが終了する場合には最後に「onDestroy()」メソッドが呼ばれて終了する。

終了のフロー

いつActivityは落ちるのか

結論、AndroidアプリはOSの都合で勝手に終了する。

 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
27
28
29
30
31
32
33
34
35
36
class MainActivity : AppCompatActivity() {
    private val tag = this::class.simpleName

    private val handler = Handler(Looper.getMainLooper())
    private var count = 0

    private val task = object : Runnable {
        override fun run() {
            Log.d(tag, "count=$count")
            count++
            handler.postDelayed(this, 1000)
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        task.run()
    }

    override fun onPause() {
        super.onPause()
        Log.d(tag, "onPause:")
    }

    override fun onStop() {
        Log.d(tag, "onStop:")
        super.onStop()
    }

    override fun onDestroy() {
        Log.d(tag, "onDestroy:")
        super.onDestroy()
    }
}

上のアプリのログ。

1
2
3
4
5
6
7
8
D/MainActivity: count=6
D/MainActivity: count=7
D/MainActivity: onPause:     ← バックキー押下
D/MainActivity: onStop:      ← Activity停止
D/MainActivity: onDestroy:   ← Activity破棄
D/MainActivity: count=8      ← 動き続けている
D/MainActivity: count=9
D/MainActivity: count=10
  • つまり、Activity が破棄された後もカウントアップが続いてる
  • アプリ内の全Activityが破棄されたからといって、すぐにアプリのプロセスが終了するわけではない

ではいつアプリがいつ死ぬのか?

1
2
3
4
5
D/MainActivity: count=30
D/MainActivity: count=31
D/MainActivity: count=32
I/ActivityManager: Force stopping com.ishihatta.terminationtest appid=10080 user=0: from pid 744
I/ActivityManager: Killing 9768:com.ishihatta.terminationtest/u0a80 (adj 903): stop com.ishihatta.terminationtest

つまり、Activity が残っているかどうかに関係なく、画面表示されていないアプリ(=フォアグラウンドな ActivityやService がないアプリ)のプロセスは、いつkill されてもおかしくないということ。

Service

Serviceとは

  • Serviceはアプリケーションがバックグラウンドで長時間実行する処理を実装するためのコンポーネント
  • あくまで画面のない、Userとの対話で動かないActivityという理解が正しい
  • なぜなら、Service 自体はバックグラウンドスレッドではなくメインスレッドで実行されるから

特徴

  • バックグラウンド処理
    • Activityとは異なり、ユーザーインターフェースがない
    • そのため、ユーザーの操作に依存しないバックグラウンド処理を行うことができる
  • 長時間実行
    • タスクの完了まで長時間実行し続けることが可能
    • 例として、音楽プレーヤーやデータのダウンロード・アップロードなどがある

Serviceの種類

3つのAndroidのサービス

  • Started Service -startService()メソッドで開始されるサービス
    • このサービスは、タスクが完了するまでバックグラウンドで実行される
    • タスクが完了した後にstopSelf()stopService()メソッドを呼び出して停止する必要がある
    • 一定の時間ごとに何らかの処理を行う場合
  • Bound Service
    • bindService()メソッドを使用して他のアプリケーションコンポーネントにバインドされるサービス
    • このタイプのサービスは、クライアントと対話するためにインターフェースを提供する
    • すべてのクライアントがバインドを解除するとサービスは停止する
    • 他のアプリケーションコンポーネント(例: Activity)と対話しながら、定期的なタイマー処理を行う場合
  • Foreground Serviceとは
    • Foreground Serviceとは、通常のサービスと違い、通知を表示し、バックグラウンドで実行している事をユーザに認識させた状態で実行するService
    • Foreground serviceが動いている間はサービスがシステムのリソースを消費し続けている事実をユーザーに認識させるために、専用の通知を通知領域に表示し続ける必要がある

Foreground Service

Serviceの実装例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MyService : Service() {

    override fun onCreate() {
        super.onCreate()
        // 初期化処理
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // バックグラウンド処理の開始
        return START_STICKY // サービスがシステムによって再起動されるべきであることを示す
    }

    override fun onBind(intent: Intent): IBinder? {
        // バインド処理
        return null // バインドサービスではない場合
    }

    override fun onDestroy() {
        super.onDestroy()
        // クリーンアップ処理
    }
}

Serviceのライフサイクル

次の2系統がある。

  • Started
    • startService()メソッドで開始され、停止されるまで実行され続ける
  • Bound
    • bindService()メソッドで他のコンポーネントにバインドされ、バインドが解除されるまで実行される

Serviceのライフサイクル

Intent

Intentとは

  • Web敵に言うと、Itentはハイパーリンクみたいなもの
  • さらに、インテントは、アクティビティやサービスの開始、ブロードキャストの送信など、より広範な機能を持つ

Explicit Intent(明示的インテント)

1
2
3
// 明示的に特定のコンポーネントを指定して操作を開始するためのインテント
Intent intent = new Intent(this, TargetActivity.class);
startActivity(intent);

Implicit Intent(暗黙的インテント)

1
2
3
4
5
6
// 実行したい動作を指定するが、特定のコンポーネントを指定しないインテント
// システムは、指定された動作を処理できるコンポーネントを見つけ出して実行する
// 例えば、ウェブページを開く、電話をかける、写真を撮るなど
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.example.com"));
startActivity(intent);

Intentの例

1
2
3
4
5
6
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.example.com"));
intent.addCategory(Intent.CATEGORY_BROWSABLE);
intent.putExtra("key", "value");
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

Intentが必要な理由

  • AndroidでActivityとServiceやViewModelが通信するのにIntentを使うのは、結局コンポーネントが別々のライフサイクルを持っている事に起因する
  • コンポーネントがかってに作られたり消されたりしたときに、直接参照を持つとメモリーリークになる
  • 故に、メッセージパッシングで通信するためにIntentなどを使って疎結合に通信する

Context

Contextとは

  • Contextは、アプリケーションやアクティビティのライフサイクルを管理する抽象クラス
  • ActivityもApplicationもContextクラスを継承している

Contextは3つの種類がある。

  • Application Context
    • アプリケーションのライフサイクル全体に関連している
    • アプリケーション全体にわたるリソースやクラスへのアクセスを提供する
    • 通常、getApplicationContext()メソッドで取得する
  • Activity Context
    • 特定のアクティビティに関連している
    • そのアクティビティのライフサイクルに依存する
    • 通常、アクティビティ内でthisを使用して取得する
  • Service Context
    • 特定のサービスに関連している
    • そのサービスのライフサイクルに依存する
    • 通常、サービス内でthisを使用して取得する

Contextの注意点

  • メモリリークの回避
    • 長時間保持されるオブジェクト(例えば、静的フィールドやシングルトン)にActivity Contextを保持すると、
    • アクティビティが破棄されてもガベージコレクションされず、メモリリークの原因となる
    • この場合、Application Contextを使用することが推奨される
  • 適切なコンテキストの選択
    • アクティビティ固有の操作(例えば、UI操作やダイアログの表示など)にはActivity Contextを使用するのが良い
    • アプリケーション全体に影響を与える操作にはApplication Contextを使用するのが一般的

ContextとIntent

  • Intent
    • アプリケーション内やアプリケーション間で動作を開始するためのメカニズムを提供するオブジェクト
  • Context
    • アプリケーション環境に関する情報を提供するインターフェース
    • アプリケーションのリソースやクラスにアクセスするためのメソッドを提供する

ContextはIntentを使用してアクティビティやサービスを起動するためのメソッドを提供する。

1
2
3
4
5
6
7
// Contextを使用してアクティビティを起動
Intent intent = new Intent(this, TargetActivity.class);
Context.startActivity(intent);

// Contextを使用してサービスを開始
Intent serviceIntent = new Intent(this, MyService.class);
Context.startService(serviceIntent);

Fragment

Fragmentとは

  • Fragmentは、アクティビティ内で再利用可能なUIコンポーネント
  • 複数の画面サイズに対応するレイアウトを作成する際に特に有用

Fragmentの特徴

  • モジュール性:UIの一部を再利用可能なコンポーネントとして定義できる
  • ライフサイクル:独自のライフサイクルを持ち、アクティビティのライフサイクルと連動する
  • 状態保持:構成変更(画面回転など)時にも状態を保持できる
  • 通信:ホストアクティビティや他のフラグメントと通信が可能

Fragmentの例

Fragmentクラスの定義。

1
2
3
4
5
class MyFragment : Fragment() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_my, container, false)
    }
}

アクティビティでのフラグメントの使用の例。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        if (savedInstanceState == null) {
            supportFragmentManager.beginTransaction()
                .add(R.id.fragment_container, MyFragment())
                .commit()
        }
    }
}

Broadcast Receiver

Broadcast Receiverとは

Broadcast Receiverは、システム全体やアプリケーション間でのメッセージ(ブロードキャスト)の送受信を可能にするもの。

主な役割

  • システムイベントやアプリケーションイベントを受信する
  • バックグラウンドで動作し、特定のイベントが発生したときに反応する

使用例

  • デバイスの起動完了通知
  • バッテリー残量の変化
  • ネットワーク接続状態の変化
  • アプリのアップデート完了
  • 時間帯の変更
  • 言語設定の変更
  • カスタムイベントの送受信

Broadcast Receiverの例

コード例

1
2
3
4
5
6
7
class MyReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
            // デバイス起動時の処理
        }
    }
}

AndroidManifest.xml

1
2
3
4
5
<receiver android:name=".MyReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
    </intent-filter>
</receiver>

使用例

1
2
val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
registerReceiver(myReceiver, filter)

Content Provider

Content Providerとは

  • Content Providerは、アプリ間でのデータ共有を可能にする重要なコンポーネント
  • AndroidでDBにデータを取得/保存する時に利用するクラス
  • あるアプリが持っているデータを他のアプリも使用できるようになったりする
  • もともと用意されているものとしては、着信ログや電話帳、ブラウザのブックマークなど

URI

  • データは Uniform Resource Identifier (URI) を通じてアクセス
  • Content ProviderのURIは3つの要素から構成されている

(例) content://packagename.providername/sample

  • prefix:“content://”
  • authority(パッケージ名+任意のprovider名):“packagename.providername”
  • table:“sample”

Content Providerの例

連絡先プロバイダー

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
val projection = arrayOf(ContactsContract.CommonDataKinds.Phone.NUMBER)

// DB操作と同じようにカーソルオブジェクトを使う
val cursor = contentResolver.query(
    ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
    projection,
    null,
    null,
    null
)
cursor?.use { 
    while (it.moveToNext()) {
        val phoneNumber = it.getString(0)
        println("Phone Number: $phoneNumber")
    }
}

Application

Applicationとは

ApplicationクラスはAndroidアプリケーションのライフサイクル全体を管理するコンポーネント。

次の特徴を持つ。

  • アプリケーション全体の基本クラス
  • アプリの他のすべてのコンポーネントよりも先に作成される
  • アプリケーションプロセスが存在する限り、単一のインスタンスとして維持される

主な用途:

  • アプリケーション全体で共有されるグローバルな状態の管理
  • アプリケーション起動時の初期化処理
  • アプリケーション全体で使用されるリソースの管理

Applicationの主要メソッド

  • onCreate(): アプリケーション起動時に一度だけ呼ばれる
  • onTerminate(): アプリケーション終了時に呼ばれる(エミュレータでのみ動作)
  • onLowMemory(): システムのメモリが少なくなった時に呼ばれる
  • onTrimMemory(): システムがメモリを解放する必要がある時に呼ばれる

Applicationの例

1
2
3
4
5
6
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        // アプリケーション全体の初期化コードをここに記述
    }
}

マニュフェスト

1
2
3
4
5
<application
    android:name=".MyApplication"
    ...>
    <!-- その他のコンポーネント -->
</application>

使用例

1
2
3
4
5
6
7
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val myApplication = application as MyApplication
        // myApplicationを通じてグローバルな状態や方法にアクセス
    }
}

NOTE:

  • applicationはContextのプロパティ
  • MainActivityはContextを継承している
  • 故に、applicationにアクセスできる

コンポーネント間通信

Android IPCの方法

AndroidのIPCの仕組みは2つある。

  • Binder
    • 同一プロセス内のサービスとクライアント間の通信を簡単にするためのメカニズム
  • AIDL(Android Interface Definition Language)
    • 異なるプロセス間での通信をサポートするためのメカニズム

Binderの例

サービスの作成。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class MyService : Service() {
    // バインダーのインスタンスを作成
    private val binder = LocalBinder()

    // バインダーの内部クラス
    inner class LocalBinder : Binder() {
        // サービスのインスタンスを返すメソッド
        fun getService(): MyService = this@MyService
    }

    // サービスがバインドされた時に呼ばれる
    override fun onBind(intent: Intent): IBinder {
        return binder
    }

    // サービスの他のメソッド
    fun someServiceMethod() {
        // サービスの具体的な処理
    }
}

クライアントからサービスにバインド。

 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
27
28
29
30
31
32
33
class MainActivity : AppCompatActivity() {

    private var myService: MyService? = null

    // サービス接続のためのコールバック
    private val serviceConnection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            // バインダーからサービスのインスタンスを取得
            val binder = service as MyService.LocalBinder
            myService = binder.getService()
            // サービスのメソッドを呼び出し
            myService?.someServiceMethod()
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            myService = null
        }
    }

    override fun onStart() {
        super.onStart()
        // サービスをバインド
        Intent(this, MyService::class.java).also { intent ->
            bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
        }
    }

    override fun onStop() {
        super.onStop()
        // サービスのバインドを解除
        unbindService(serviceConnection)
    }
}

通信手段の比較

以下に、通信手段を比較した表を示す。

  • Binder
    • 同一プロセス内での直接通信に適している
    • 例: アプリ内のサービスとアクティビティ間の通信
  • AIDL
    • 異なるプロセス間の複雑な通信に適している
    • 例: サービスとクライアントが異なるアプリで動作する場合
  • Messenger
    • シンプルなメッセージベースの通信
    • 例: サービスがバックグラウンドで動作し、アクティビティに結果を通知する場合
  • BroadcastReceiver
    • 一方向の非同期通信に適している
    • 例: システムイベント(ネットワーク状態の変化、バッテリーの低下)の通知
  • ContentProvider
    • 永続データの共有に適している
    • 例: 連絡先データベース、メディアストア
  • Shared Preferences
    • 小規模なデータ共有に適している
    • 例: ユーザー設定、アプリの状態保存
  • LiveData
    • ライフサイクル対応のデータホルダーで、UIとデータの同期を簡素化する(同期)
    • 例: ViewModelからアクティビティやフラグメントへのデータ通知
  • EventBus
    • アプリ内の疎結合なコンポーネント間通信
    • 例: フラグメント間でのデータやイベントのやり取り
  • Intent
    • コンポーネント間通信(同期)
    • 例: 新しいアクティビティを開始する、サービスを開始する
  • JobScheduler
    • バックグラウンドでのジョブスケジューリング
    • 例: ネットワークが利用可能なときにデータを同期するジョブ
  • WorkManager
    • 信頼性の高いバックグラウンドジョブ
    • 例: 定期的にサーバーとデータを同期する
  • Socket通信
    • リアルタイム通信

Android Jetpack

Android Jetpackとは

Android Jetpackは、Googleが提供する一連のライブラリ、ツール、およびガイドラインのコレクション。

  • Foundation
    • コアのライブラリ(AppCompat、Android KTX、Testなど)を含み、アプリの互換性やテストを容易にする
  • Architecture
    • アプリの設計を助けるライブラリ(ViewModel、LiveData、Room、DataBindingなど)で、データの管理やUIの更新を簡素化する
  • Behavior
    • ユーザーインターフェイスとユーザーの相互作用を向上させるライブラリ(Navigation、Paging、WorkManagerなど)を含む
  • UI
    • 洗練されたユーザーインターフェースの構築を助けるライブラリ(RecyclerView、Fragment、Layout、Animationsなど)を提供する

Android JetpackとJetpack Composeの違い

  • Android Jetpackはアプリ全体の開発をサポートする包括的なツールセット
  • Jetpack Composeは、Googleが提供する最新のUIツールキット

Jetpack Composeとは

  • 元々AndroidはActivityとXMLレイアウトファイルで構成されていた
  • ActivityはUIロジックを管理し、XMLファイルはUIレイアウトを定義していた
  • Jetpack Composeは、XMLファイルを使用せずにKotlinコードのみでUIを定義できるようにした

Composable関数

Composable関数とは

  • Composable関数は、Jetpack Composeを使用してUIを構築するための基本的な構成要素

  • これらの関数は、宣言的なUIフレームワークであるJetpack Composeの一部で、UIコンポーネントを作成するために使用する

  • Composable関数の特長

    • 宣言的アプローチ
      • Composable関数はUIの状態を宣言的に定義する
      • UIは状態に基づいて自動的に再描画されるため、UIの更新が簡単に管理できる
    • 再利用可能
      • Composable関数は再利用可能なUIコンポーネントを作成するために使用される
      • これにより、UIの一貫性を保ち、コードの重複を減らすことができる
    • 状態管理
      • Composable関数は状態を持つことができ、その状態に基づいてUIを動的に変更できる
      • Jetpack Composeは状態の変更を検知してUIを再描画する

Composable関数の例

Composable関数の呼び出し

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import androidx.compose.runtime.Composable
import androidx.compose.material3.Surface
import androidx.compose.material3.MaterialTheme

@Composable
fun MyApp() {
    MaterialTheme {
        Surface {
            Counter()
        }
    }
}

Composable関数の定義

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import androidx.compose.runtime.*
import androidx.compose.material3.Button
import androidx.compose.material3.Text

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }

    Button(onClick = { count++ }) {
        Text(text = "Clicked $count times")
    }
}

rememberとは

rememberはJetpack Composeにおける状態の管理方法。

  • rememberは、Composeがコンポジションの間に値を記憶しておくために使用する
  • これにより、再コンポーズ時に値がリセットされないようにする

1
2
3
4
5
6
7
8
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.remember

@Composable
fun TimerScreen() {
  var shouldRestartTimer by remember { mutableStateOf(false) }
}
  • mutableStateOf
    • mutableStateOfは、Composeで状態を保持するためのクラス
    • 状態が変更されると、Composeはその変更を検知し、自動的にUIを再コンポーズする
  • by
    • byキーワードは、Kotlinのデリゲーションプロパティを使用して、プロパティのゲッターとセッターをカスタマイズする
    • remember { mutableStateOf(false) }の結果を直接shouldRestartTimerとして扱えるようにする

ViewModel

ViewModelとは

  • ViewModelは、UI関連のデータを保存し管理するためのクラス
  • ActivityやFragmentのライフサイクルとは独立して動作する

目的

  • UI関連のデータをライフサイクルの変更(画面回転など)から保護する
  • ビジネスロジックをUIコントローラー(ActivityやFragment)から分離する

ライフサイクル

  • ViewModelは、関連するUIコンポーネント(ActivityやFragment)が完全に破棄されるまで存在し続ける
  • 画面回転などの設定変更では破棄されない

注意

  • ViewModelはあくまくでViewのModel
  • ViewModelでServiceの状態まで管理するのは得策ではない
  • ViewModelにServiceの状態を伝えたかったらBinderを使う

ViewModelの利用例

ViewModelの定義

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

class CounterViewModel : ViewModel() {
    private val _count = MutableStateFlow(0)
    val count: StateFlow<Int> get() = _count

    fun increment() {
        viewModelScope.launch {
            _count.value += 1
        }
    }
}

Composable関数の定義

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.compose.material3.Button
import androidx.compose.material3.Text

@Composable
fun Counter(viewModel: CounterViewModel = viewModel()) {
    val count by viewModel.count.collectAsState()

    Button(onClick = { viewModel.increment() }) {
        Text(text = "Clicked $count times")
    }
}

Activityの例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
          MaterialTheme {
            Surface {
              Counter()
            }
          }
        }
    }
}

ViewModelを使わないと

もしViewModelを使わずActivityのPrivateメンバにデータを保存すると、画面を回転させるとActivityが再度生成されるので、データが保持されず、リセットされる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@data
public class Person {
    private String name;
    private int age;
}

public class MainActivity extends AppCompatActivity {

    private Person person;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        person = new Person("John Doe", 30);
    }
}

AndroidのScope

Scopeとは

  • AndroidのScopeは、主にKotlin Coroutinesと関連して使用される概念
  • Scopeは非同期処理の範囲を定義し、管理するために使用される

Scopeの目的は以下。

  • リソース管理
    • Scopeが終了すると、その中で実行中のすべてのCoroutineがキャンセルされる
  • メモリリーク防止
    • ライフサイクルに紐づくことで、不要になった処理を自動的に停止
  • 構造化並行性
    • 関連する非同期処理をグループ化し、管理を容易にする

Scopeの種類

  • CoroutineScope
    • 一般的なScope
    • 自由に定義可能
  • ViewModelScope
    • ViewModelのライフサイクルに紐づく
    • publicプロパティ
  • LifecycleScope
    • ActivityやFragmentのライフサイクルに紐づく
    • publicプロパティ

Scopeの例

使用例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
val scope = CoroutineScope(Dispatchers.Default)
scope.launch {
    // コルーチンの処理
}

class MyViewModel : ViewModel() {
    fun loadData() {
        viewModelScope.launch {
            // 非同期処理
        }
    }
}

キャンセル処理

1
2
3
val job = scope.launch { /* ... */ }
job.cancel() // 特定のジョブをキャンセル
scope.cancel() // Scope全体をキャンセル

scope.launch

  • Kotlin の coroutine を使用するための構文
  • scope は CoroutineScope のインスタンス
  • launch は coroutine を開始するための関数
  • scope.launch { … } は、指定されたスコープ内で新しい coroutine を開始

scope.launch を使用する主な理由は以下:

  • 非同期処理: UI スレッドをブロックせずに、時間のかかる処理を実行
  • 構造化並行性: coroutine は階層的に管理され、親 coroutine がキャンセルされると、その子 coroutine も自動的にキャンセルされる

例えば、下の操作は非同期で実行されるため、UI スレッドをブロックせずに音を再生し、音声入力を促すことができる。

1
2
3
4
scope.launch {
    viewModel.playSound()
    promptSpeechInput(textToSpeech.language)
}

Jetpack Room

Jetpack Roomとは

Jetpack Roomは、Android開発においてSQLiteデータベースの操作を簡素化するための永続性ライブラリ。

次の利点がある。

  • SQLiteの低レベルAPIを直接扱う必要がない
  • コンパイル時のSQL検証
  • 非同期処理との連携が容易
  • データベース操作のボイラープレートコードを削減

Jetpack Roomの例

EntityとDaoの定義して非同期でアクセスする。

 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
27
28
29
30
31
32
33
34
35
36
37
38
@Entity(tableName = "users")
data class User(
    @PrimaryKey val id: Int,
    @ColumnInfo(name = "name") val name: String,
    @ColumnInfo(name = "email") val email: String
)

@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    fun getAll(): List<User>

    @Insert
    fun insert(user: User)

    @Delete
    fun delete(user: User)
}

@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao

    companion object {
        private var instance: AppDatabase? = null

        fun getInstance(context: Context): AppDatabase {
            if (instance == null) {
                instance = Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "app_database"
                ).build()
            }
            return instance!!
        }
    }
}

使用方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
val db = AppDatabase.getInstance(context)
val userDao = db.userDao()

// 非同期で操作を行う
lifecycleScope.launch(Dispatchers.IO) {
    // データの挿入
    userDao.insert(User(1, "John Doe", "john@example.com"))

    // データの取得
    val users = userDao.getAll()
    withContext(Dispatchers.Main) {
        // UIの更新
    }
}

WorkManager

WorkManagerとは

  • WorkManagerは、延期可能な非同期タスクを簡単にスケジュールするためのAPIを提供する
  • バックグラウンドで実行する必要がある作業に適している

WorkManagerの例

1
2
3
4
val uploadWorkRequest: WorkRequest =
    OneTimeWorkRequestBuilder<UploadWorker>().build()

WorkManager.getInstance(context).enqueue(uploadWorkRequest)

LiveData

LiveDataとは

  • LiveDataは観測可能なデータホルダークラス
  • ライフサイクルを意識したデータ保持の仕組みを提供し、主にUIとデータの同期に使用される

主な特徴:

  • ライフサイクル認識:ActivityやFragmentのライフサイクルに応じてデータの更新を管理する
  • メモリリーク防止:ライフサイクルが終了すると自動的に購読を解除する
  • 常に最新のデータ:設定変更(画面回転など)後も最新のデータを保持する
  • UIとの一貫性:メインスレッドでデータ更新を行う

LiveDataの例

LiveDataの定義

1
2
3
4
5
6
7
8
class MyViewModel : ViewModel() {
    private val _data = MutableLiveData<String>()
    val data: LiveData<String> = _data

    fun updateData(newValue: String) {
        _data.value = newValue
    }
}

LiveDataの利用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class MyActivity : AppCompatActivity() {
    private val viewModel: MyViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        viewModel.data.observe(this) { newValue ->
            // UIの更新
            textView.text = newValue
        }
    }
}

LifecycleOwner

LifecycleOwnerとは

  • LifecycleOwner は Android Jetpack の Lifecycle コンポーネントの一部
  • オブジェクトのライフサイクル状態を持つクラスを表すインターフェース
  • つまり、MainActivityやServiceなどのコンポーネントの状態を抽象化したものがLifecycleOwner

目的

  • コンポーネントのライフサイクル状態を観察可能にする
  • ライフサイクルに応じた処理を簡単に実装できるようにする

ライフサイクル状態

  • INITIALIZED
  • CREATED
  • STARTED
  • RESUMED
  • DESTROYED

LifecycleOwnerの例

  • 以下の例では、MyLocationTrackerは具体的なActivityやFragmentに依存せず、LifecycleOwnerインターフェースを通じてライフサイクルに応じた処理を行っている
  • つまり、これにより、同じMyLocationTrackerをActivity, Fragment, Serviceなど、LifecycleOwnerを実装する任意のコンポーネントで再利用できる
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// コンポーネントの定義
class MyLocationTracker(private val lifecycleOwner: LifecycleOwner) {
    init {
        lifecycleOwner.lifecycle.addObserver(object : LifecycleObserver {
            @OnLifecycleEvent(Lifecycle.Event.ON_START)
            fun start() {
                // 位置追跡開始
            }

            @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
            fun stop() {
                // 位置追跡停止
            }
        })
    }
}

// 使用例
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val tracker = MyLocationTracker(this)
    }
}

参考文献

Built with Hugo
テーマ StackJimmy によって設計されています。