Androidアプリ開発の極意 4,5章ユーザー体験と非同期処理

前の記事同様に Androidアプリ開発の極意 | 技術評論社の「ユーザーストレス軽減」まわりの実装Tipsと、並行処理の選択肢をざっくり整理してExpo周りと絡めたもの。細かい実装には踏み込まず、要点だけ。 はじめに アプリが固まる原因の大半は、メインスレッド(UIスレッド)を長時間ブロックしていること。非同期処理の設計は結局「ユーザーを待たせない」という一点のためにある。 二部構成で一部は本を読んだ感想+α、二部がExpoだとどうなるかって話。 基本的にAIで出して、それを読んで調べたりしたもの。 第1部:Androidの話 Android(Java/Kotlin)側だけで完結する話。UXまわりの実装Tipsと、非同期処理の手段がsynchronizedからVirtual Threadsまでどう変遷してきたかを、Expoの話を挟まずに整理する。 UXまわりの小さな判断 時間のかかる処理にはProgressBarで進捗を見せる SharedPreferencesは永続化のためディスクI/Oが発生する セルラー通信時は重い処理の前に一言断りを入れると親切 エラーはその場で表示しつつ、Crashlytics/ACRAのような開発者向けツールで後から追える形が理想 NDKは高速化と引き換えにデバッグの複雑さが増す。基本的には奥の手 非同期処理はなぜ増え続けているのか synchronizedは「同時に入らない」ことしか保証しない。順番までは保証しない。 Threadを直接生成した場合も同様で、生成順と実行順は一致しない。順番を保証したいならExecutors.newSingleThreadExecutor()のような直列化の仕組みを使う。 ExecutorServiceは、スレッドを直接管理せず、プールとキューで管理する仕組み。スレッド数の制御や再利用をアプリ側のロジックから切り離せる。 AsyncTaskはAPI 30で非推奨になった。ライフサイクル管理が弱く、Activity/Fragment破棄後もタスクが動き続けてリークやクラッシュを招くのが理由。 今の主流はKotlin Coroutines。スレッドをブロックせず中断できる軽量な並行処理で、排他制御が必要ならMutexを使う。UIに紐づく短い処理はCoroutines、画面を離れても続けるべき処理はWorkManagerと使い分ける。 JVM側ではJDK 21でVirtual Threads(JEP 444)が正式機能になった。I/Oバウンドな処理を大量にさばくのに向くが、CPUバウンドには向かない。ただしこれはJDK/Java SE側の機能で、Android Runtime(ART)は非対応。Androidアプリでそのまま使えるわけではない。 Akkaはアクター単位でメッセージパッシングする設計思想。2022年にライセンスがApache 2.0からBSL(Business Source License)に変わり、2024年10月以降はproduction利用にライセンスキーが必須になった。個人・OSS・startupは無料キー、商用は原則サブスクという体系。汎用ツールというより大規模分散システム向けの専門ツールという位置づけに変わっている。 比較表(Android) 手段 排他制御 順序保証 位置づけ synchronized あり なし 低レベルの排他制御プリミティブ Thread直接生成 なし なし 基本的には避ける ExecutorService 設計次第 直列化すれば可 CPUバウンド処理で有効 AsyncTask なし なし API 30で非推奨 Kotlin Coroutines Mutexで対応 設計次第 Android/Kotlinの主流 Virtual Threads 従来通り 設計次第 JVM側、I/Oバウンド向け(ART非対応) Akka アクターモデル 条件付き 大規模分散システム向け 「排他制御」と「順序保証」は別物。ここを混同すると設計は破綻する。AsyncTaskはAPI 30で非推奨、今はCoroutines(短命処理)とWorkManager(永続処理)を使い分けるのが主流。Virtual Threads(JDK 21)はJVM側の話でARTは非対応。 ...

August 18, 2026 · 1 min

Androidアプリ開発の極意 2,3章あたり ANR、UIスレッド

Androidアプリ開発の極意 | 技術評論社 この本読んでる。2017年に出版してて、内容には古いものがいくつかある。 そこはAIの使いどころ。とりあえず学習したことを軽くメモして、中身について古い場合は指摘を入れたり、今は何が主流なのかといったことを補完するような指示を出した。 サポート付きの読書っぽいことをしながら読み進めて、文章をまとめた。 概要 React Native/Expoでアプリを書いていると、「なぜかカクつくが、クラッシュログには何も残っていない」という現象に何度もぶつかる。 原因を追っていくと、大体はUIスレッドを塞いでいるか、メモリを食い潰しているかのどちらかという同じ根っこに行き着くことが多い気がする。 この記事では、Androidアプリ開発の極意 | 技術評論社の3章UIスレッド/ANRの基礎から、React NativeにおけるJS-ネイティブのスレッド分離、コルーチンとasync/awaitの正体、Android/iOS/RNの非同期処理対応、そして見落とされがちなメモリ管理(OOM・LMK・Javaのメモリリーク・useEffectのクリーンアップ)までを、実務で踏んだり踏みそうな地雷も交えて整理した。 なお、私は仕事でしばしReact Native Expoを利用しているため、なるべくそちらに沿った内容に寄せて本文を作成する。 それによって本の内容とは離れることがしばしあるが、そこは予め留意してほしい。 全体像 まずスレッドの分離構造をざっくり図にする。 [Android/ネイティブ] UIスレッド(メインスレッド) ── 描画・入力処理を担当 │ │ 5秒ブロック → ANRダイアログ ▼ ワーカースレッド/コルーチン(Dispatchers.IO等) ── 重い処理はここへ逃がす [React Native/Expo] JSスレッド ── ビジネスロジック・状態更新・レンダリング計算 │ │ (ブリッジ/JSI経由) ▼ ネイティブUIスレッド ── 実際の描画 │ (任意) Worklet用の別JSコンテキスト ── react-native-worklets-core / Reanimated ポイントは、Androidのネイティブ層には「ANR」という可視化された強制終了の仕組みがあるが、React NativeのJSスレッドが詰まっても同じ仕組みは働かないという非対称性にある。以下、順番に見ていく。 UIスレッド/ANRの基礎 Androidでは、UIスレッド(メインスレッド)が入力イベントに応答できない状態が一定時間続くと、システムが強制的にANR(Application Not Responding)ダイアログを出す。Android公式ドキュメントによれば、入力イベント(キー押下や画面タップなど)に5秒以内に応答しない場合にANRがトリガーされる。これはフォアグラウンドの入力ディスパッチに関する条件で、他にもブロードキャストレシーバーやサービスの応答遅延など複数のANR条件が存在する。 一方、React Native/Expoでは事情が異なる。RNのアーキテクチャはJSスレッドとネイティブUIスレッドが分離されている。JSスレッドが重い処理で詰まっても、ネイティブ側のUIスレッド自体がブロックされているわけではないため、Android OSが検知する「入力ディスパッチのタイムアウト」という意味でのANRダイアログは出にくい。ただし、体感としては「タップしても反応がない」「アニメーションがカクつく」という、ユーザーから見ればANRと区別のつかない現象が起きる。ANRダイアログが出ないぶん、むしろ発見が遅れて厄介とも言える。 補足: 「RNならANRが原理的に起きない」という理解は不正確。JSスレッドが完全にブロックされている間、そのJSスレッドに紐づくタッチイベント処理やRedux的な状態更新も止まるため、ユーザー体験としては同種の「固まり」が発生する。OS側の検知メカニズムが働かないだけで、問題自体は消えていない。 重い処理の逃がし方:InteractionManager(旧API)とWorkletsは別物 RN側で「重い処理をUI/JSスレッドから逃がす」ための手段として、従来は InteractionManager がよく使われていたが、2026年8月リリースのReact Native 0.87で InteractionManager は削除済みであり、公式は代替として requestIdleCallback を案内している。0.82〜0.86の時点では非推奨警告が出るようになっていたため、既存プロジェクトでこの警告を見た人も多いはずだ。以降は「過去にどう動いていたか」の説明として読んでほしい。 ...

August 16, 2026 · 2 min

WSL2でExpo + E2Eテスト(MaestroとDetox)を試みて完全に詰んだ話

TL;DR WSL2環境でExpo(React Native)のE2EテストをMaestroとDetoxで試みたが、どちらもWSL2とWindowsエミュレータの構造的な問題で動かなかった。 かなり過言ではあるが、あえて感情的になるならば、Mobile開発においてMac以外は人権がない。というかあまりにもMac環境以外がだるすぎる。 環境 OS: Windows + WSL2(Ubuntu) Expo SDK 54 / React Native 0.81.5 New Architecture有効 Androidエミュレータ: Windows側で動作(Medium Phone API 36) ADB: Windows側のものをWSL2から参照 Maestroを試みる インストール curl -Ls "https://get.maestro.mobile.dev" | bash export PATH="$HOME/.maestro/bin:$PATH" ここで最初の罠。maestro --helpを叩くとAI系の全く別のCLIツールが応答した。同名の別アプリが先にPATHに入っていたため。$HOME/.maestro/binをPATHの先頭に置くことで解決。 フローの準備 # .maestro/add_and_complete_task.yml appId: com.example.myapp --- - launchApp - tapOn: text: "追加" - inputText: "テストタスク" - tapOn: text: "追加する" - assertVisible: text: "NOW" 実行して即死 You have 0 devices connected, which is not enough to run 1 shards. エミュレータはWindows側で動いており、adb devicesにはemulator-5554が見えている。しかしMaestroはWSL2側でデバイスを探すため認識できない。 --udid=emulator-5554を指定しても: Device emulator-5554 was requested, but it is not connected. maestro start-device --platform=androidを試みると: ...

February 23, 2026 · 2 min

react-native-pdf 6.7.7のiOS表示問題をpatch-packageで解決する

はじめに 業務でreact-native-pdfを使用した際、AndroidではPDFが正常に表示されるのにiOSでは表示されないという問題に遭遇しました。 この記事では、GitHubのissueで共有された解決策であるpatch-packageを使ったパッチ適用方法について解説します。 問題の概要 環境 { "react-native-pdf": "^6.7.7", "react-native": "0.80.1", "react-native-blob-util": "^0.22.2" } 症状 Android: PDF表示が正常に動作 iOS: PDFが表示されない この問題は、React Native 0.80以降でreact-native-pdfを使用した際に発生することが確認されています。 参考: pdf is not displayed,Android is working fine, but there are problems with iOS #966 解決策: patch-packageを使う GitHubのissueで@anhnguyen123さんが共有してくれたパッチファイルを適用することで、この問題を解決できます。 1. patch-packageのインストール まず、patch-packageとpostinstall-postinstallをdevDependenciesとしてインストールします。 # npmの場合 npm install --save-dev patch-package # yarnの場合 yarn add --dev patch-package postinstall-postinstall 参考: patch-package - npm 2. package.jsonにpostinstallスクリプトを追加 package.jsonのscriptsセクションに、postinstallスクリプトを追加します。 { "scripts": { "postinstall": "patch-package" } } このスクリプトにより、npm installまたはyarn installを実行するたびに、自動的にパッチが適用されます。 3. パッチファイルの配置 GitHubのissueからパッチファイルreact-native-pdf+6.7.7.patchをダウンロードし、プロジェクトルートにpatchesディレクトリを作成してそこに配置します。 ...

February 17, 2026 · 2 min

AsyncStorageって裏側何やってんの? - 2.0と3.0の実装の違いを調べてみた

私は普段React NativeでExpo触ってるので、AsyncStorageはよく使うんだけど、「そういえばAsyncStorageって裏側何やってんだろう?」って疑問が湧いてきたので調べてみることにした。 AsyncStorageの裏側 AsyncStorageのバージョンによって実装が少し違う。 AsyncStorage 2.0の実装 iOS/Androidのみ調査。 公式ドキュメント: Where your data is stored - Async Storage iOS (2.0) manifest.jsonファイルに保存される JSONファイル形式 パス: Documents/RCTAsyncLocalStorage_V1/manifest.json 詳細: 1024文字以下のデータはmanifest.jsonに、それより大きいデータは個別ファイル(MD5ハッシュ名)に保存される Android (2.0) SQLiteデータベースに保存される データベース名: RKStorage パス: /data/data/{package_name}/databases/RKStorage AsyncStorage 3.0 (next)の実装 公式ドキュメント: https://react-native-async-storage.github.io/3.0-next/ 対応プラットフォーム Android (SQLite) iOS (SQLite) ✨ macOS (SQLite) visionOS (legacy fallback, single database only) Web (IndexedDB backend) Windows (legacy fallback, single database only) iOS (3.0) SQLiteデータベースに変更された Androidと同じ実装に統一 パフォーマンスと安定性が向上 Android (3.0) 引き続きSQLite より洗練された実装 3.0からはiOSもAndroidも両方SQLiteになって、実装が統一されるそうだ。 互換性 React Native 0.76以降が必要(iOS/Android) Kotlin 2.1.0 iOS minimum target: 13 Android minimum SDK: 24 なぜiOSでmanifest.jsonからSQLiteに変更したのか あくまでも推測ではあるがやってみた。 ...

February 8, 2026 · 2 min

React NativeのTodoアプリで実装する相対時間ベースのプリセット機能

はじめに Todoアプリを使っていると、毎日・毎週繰り返す定型タスクの登録が面倒に感じることはありませんか? 「毎朝のルーチン」「週次ミーティングの準備タスク」など、同じタスクセットを何度も手入力するのは非効率です。この記事では、相対時間を使ったプリセット機能の実装方法を紹介します。 実装したアプリのソースコード: https://github.com/your-repo (適宜修正してください) 問題:絶対時間で期限を保存すると使い回せない 一般的なTodoアプリでプリセット機能を実装する場合、以下のような設計になりがちです: // ❌ よくある実装(絶対時間) interface PresetTask { text: string; dueDate: Date; // 2026-02-08 09:00:00 } この設計の問題点: プリセット作成時の日時が保存される 翌日読み込むと「昨日の9時」が期限になってしまう 毎回手動で期限を修正する必要がある 解決策:相対時間(dueHoursOffset)で管理する 代わりに、「今から何時間後」という相対的な時間で期限を管理します: // ✅ 相対時間ベースの設計 export interface PresetTask { id: string; text: string; priority?: Priority; dueHoursOffset?: number; // 現在時刻からの相対時間(時間単位) checklist?: string[]; } export interface Preset { id: string; name: string; tasks: PresetTask[]; createdAt: Date; } 公式ドキュメント: date-fns addHours: https://date-fns.org/v4.1.0/docs/addHours 実装の全体像 1. プリセット作成時の実装 プリセット編集画面では、期限を「現在時刻から何時間後」として入力します: // screens/PresetEditScreen.tsx const TaskInputRow = ({ item, index, onTaskTextChange, onDueHoursOffsetChange, // ... }: { item: PresetTask; index: number; onTaskTextChange: (index: number, text: string) => void; onDueHoursOffsetChange: (index: number, value: string) => void; // ... }) => { return ( <Card style={styles.taskCard}> <View style={styles.taskInputRow}> <TextInput label={`タスク ${index + 1}`} value={item.text} onChangeText={text => onTaskTextChange(index, text)} mode="outlined" style={styles.taskTextInput} autoComplete="off" autoCorrect={false} /> <TextInput label="期限(時間)" value={item.dueHoursOffset?.toString() || ''} onChangeText={value => { // 数字以外を除去 const filteredValue = value.replace(/[^0-9]/g, ''); onDueHoursOffsetChange(index, filteredValue); }} keyboardType="numeric" mode="outlined" style={styles.dueOffsetInput} /> </View> {/* ... */} </Card> ); }; ポイント: ...

February 8, 2026 · 4 min

三竦(さんすくみ)要件定義書

1. 概要 1.1 ゲームコンセプト 「三竦(さんすくみ)」は、犬・猿・雉の三すくみ関係を利用した追跡型対戦ゲーム。プレイヤーは召喚獣を配置して相手を攻撃しつつ、敵の召喚獣から逃げ切る戦略性とアクション性を兼ね備えたリアルタイムバトルゲーム。 1.2 コアメカニクス 三すくみ関係: 犬 → 猿 → 雉 → 犬 追跡システム: 召喚獣は相手プレイヤーを自動追跡 相性バトル: 有利な召喚獣は相手を一方的に倒す 戦略的配置: 召喚位置とタイミングが勝敗を分ける 1.3 開発目標 シンプル: ルールが3分で理解できる 完成優先: 1ヶ月以内にプレイアブル版完成 Android専用: Expo使用、まずCPU対戦のみ 2. ゲーム仕様 2.1 基本ルール 勝利条件 HP制: 各プレイヤーHP 3 制限時間: 3分 勝敗判定: 相手のHPを0にした方が勝ち 3分経過時、HP多い方が勝ち 同点の場合は引き分け ゲームフロー graph TD A[ゲーム開始] --> B[3分タイマー開始] B --> C{ゲーム中} C --> D[プレイヤー移動] C --> E[召喚獣配置] C --> F[召喚獣追跡] F --> G{当たり判定} G -->|当たった| H[HP-1] G -->|外れた| C H --> I{HP=0?} I -->|Yes| J[ゲーム終了] I -->|No| C C --> K{3分経過?} K -->|Yes| J K -->|No| C J --> L[リザルト表示] 2.2 召喚獣仕様 三すくみ関係 graph LR A[犬] -->|勝つ| B[猿] B -->|勝つ| C[雉] C -->|勝つ| A パラメータ表 召喚獣 速度 寿命 クールダウン 特性 犬 🐕 速い 10秒 10秒 素早く追跡、短命 猿 🐒 中速 10秒 10秒 バランス型 雉 🐦 遅い 10秒 10秒 遅いが長持ち 共通ルール: ...

February 6, 2026 · 6 min

Gemini CLIでExpo Todoアプリを爆速開発した話

やりたかったこと WSL2 環境で Expo を使った Todo アプリを作りたい。ただし、UI ライブラリの選定やナビゲーション設定など、細かい作業は Gemini に任せて効率化したい。 GEMINI.md でルール管理 プロジェクトルートに GEMINI.md を作成し、Gemini に従ってほしいルールを記載しました。 # Gemini AI Coding Rules ## Expo/React Native Specific - Use Expo SDK compatible packages only - Prefer `npx expo install` over `npm install` - Use functional components with hooks ## Expo Specific Rules - Use `@expo/vector-icons` instead of `react-native-vector-icons` - Never use packages that require native linking ## Tech Stack (Fixed) - Expo with TypeScript - React Native Paper for UI - AsyncStorage for persistence このファイルを事前に作っておくことで、Gemini が一貫した品質のコードを生成してくれます。 ...

January 31, 2026 · 2 min

React NativeでTextInputの日本語入力が壊れる問題と解決方法

最近趣味の方でモバイル開発を始めた。 Android端末を普段遣いしている点、仕事上iOSのアプリ周りのリリースがクソだるいことを知っているため Expoで開発しつつも、Androidのみを想定した開発を行っている。 その延長線で引っかかった部分とかをメモに残そうと思ったので記事にした。 問題:日本語入力で変換候補が消える Expo/React Native で Todo アプリを作っていたときTextInput で日本語を入力すると変換候補が一瞬で消えてしまう問題に遭遇。 // 問題のあるコード const [text, setText] = useState(''); <TextInput value={text} onChangeText={setText} /> 「あ」と入力しても変換候補が表示されず、即座に確定されてしまい、ローマ字入力も正常に動作しない。 原因:State更新による再レンダリング React Native の TextInput は制御コンポーネント(value + onChangeText)として使うと、以下の流れで問題が発生する。 日本語入力で「あ」と入力 OS が変換候補を表示するために内部バッファを保持 onChangeText が発火して State 更新 再レンダリングで TextInput が新しい value で再構築 controlled component としての value の強制が、IME の内部バッファと衝突する ← ここが問題! 変換候補が消える autoComplete や autoCorrect が有効だと、OS の補完機能が value の強制にさらに抵抗するため、IME との同期がズレやすくなる。 解決方法:autoComplete と autoCorrect を OFF // 修正後のコード <TextInput value={text} onChangeText={setText} autoComplete="off" autoCorrect={false} /> この2つのプロパティを追加するだけで、IME が安定して動作した。 ...

January 31, 2026 · 1 min