読書ブログAI開発記・第6回(最終回)|1個のAPKにまとめて、アプリ完成
第5回までに完成したAndroidプロジェクトを、自分用のデバッグAPKへまとめる記事です。実際に生成へ成功した処理をもとに、説明用のコマンドとBATを掲載しています。個人のユーザー名・作業パス・認証情報は載せていません。
コマンドはWindowsのコマンドプロンプト向けです。今回の例は
appモジュールのdebugビルドを前提にしています。完成したので、APKとして残しておきたい
開発中はAndroid Studioから実行していた。しかし、自分用アプリとして使うなら、スマホへコピーしてインストールできるファイルを残しておきたい。
今回のプロジェクトには、アプリ本体とGoogle Tensor用runtimeの追加モジュールがある。別々のアプリを作っているのではなく、一つのアプリを構成する部品が分かれている状態だった。
Bundleを作れば、そのままスマホへ入るわけではなかった
これまで実行していたビルドは、gradlew.bat :app:bundleDebug。生成されるのはAndroid App Bundle、拡張子が.aabのファイルである。
今回欲しいのは、スマホへコピーしてタップできるAPKだった。そこで、Bundleからインストール用APKを生成する工程を加えることになった。
開発時の分割APKを使う方法もあるが、それぞれを別々にタップする使い方とは異なる。今回は保存して扱いやすい1個のファイルを作る方へ進めた。
似た拡張子を、先に整理する
今回の処理で出てくるファイルは3種類ある。名前が似ているので、完成したあとに迷わないよう整理しておく。
| 拡張子 | 今回の役割 | スマホへ渡すもの |
|---|---|---|
.aab | Gradleで作るAndroid App Bundle。APK生成の材料 | 直接タップしてインストールするものではない |
.apks | bundletoolが生成するAPKセットの入れ物 | 今回は中からAPKを取り出す |
.apk | 取り出したUniversal APK | 今回インストールに使う完成ファイル |
bundletool build-apksに--mode=universalを指定すると、1個のAPKを含むAPKセットを生成できる。機能モジュールをそのAPKに含める条件として、Manifestのdist:fusingも確認する。Android公式:bundletool
Tensor runtimeを含められる設定にする
まず、Tensor用Dynamic FeatureのManifestを調整した。今回のパッチでは、インストール時に組み込む設定と、Universal APKへ含めるための設定を整えた。
そのうえで、BundleからAPKを生成するbundletoolを使う。最終的な生成処理はWindows用のbuild_universal_apk.batへまとめた。
Universal APKにしたからといって、すべてのAndroid端末で今回のTensor向けAI構成が使えるという意味ではない。ここで目指したのは、自分の端末向けに作ったアプリを1個のインストール用ファイルへまとめることだった。
変更するのは、Tensor用モジュールのManifest
今回のプロジェクトでは、次のファイルを調整した。これはプロジェクトからの相対パスである。
litert_npu_runtime_libraries\google_tensor_runtime\src\main\AndroidManifest.xml
既存の<dist:module>内で、配信設定とfusing設定を次の形にする。Manifest全体の置き換えではなく、該当部分の例だ。既存のモジュール名・タイトル・application設定などは保持する。
<dist:delivery>
<dist:install-time>
<dist:removable dist:value="false" />
</dist:install-time>
</dist:delivery>
<dist:fusing dist:include="true" />
distは、Manifestのルートでxmlns:dist="http://schemas.android.com/apk/distribution"として宣言されている名前空間である。既に同じ要素がある場合は、重ねて追加せず既存の値を変更する。
removable=falseはインストール時モジュールを分割APK生成時に本体へ統合する設定、fusing=trueはUniversal APKへ含めるためにも使う設定である。今回のパッチでは両方を整えた。Android公式:Feature ModuleのManifest
まずは3段階のコマンドで確認する
使うのは、既存プロジェクトのGradle Wrapper、ビルドに使えるJDK、bundletool、ZIPから取り出すためのPython 3。javaとpythonはコマンドプロンプトから実行できる状態にしておく。
今回の成功時に使ったbundletoolは1.18.3だった。以下のサンプルもその版を例にする。「常に最新版」という意味ではなく、今回使った版の記録である。bundletool公式配布ページからJARを用意し、プロジェクト直下へ置く。
まず、gradlew.batがあるプロジェクト直下でBundleを作る。
gradlew.bat :app:bundleDebug
今回の構成ではapp\build\outputs\bundle\debug\app-debug.aabができる。次に、それをUniversal APKを含むAPKセットへ変換する。
if not exist "apk_output" mkdir "apk_output"
java -jar "bundletool-all-1.18.3.jar" build-apks ^
--bundle="app\build\outputs\bundle\debug\app-debug.aab" ^
--output="apk_output\BookBlogAI-debug.apks" ^
--mode=universal --overwrite
最後に、.apksの中のuniversal.apkを取り出す。次のコマンドは1行で実行する。
python -c "from pathlib import Path; import zipfile; z=zipfile.ZipFile(r'apk_output\BookBlogAI-debug.apks'); Path(r'apk_output\BookBlogAI-debug-universal.apk').write_bytes(z.read('universal.apk')); z.close()"
これで、使いたい名前のAPKがapk_outputへ出力される。コマンドプロンプトの複数行例では、行末の^が継続を表す。
最後のBATでも、少しつまずいた
APK生成用のBATを実行すると、最初はbundletoolが見つからずに停止した。さらに、日本語のメッセージがWindowsのコマンドプロンプトで文字化けし、一部がコマンドとして解釈される問題も起きた。
その後は、ダウンロード後の変数を確認する処理にも修正が必要になった。取得に失敗したように見える表示でも、実際には確認側の変数展開に問題があった。
ここまで来ると、AIそのものとは関係のないWindows側の仕上げである。最終的には生成処理が最後まで通るようになった。
普段はBATを実行するだけにする
毎回3段階のコマンドを打たなくて済むよう、生成処理をBATへまとめる。実際の完成版にはbundletoolの自動取得も加えたが、記事のサンプルはJARを先に置く形に絞った。ビルド・APK生成・抽出の流れは同じである。
以下をbuild_universal_apk.batとしてプロジェクト直下に保存する。本文は英数字だけにし、Windows用の改行(CRLF)で保存する。
@echo off
setlocal EnableExtensions DisableDelayedExpansion
cd /d "%~dp0"
if not exist "gradlew.bat" (
echo ERROR: gradlew.bat was not found.
exit /b 1
)
where java >nul 2>nul
if errorlevel 1 (
echo ERROR: java was not found in PATH.
exit /b 1
)
where python >nul 2>nul
if errorlevel 1 (
echo ERROR: python was not found in PATH.
exit /b 1
)
set "BUNDLETOOL=%CD%\bundletool-all-1.18.3.jar"
if not exist "%BUNDLETOOL%" (
echo ERROR: Place bundletool-all-1.18.3.jar in this folder.
exit /b 1
)
set "AAB=%CD%\app\build\outputs\bundle\debug\app-debug.aab"
set "OUTDIR=%CD%\apk_output"
set "APKS=%OUTDIR%\BookBlogAI-debug.apks"
set "APK=%OUTDIR%\BookBlogAI-debug-universal.apk"
set "TMPAPK=%OUTDIR%\BookBlogAI-debug-universal.tmp.apk"
if not exist "%OUTDIR%" mkdir "%OUTDIR%"
if not exist "%OUTDIR%" goto :failed
echo [1/3] Building the Debug App Bundle...
call gradlew.bat :app:bundleDebug
if errorlevel 1 goto :failed
if not exist "%AAB%" goto :failed
echo [2/3] Generating one Universal APK...
java -jar "%BUNDLETOOL%" build-apks --bundle="%AAB%" --output="%APKS%" --mode=universal --overwrite
if errorlevel 1 goto :failed
echo [3/3] Extracting universal.apk...
python -c "import sys,zipfile; from pathlib import Path; z=zipfile.ZipFile(sys.argv[1]); Path(sys.argv[2]).write_bytes(z.read('universal.apk')); z.close()" "%APKS%" "%TMPAPK%"
if errorlevel 1 goto :failed
if not exist "%TMPAPK%" goto :failed
move /y "%TMPAPK%" "%APK%" >nul
if errorlevel 1 goto :failed
echo SUCCESS: Universal APK created.
echo "%APK%"
exit /b 0
:failed
echo ERROR: APK generation did not complete.
exit /b 1
別のBATであるgradlew.batを呼ぶ箇所にはcallを付け、Gradleが終わったら後続の変換へ戻るようにする。各段階でエラーを確認し、途中で失敗した場合に成功表示へ進ませない。
掲載版では、APKを一時ファイルへ取り出してから完成名へ置き換える形に整理した。ビルドや抽出が失敗した場合、以前の完成APKが残ることがあるので、最新版ができたかどうかはSUCCESSの表示で判断する。
そして、Universal APKの生成に成功した
成功時の処理は3段階。Debug App Bundleをビルドし、bundletoolでUniversal APKを生成し、最後にAPKセットの中から取り出す。
[1/3] Building the Debug App Bundle...
[2/3] Generating one Universal APK from the App Bundle...
[3/3] Extracting universal.apk...
SUCCESS: Universal APK created.
完成したファイルは、プロジェクト内の次の場所へ出力された。
apk_output\BookBlogAI-debug-universal.apk
ビルドログではデバッグ用キーストアで署名されており、今回の成果物は自分用のデバッグAPKである。ここで確認できたのはUniversal APKの生成成功まで。統合後の端末上での動作は、インストールして確認する段階になる。
スマホへインストールする
生成されたBookBlogAI-debug-universal.apkをスマホへコピーし、ファイルから開いてインストールする。端末がインストール元の許可を求めた場合は、使用しているファイルアプリなどに対して許可する。
USB接続で入れる場合は、USBデバッグを有効にし、PCからの接続を端末側で許可したうえで、プロジェクト直下から次を実行する。Android SDKを標準の場所へ入れた環境の例で、ユーザー名は環境変数に置き換えている。
"%LOCALAPPDATA%\Android\Sdk\platform-tools\adb.exe" install -r "apk_output\BookBlogAI-debug-universal.apk"
-rは既存アプリへの再インストールを指定する。更新として扱えるには、パッケージ名や署名などが既存アプリと整合している必要がある。今回の生成ログでは開発用のデバッグキーストアが使用された。更新時の署名の考え方はAndroid公式:アプリへの署名を参照してほしい。
Gemmaのモデルファイルは別に用意する
今回1個にまとめるのは、アプリ本体とTensor用runtimeなどのビルド対象である。アプリから選んで読み込むGemmaの.litertlmモデルは、この構成では別途取り込むファイルとして扱っている。
新しい端末へインストールしただけで、以前の端末のモデル・設定・履歴まで揃うわけではない。対応する端末とモデルを用意し、アプリ内で必要な設定を行う。
同じアプリへの更新でデータが維持される場合と、新規インストールは分けて考える。自分用APKを残すときも、ソースプロジェクトと署名に使ったキーストアは別に保管しておきたい。
隣にある「.apks」は何なのか
出力先を見ると、BookBlogAI-debug.apksという似た名前のファイルもできていた。こちらはAPKセットを格納した入れ物で、今回使いたいAPKを取り出す途中に生成される。
.apksの方は入れ物だ。今回は、その中からuniversal.apkを取り出した。BookBlogAI-debug-universal.apkの方だ。今後ソースを更新したときも、生成用BATを実行して最新版のAPKを作る。毎回Bundleの変換手順を手で打ち直さずに済むようになった。
build_universal_apk.bat
完成APKと、再ビルドに使うものを分けて残す
インストールするときに渡すのは1個のAPK。後で直したいときには、ソースや生成ツールも必要になる。今回の保存物は、次のように分けると分かりやすい。
| 保存するもの | 用途 |
|---|---|
BookBlogAI-debug-universal.apk | スマホへのインストール |
| ソースプロジェクトと生成用BAT | 修正してAPKを作り直す |
| bundletoolのJAR | BundleからAPKを生成する |
| 使用した署名用キーストア | 同じ署名で更新版を作る |
| 対応するGemmaモデル | アプリへ読み込んで文章生成に使う |
.aabと.apksは今回の生成工程の途中にできるものなので、APKをインストールするために一緒にコピーする必要はない。ソースとビルド環境が揃っていれば再生成できる。
本の感想を書きたい、から始まったアプリの完成
最初にやりたかったのは、本を読んだあと、写真と感想をブログに残すことだった。途中で端末内AIを使う方向へ進み、Tensor G5でGemmaを動かし、そこから投稿画面、書誌情報の確認、Blogger連携、履歴管理までつながった。
完成したアプリで行うことは、本の写真を選び、書名や感想、評価を入力して記事を作ること。裏側では、端末内の文章生成と通信処理を組み合わせ、Bloggerの下書きまで仕上げる。
AIが動くことを確認するテスト画面から、自分の読書記録を残すアプリへ。最後にインストール用のAPKも作成できた。ここで一度開発を区切り、実際の読書に使っていく。
完成したアプリのスクリーンショット
この記事は実際の開発内容をもとに、説明の順序を整理し、登場人物の会話として再構成しています。構成・文章・会話の作成にはOpenAIのChatGPTを使用しました。掲載コマンドとBATは実際に成功した生成処理をもとに整理したサンプルで、個人の作業パスや認証情報は掲載していません。アプリ内の文章生成には、端末内で実行するGemmaを使用しています。







0 件のコメント:
コメントを投稿