Swift 5 macOS应用中ADB命令实现、工具打包及参数问题排查
Hey there! Let's walk through how to build your macOS app that interacts with Android devices using ADB—fixing your current shell function issue, packaging platform-tools, and implementing all the features you need.
1. 先修复你的Shell函数参数错误
Your current shell function has a mix-up in how it handles ADB commands. The Process class in Swift works like this:
executableURL(orlaunchPathin older versions) points directly to the ADB executable file, not a command string.argumentsis an array of the parameters you want to pass to ADB, not a hardcoded "args" or full command.
For example, to run adb devices, you need to tell Process to use the ADB executable, then pass ["devices"] as arguments. Here's a revised, reusable function that fixes this and even captures output/errors:
func runADBCommand(arguments: [String]) -> Int32 { let task = Process() // Get path to ADB inside your app bundle (we'll set this up next) guard let adbPath = Bundle.main.url( forResource: "adb", withExtension: "", subdirectory: "platform-tools" )?.path else { print("ADB executable not found in app bundle") return -1 } task.executableURL = URL(fileURLWithPath: adbPath) task.arguments = arguments // Capture output and error messages let outputPipe = Pipe() let errorPipe = Pipe() task.standardOutput = outputPipe task.standardError = errorPipe do { try task.run() // Read and print output/error (optional, but helpful for debugging) let outputString = String( data: outputPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8 ) let errorString = String( data: errorPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8 ) outputString.flatMap { print("ADB Output:\n\($0)") } errorString.flatMap { print("ADB Error:\n\($0)") } task.waitUntilExit() return task.terminationStatus } catch { print("Failed to run ADB command: \(error.localizedDescription)") return -1 } }
Now to list connected devices, just call:
let deviceStatus = runADBCommand(arguments: ["devices"])
No more "unknown command args" errors!
2. Package platform-tools into your app
To avoid forcing users to install the Android SDK, you can bundle the platform-tools directly into your app:
- Download the macOS version of platform-tools (from the Android developer site)
- Unzip the folder, then drag the entire
platform-toolsdirectory into your Xcode project - In the Xcode File Inspector, make sure your app target is checked under "Add to targets" and "Copy items if needed" is enabled
- To ensure ADB has executable permissions, add a Run Script phase in your target's Build Phases:
chmod +x "$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.app/Contents/Resources/platform-tools/adb"
This way, your app will always have access to ADB without relying on external installs.
3. Implement your core features
With the fixed command function and bundled ADB, you can now build out each feature you need:
Push .txt/.obb files to Android
Use the adb push command, which takes a local file path and a remote device path:
func pushFile(localPath: String, remotePath: String) -> Int32 { return runADBCommand(arguments: ["push", localPath, remotePath]) } // Example: Push a local text file to the device's Downloads folder let pushStatus = pushFile( localPath: "/Users/yourname/Documents/notes.txt", remotePath: "/sdcard/Download/" )
Install APK files
Use adb install (add -r to overwrite an existing installation):
func installAPK(apkPath: String, overwriteExisting: Bool = false) -> Int32 { var args = ["install"] if overwriteExisting { args.append("-r") } args.append(apkPath) return runADBCommand(arguments: args) } // Example: Install an APK the user selects installAPK(apkPath: "/Users/yourname/Downloads/myapp.apk")
Grant app permissions
Use adb shell pm grant to give permissions to a specific app:
func grantAppPermission(packageName: String, permission: String) -> Int32 { return runADBCommand(arguments: ["shell", "pm", "grant", packageName, permission]) } // Example: Grant storage permission to an app let grantStatus = grantAppPermission( packageName: "com.yourcompany.yourapp", permission: "android.permission.WRITE_EXTERNAL_STORAGE" )
4. Important macOS Sandbox Notes
macOS apps run in a sandbox by default, so you can't access arbitrary files on the user's system without permission. Use NSOpenPanel to let users select files (like APKs or text files) instead of hardcoding paths:
func promptForFile(allowedTypes: [String], completion: @escaping (String?) -> Void) { let openPanel = NSOpenPanel() openPanel.allowsMultipleSelection = false openPanel.canChooseDirectories = false openPanel.allowedFileTypes = allowedTypes if openPanel.runModal() == .OK { completion(openPanel.url?.path) } else { completion(nil) } } // Example: Prompt user to select an APK promptForFile(allowedTypes: ["apk"]) { apkPath in guard let path = apkPath else { return } let status = installAPK(apkPath: path) print("Installation status code: \(status)") }
Also, make sure users enable USB debugging on their Android devices and trust your macOS computer when prompted—otherwise ADB won't detect the device.
内容的提问来源于stack exchange,提问作者ModernEra

