🚀 ニフティ’s Notion

【モバイルアプリ2024 #7】Androidのドメイン知識

💡
この章で話すこと
  • Androidの4つのコンポーネント
  • Activityとは
  • Intentとは
  • Androidアプリの権限

4つのコンポーネント

アプリを構成する最上位の構成要素をコンポーネントと呼び、Androidは4つのコンポーネントを軸に構成されています。

コンポーネント 担当 主な処理
Activity 画面 ・画面描画
・画面イベントハンドリング
・画面遷移
など
アプリの画面を担当するコンポーネント。
これがないと画面表示ができないため、大抵のアプリで必須。
Service Activityが消えても継続する処理 ・音楽再生
・大容量データのダウンロード
など
長時間継続する処理に使う 主に画面(Activity)が消えても存続させたい処理に使われる
* 何処かに表示は必要なので、通知欄などActivity以外の表示が存在
Broadcast Receiver ブロードキャストメッセージの受信 ・システムイベントに応じた処理
 ・SMS受信
 ・USB接続 など
システムイベントのメッセージを受け取って、それに応じた処理をする場合に用いる。
特殊な使用例を除いて使われない。
Content Provider データ提供 ・DB
・ファイル保存したデータなどの提供
アプリが持っているデータ(sqliteやファイル)などをREST APIライクなURI形式で抽象化して外部に提供

Androidはリリース当初から複数アプリの連携を考えた設計になっており、上記4コンポーネントはその機能を担っています。

Activity

役割

Activityは画面を管轄するコンポーネントです。具体的には以下のような役割を担っています。

  • レイアウトファイルを読み込み、画面を作成
  • 画面の書き換え
  • 画面イベント(ボタンクリックなど)の処理

基本的には1つのActivityで1つの画面に対応... していました 。現在ではFragmentという、もう1段階小さい単位で画面を構成することが多くなっています。

TaskとActivity

起動したActivityは、 Task という単位で管理されます。「■ボタン (タスク一覧)」を押したときに現れる、アプリ一覧のようなものの1つ1つがTaskです。

image block

Taskの中はスタック構造になっており、その中にActivityが積み上がる形になっています。一番表にあるActivityだけがユーザに見えることによって、画面遷移が実現されます。

このとき、一番上以外のActivityは停止状態になることで、リソースの消費を抑えています。

image block
ℹ️
このActivity遷移、実は遷移先が他のアプリであっても同じ挙動になります。つまり、 1つのTaskに複数アプリが乗る 可能性があるのです。

ある操作をして、別のアプリに行って、戻ってくる、この一連の動作を「タスク」と捉え、1つの作業単位とするのがAndroidの考え方です。この辺りの思想も、最初からアプリ連携を前提としたプラットフォームならではのものと言えます。

ただし動作が分かりにくくなるという面もあるので、「別アプリは別Taskで起動する」ようなオプションも存在します。

ライフサイクル

Activityは OSにより起動され、OSにより管理されます 。したがって、アプリ側で起動・停止のタイミングを自由に記述することができません。

このため、OSが何かをするタイミングで処理を差し込む方法が用意されています。これをActivityの ライフサイクル と呼び、特定タイミングで呼ばれるメソッドを ライフサイクルメソッド と言います。

image block

最低限覚えておく必要があるのは以下です。

onCreate()

文字通り、Activityが作成されたときに呼び出されます。ここでレイアウトファイルをもとに画面を作成します。

画面オブジェクトを後から操作する必要がある場合、そのオブジェクトの取得も同時に行います。

onResume() / onPause()

Activityが裏面に回るなどして停止状態になるとonPause()、再開するとonResume()が呼ばれます。

画面の更新処理は必ずActivityが起動状態であるときに行わなければなりません。停止状態のActivityを操作してしまうと、 NullPointerExceptionでエラー落ち します。

したがって、画面を更新するようなイベントはonResume()で開始、onPause()で停止させます。

ソースコードの記述方法
package com.example.firstapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView

class MainActivity : AppCompatActivity() {

    private lateinit var button : Button
    private lateinit var text: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 画面レイアウトの読み込み
        setContentView(R.layout.main_activity)

        // 画面オブジェクトの取得
        button = findViewById(R.id.button)
        text = findViewById(R.id.text)

    }

    override fun onResume() {
        super.onResume()
	
        // 画面更新イベントの開始
    }

    override fun onPause() {
        super.onPause()

        // 画面更新イベントの終了
    }
}
  • AppCompatActivityを継承したクラスとして、新たなAcvitityを記述する
    • AppCompatActivityは、Androidバージョン間での動作の違いを吸収してくれるActivity
  • ライフサイクルメソッドをオーバーライドして、処理を記述する
    • onCreate()でレイアウトを読み込み、画面を作成する
      • 詳細は次の章で
    • onResume()で画面更新に関わるイベントを開始する
    • onPause()で画面更新に関わるイベントを停止する
<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
  • AndroidManifest.xmlには作成したActivityを記載する
  • アプリ起動時に起動したいActivityにはintent-filterを記載する
Activityの破棄

Activityはソースコード上でfinish()を呼ぶと終了して破棄されますが、その他にも以下のタイミングで破棄されます。

設定の変更

以下のような場合が該当します。

  • 画面解像度の変更 ( 画面回転を含む )
  • 言語の変更

この場合、Activityは一度破棄された後に再作成されます。この際、(当たり前ですが)明示的に保存していないデータは消えます。

メモリ不足による破棄

アプリを複数起動したような場合、前面にないアプリはメモリ不足になるとプロセスごと破棄されます。このため、プロセスに載っているActivityも破棄されることになります。

ただし タスクの状態は残る ので、再び前面に来た段階でプロセスごと再作成され、元いた画面から再開します。

⚠️
プロセスごと破棄されたとしても、再開時には元の画面から再開することに注意してください。

例えば起動時に必ずログイン用のActivityを通り、ログイン後にメインのAcitivyへ遷移するような設計にしたとします。

image block

ここで、MainActivityにいる状態で別アプリを開き、その間にプロセス破棄が起こったとします。プロセスが破棄されたので メモリ上のデータは全部消えます 。しかし画面はMainActivityを開いた状態なので、復活時にはMainActivityから復活することになります。ログインしていないので、処理に失敗することになるでしょう。

image block

このように、 特定の画面を通ることが必須、という処理はActivity遷移ではご法度 です。

Intentによるアプリ間連携

アプリとアプリは、 Intent と呼ばれるメッセージによって連携を行うことができます。

image block
  • アプリは自身の持つコンポーネントをOSに 登録 する
  • 他のアプリからメッセージ( Intent )を送り、返答したり処理を開始したりすることで連携を行う
    • ex) 他のアプリの持つデータを取得する ( Activity → ContentProvider )
    • ex) 他のアプリの画面へ遷移する ( Activity → Activity )
  • 同一アプリ内でも、コンポーネント間はIntentでのやりとりが必須
例: アプリの起動

アプリの起動すらもIntentによって成り立っています。

image block
  1. ホームアプリからLaunch Intentが飛ぶ
  2. Activityが受け取る
  3. Activityを動作させるために、OSがプロセスを起動してアプリが開始される

普通のソフトウェアの起動順序と異なることに注意してください。

  • 普通のアプリ: ユーザがプロセスを起動する → 結果として画面が表示される
  • Androidアプリ: 画面(Activity)が要求される → 結果としてプロセスが起動する

権限(permission)

Androidアプリの権限には3種類あります。

インストール時の権限

ユーザーがアプリをインストールすると、自動的にアプリに権限が付与されます。

ネットワークアクセスにも権限が必要なので注意

マイニフティの例

image block
実行時の権限

アプリが必要に応じてユーザにリクエストする権限

マイク、カメラ、位置情報、PUSH通知などがある。

特別な権限

特定のユースケースでのみ使用する権限。

  • 正確なアラームをスケジュールする。
  • 他のアプリの上に重ねて表示、描画する。
  • すべてのストレージ データにアクセスする。

など

実装方法

Intentを受け取るためには、アプリが何のコンポーネントを持っていて、どんな種類のIntentを受け付けられるのか、OSに教える必要があります。

その役割を担うファイルがAndroidManifest.xmlです。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.mvpapp">

    <!-- アプリケーション定義 -->
    <application
        android:allowBackup="false"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        android:name="com.example.mvpapp.MainApplication">
        <!-- Activity定義 -->
        <activity
            android:name="com.example.mvpapp.ui.MainActivity"
            android:windowSoftInputMode="adjustResize">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

このファイルは、OSに以下のようなことを伝えています。

  • アプリのパッケージ名はcom.example.mvpappである
  • Activityとして、com.example.mvpapp.ui.MainActivityクラスを持っている
    • このActivityは、起動Intent( action.MAIN かつ category.LAUNCHER )を受け取ることができる

またアプリの動作要件によっては以下の情報を追加します。

<uses-permission>

セキュリティの観点から、アプリの使える機能は強く制限されています。

  • インターネット通信
  • SMSへのアクセス
  • カメラへのアクセス

などの機能を使う場合はここに記述し、ユーザの許可( パーミッション )を得るようになっています。 ユーザの許諾を得る ことでようやく動作できる、というのがアプリの基本であり理念です。

ex) インターネットアクセス

<uses-permission android:name="android.permission.INTERNET" />
ℹ️
Android 5.1まで、全てのパーミッションの確認はインストール時に行われていました。しかし大半のユーザが確認もせず許可するため、無意味になっていました。
image block

Android 6.0からは確認方法を見直し、一部の重要なパーミッションは 実行時 に、 最初に権限が必要になった時 に確認を行うようになりました。また、インターネット通信などほぼ全てのアプリで使う権限については、確認を行わないようになりました。(AndroidManifest.xmlへの記載は必要です)

image block
<uses-feature>

アプリ動作に特定の機能が必須の場合、ハードウェアによってはその機能がない場合があります。特定機能がないデバイスにはインストールできないようにするため、必要な機能を宣言することができます。

ex) コンパスを必須とする

<uses-feature android:name="android.hardware.sensor.compass"
                  android:required="true" />