8年前のMacBook Air(Intel)で全テスト済み。7本のMacアプリをソロ開発した実体験から書いています。案件でも広告でもありません。
HiyokoKitにはAPKインストールとAndroidアプリマネージャーが含まれている。どちらも内部でADBを使っている。実装を紹介する。
APKインストール
#[tauri::command]
async fn install_apk(apk_path: String) -> Result<String, AppError> {
let output = tokio::process::Command::new("adb")
.args(["install", "-r", &apk_path]) // -r = 既存アプリの上書きインストール
.output()
.await?;
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
if stdout.contains("Success") {
Ok("インストール成功".into())
} else {
let error = if stderr.contains("INSTALL_FAILED_VERSION_DOWNGRADE") {
"新しいバージョンの上に古いバージョンはインストールできません。ダウングレードは -d フラグを使用してください。"
} else if stderr.contains("INSTALL_FAILED_ALREADY_EXISTS") {
"アプリは既にインストールされています。再インストールオプションを使用してください。"
} else {
"インストールに失敗しました"
};
Err(AppError::Adb(error.into()))
}
}
エラーコードを個別にパースする——「インストール失敗」という汎用メッセージはユーザーの役に立たない。
アプリ一覧取得
#[derive(Serialize)]
pub struct AppInfo {
package_name: String,
is_system: bool,
}
#[tauri::command]
async fn list_apps(include_system: bool) -> Result<Vec<AppInfo>, AppError> {
let flag = if include_system { "-l" } else { "-3" }; // -3 = サードパーティのみ
let output = tokio::process::Command::new("adb")
.args(["shell", "pm", "list", "packages", flag])
.output()
.await?;
let apps = String::from_utf8_lossy(&output.stdout)
.lines()
.filter_map(|line| {
line.strip_prefix("package:").map(|pkg| AppInfo {
package_name: pkg.trim().to_string(),
is_system: !include_system,
})
})
.collect();
Ok(apps)
}
アプリのアンインストール
#[tauri::command]
async fn uninstall_app(package_name: String) -> Result<(), AppError> {
let output = tokio::process::Command::new("adb")
.args(["uninstall", &package_name])
.output()
.await?;
if String::from_utf8_lossy(&output.stdout).contains("Success") {
Ok(())
} else {
Err(AppError::Adb(format!("{} のアンインストールに失敗しました", package_name)))
}
}
AndroidとMacのクリップボード同期
#[tauri::command]
async fn push_clipboard_to_android(text: String) -> Result<(), AppError> {
tokio::process::Command::new("adb")
.args(["shell", "am", "broadcast", "-a", "clipper.set", "-e", "text", &text])
.status()
.await?;
Ok(())
}
#[tauri::command]
async fn get_android_clipboard() -> Result<String, AppError> {
let output = tokio::process::Command::new("adb")
.args(["shell", "am", "broadcast", "-a", "clipper.get"])
.output()
.await?;
let stdout = String::from_utf8_lossy(&output.stdout);
extract_clipboard_from_broadcast(&stdout)
}
注意:ADB経由のクリップボード同期にはAndroidデバイス側にClipperなどのアプリが必要。
ADBが見つからない場合のエラーハンドリング
fn check_adb_available() -> Result<(), AppError> {
Command::new("adb")
.arg("version")
.output()
.map_err(|_| AppError::Adb(
"ADBが見つかりません。Android Platform Toolsをインストールしてください。".into()
))?;
Ok(())
}
初回使用時ではなく、起動時にチェックする。ADBがない場合はすぐにユーザーに伝えるべきだ。
記事が参考になったら ❤️ もらえると励みになります!