引き出しの中の端末から、Pull Requestの緑のチェックまで。
所要時間はおよそ15分。そのほとんどはruntime bundleのダウンロードです。
arm64-v8a)、Android 9 / API 28以降非対応: 32bit ARM、x86 Android、Docker、KVM、ネスト仮想化。
DroidRunnerはGoogle Playにはありません。ストア外から取得したコードを実行する という性質上、Device & Network Abuseポリシーに抵触するためです。 APKを直接インストールしてください。
推奨: Obtainium。 無人で動くRunnerの更新を、人がリリースに気づくまで待たせるべきではないからです。 Add app からリポジトリURLを貼り付けます:
https://github.com/m96-chan/DroidRunner
APKフィルタに droidrunner-v.*\.apk を設定してください。各リリースには
対応ソースアーカイブ droidrunner-v<version>-source.tar.gz も入っている
ため、フィルタが無いとObtainiumは2つのアセットから選ぶことになります。設定後は、
新しいタグが公開されるたびにObtainiumがアプリを上書き更新します。
手動:
最新リリース
から droidrunner-v<version>.apk をダウンロードして開きます。
ブラウザやファイルマネージャに対して「提供元不明のアプリ」の許可を一度求められます。
DroidRunnerはトークンを貼り付けさせるのではなくGitHub Appとしてサインインします。 そのため、担当させるRepositoryまたはOrganizationにAppを入れておく必要があります。
github.com/apps/droidgithubrunner を開き、Runnerに担当させるリポジトリへインストールします。 要求する権限は Administration: read and write で、 これはself-hosted runnerの登録に必要な権限です。 同時にリポジトリ設定を変更できる権限でもあるので、 所有する全リポジトリにではなく、対象を選んで付与してください。
入れ忘れていても、サインイン時にアプリが検出して案内します。
DroidRunnerを起動し、ヘッダの⚙からsetup画面を開いて Connect GitHubをタップします。
8文字のコードが表示され、同時にクリップボードにコピーされます。 github.com/login/device で入力して承認してください。端末上でも、別のPCからでも構いません。
これはOAuth Device Flowなので、APKの中にclient secretは入っておらず、 あなたがPATを発行したり更新したりする必要もありません。 得られたユーザートークンはAndroid Keystoreで暗号化されてAndroid側に留まり、 必要なときに短命のregistration tokenと交換されます。 ジョブが動くLinux環境にトークンが渡ることはありません。
Repository、またはAppをインストール済みのOrganizationを選び、 Registerをタップします。
登録はプログレスダイアログの中で進みます。registration tokenの取得、
最新のruntime-*リリースの探索、manifest署名の検証、
約200MBのbundleのダウンロード、SHA-256の照合、展開、
そして公式config.shの実行まで。
回線が細いと、ここがセットアップで一番長い工程になります。
あとから登録先を変えることもできます。Runnerを停止して新しい対象を選ぶと、 ボタンがRe-registerに変わります。
引き続きsetup画面で、この端末がいつ仕事を受けてよいかを決めます。
バッテリー残量、thermal status、空き容量の しきい値が、ジョブを保留する条件になります。高温・容量不足、あるいは バッテリー動作かつ残量がしきい値未満のあいだは新しいジョブを受け取らず、 条件が解消すれば自動で再開します。 例外はcritical thermalで、これは実行中のジョブも中断します。 サーマルスロットリング中の端末が出す数値は、そもそも信用に値しないからです。
給電が消えただけでは保留しません。充電済みの端末は、残量が実際にしきい値を 割るまで停電をまたいで走り続けます。バッテリーを積んでいる意味はほぼそこにあります。 Run only on mains powerは別のトグルで、既定はオフです。 自分の充電を絶対に使わせたくない端末のために残してあります。
ephemeralモードは、ジョブごとにRunnerを再登録し work directoryを消去します。ジョブの起動は遅くなりますが、 複数のWorkflow — あるいは複数の人 — がこの端末に届きうるなら、 こちらを既定にすべきです。
起動時に開始を有効にすると、再起動後もRunnerが戻ります — ただし端末がアンロックされてからです。Androidは端末がcredential-lock状態のあいだ 起動ブロードキャストを保留し、最初のアンロックで配信します。それまでは runtimeも保存済みの認証情報も、そもそも読み出せません。 つまりセキュアなロック画面のある端末は、停電のあと誰かが手に取るまでCIに戻りません。 CI専用の端末にはロック画面を設定しないでください。再起動が本当に無人で完結するのは、 その構成だけです。それでも待ち時間が生じた場合は、 Runnerパネルが端末のオフラインだった時間を表示します。
アプリが求めるバッテリー最適化の除外も許可しておいてください。 Runnerが夜のあいだに静かにオフラインになる原因は、 たいていベンダー製のタスクキラーです。
ダッシュボードに戻ります。数秒でRunnerパネルが listening for jobs になり、ヘッダにも同じ状態が出ます。
登録済みの端末は、アプリを起動するたびにRunnerを自動で開始します。 手動のStart/StopはRunnerパネルにあります。GitHub側では Settings → Actions → Runners に、ラベル付きで端末が現れます。
.github/workflows/device.yml としてコミットします:
name: Device smoke on: workflow_dispatch: permissions: contents: read jobs: smoke: runs-on: [self-hosted, android, arm64] steps: - uses: actions/checkout@v4 - run: uname -a && nproc && free -h
Actionsタブから実行します。端末側のRunnerパネルが running jobに切り替わってログが流れ、 完了するとジョブカウンタが増えます。
runs-onのラベルが、
端末が実際に登録したラベルと一致しているか確認してください。
ラベルはアプリのタイトル下と、GitHubのRunner一覧に出ています。
runs-onのラベルは全て揃っている必要があり、
タイプミスがあるとジョブは失敗せずに待ち続けます。
droidrunner-deviceはRunnerが起動するたびにAPKからゲストへ入るので、
対話するDevice Agentと常に同じ世代に保たれます。そのAgentとは、
実行中のジョブ向けに発行されたトークンで認証しながら、loopback経由で通信します。
runs-on: [self-hosted, android, nnapi-accelerator] steps: - run: droidrunner-device capabilities - run: droidrunner-device devices - run: droidrunner-device test model my-model.tflite --device google-edgetpu --iterations 30
android-npu ではなく nnapi-accelerator です。
android-npu は「そのSoCファミリーにNPUがあると分かっている」という
ヒントにすぎず、約束ではありません。これを指定したジョブは、
全部CPUで走って何も言わない端末に着地することがあります。
レイテンシは「グラフが受理された」ことしか言いません。正しく計算されたかは 別の問いで、モデルを出荷するなら重要なのはそちらです。ベンダーのシリコン上でだけ出る バグは、手元のPCで検査している限り正しいコードと見分けがつきません。
- uses: m96-chan/DroidRunner/actions/run-model@main id: device with: model: build/model.tflite device: qnn-htp inputs: fixtures/input-0.bin output-dir: out - run: test "${{ steps.device.outputs.executed }}" = accelerator - run: cmp out/output-0.bin fixtures/golden-0.bin
結果には必ず executed(依頼した相手ではなく実際に実行した相手)が
付き、帰属できない実行は数値として報告せず拒否します。他リポジトリが依存できる形は
RESULT-CONTRACT.md
に書いてあります。
ほぼ確実にベンダーの電池管理です。アプリ側は二段構えで、サービスはstickyにしてあり、 それでも落とされた場合は監視ジョブが15分以内に起こし直します(Xiaomiで両方が実際に 働くことを実測。片方が取りこぼしても、もう片方が拾いました)。あわせて、アプリが要求する バッテリー最適化の除外を許可し、Xiaomi/MIUI、Samsung、Huawei、OPPOでは最近のアプリ 一覧でロックし、自動起動も許可してください。
ただし、それらが一切通じないROMがあります。RedMagic 8では、stickyなサービスも、 アラームも、全制約を満たした監視ジョブも、30分待って一度も実行されませんでした (バッテリー最適化は除外済み、バックグラウンド制限もない状態で)。メーカーのアプリ別 電源設定画面は署名権限で保護されていてアプリからは開けないため、端末の設定から 手動でDroidRunnerを許可してください。
アプリの再インストールや強制停止のあとに出ます。GitHub側に古いリスナーセッションが 残っている状態です。リスナーが再試行してRunner reconnectedを出せば解消します。 それまでキュー中のジョブは割り当てられないので、数分待つか、 ダッシュボードからRunnerを再起動してください。
再インストールではなく、setup画面から再登録してください。古いRunner状態は アプリ側で消します。トークンが期限切れの場合は、サインインし直してください。 ユーザートークンには有効期限を設定でき、期限切れはログイン要求ではなく 登録の失敗として現れます。
理由はダッシュボードに出ています。バッテリー動作かつ残量がしきい値未満、 上がったthermal status、下限を割った空き容量のいずれかです。 Run only on mains powerをオンにしているなら、給電が切れただけでも保留になります。 充電する、冷ます、あるいはsetup画面でしきい値を下げてください。 これはバグではなく、ジョブ受付制御が仕事をしている状態です。
APKを開いたアプリに対して「不明なアプリのインストール」を許可してください。
MIUIでadbからサイドロードする場合は、開発者オプションの
USBデバッグ(インストール)も必要です。
自前ビルドの場合、Device Flow用のGitHub App(無料・サーバ不要)、 APK署名鍵、そしてruntime bundleを配布するならmanifest署名鍵が必要です。 いずれも README に手順があります。最初のGradleビルドの前に一度だけ必要な prootのクロスコンパイルも同様です:
git clone https://github.com/m96-chan/DroidRunner cd DroidRunner ANDROID_NDK_ROOT=$ANDROID_HOME/ndk/<version> runtime/build-proot.sh ./gradlew assembleDebug
信頼する署名鍵を設定していないビルドはruntime bundleを検証できません。 その場合は、検証したふりをするのではなく、インストール時にその旨を表示します。