Android 手机通过 adblib 使用 ADB Wi-Fi 控制 Android 9 开发板
项目中需要让 Android 手机通过 Wi-Fi 直接连接 Android 9 开发板的 adbd,不经过电脑,也不在手机端安装 adb。这里使用 tananaev/adblib 建立 ADB TCP 通信。
通信链路:
rust
Android 手机 App -> Socket -> adblib -> Wi-Fi -> Android 9 开发板:5555 -> adbd -> shell
1. 整体流程
开发板启动后在代码中打开 adbd 的 TCP 5555 端口,手机和开发板进入同一局域网,手机取得开发板 IP 后建立 TCP Socket,adblib 通过 Socket 与开发板 adbd 通信。当前开发板关闭了 ADB RSA 授权,因此双方完成 CNXN 后可以直接建立 ADB 会话。
rust
开发板启动 -> 开启 adbd TCP 5555 -> 手机获取开发板 IP -> Socket 连接 IP:5555 -> CNXN -> ADB 连接建立 -> OPEN shell:xxx -> WRTE 返回结果
实际通信:
lua
Android 手机 Android 9 开发板
| |
|-------- TCP Connect :5555 --------->|
| |
|------------- CNXN ----------------->|
|<------------ CNXN ------------------|
| |
|------ OPEN shell:getprop ---------->|
|<------------ OKAY ------------------|
|<------------ WRTE ------------------|
|------------- OKAY ----------------->|
|<------------ CLSE ------------------|
2. 开发板代码开启 adbd TCP 5555
这部分运行在 Android 9 开发板。当前项目使用的是可控制系统服务的开发板环境,不是普通三方 App 权限模型。核心就是设置 service.adb.tcp.port=5555,然后重启 adbd。
Root 环境可以直接执行:
arduino
Runtime.getRuntime().exec(new String[]{"su", "-c", "setprop service.adb.tcp.port 5555; stop adbd; start adbd"});
如果希望重启开发板后仍保持 TCP 5555,可以写 persist.adb.tcp.port:
arduino
Runtime.getRuntime().exec(new String[]{"su", "-c", "setprop persist.adb.tcp.port 5555; stop adbd; start adbd"});
Android 9 的 adbd 启动时读取:
yaml
service.adb.tcp.port
↓ 如果为空
persist.adb.tcp.port
↓
5555
↓
监听 TCP 5555
AOSP Android 9 的 adbd 就按这个顺序读取端口配置。 adb tcpip 5555 内部同样会把端口写入 service.adb.tcp.port。
如果只需要本次开机有效,使用:
service.adb.tcp.port
如果开发板每次启动都需要开放 5555,使用:
persist.adb.tcp.port
检查结果:
arduino
Process process = Runtime.getRuntime().exec(new String[]{"su", "-c", "getprop service.adb.tcp.port"});
或者直接检查监听端口:
perl
netstat -tunlp | grep 5555
这里的关键不是启动另一个 ADB 程序,而是让开发板原本运行的 adbd 增加 TCP listener:
adbd
├─ USB ADB
└─ TCP :5555
3. 手机引入 adblib
项目加入:
arduino
implementation 'com.tananaev:adblib:1.3'
AndroidManifest 添加网络权限:
ini
<uses-permission android:name="android.permission.INTERNET" />
手机端不需要 adb 可执行文件,也不需要 USB Host,adblib 直接通过 Socket 与开发板 adbd 通信。
4. 手机连接开发板
手机取得开发板 IP 后,通过 Socket 连接开发板的 5555 端口,再创建 AdbCrypto 和 AdbConnection。adblib 官方示例也是这套流程:Socket -> AdbCrypto -> AdbConnection -> connect -> open。
ini
Socket socket = new Socket("192.168.1.42", 5555);
AdbCrypto crypto = AdbCrypto.generateAdbKeyPair(new AdbBase64() {
@Override
public String encodeToString(byte[] data) {
return Base64.encodeToString(data, Base64.NO_WRAP);
}
});
AdbConnection connection = AdbConnection.create(socket, crypto);
connection.connect();
AdbStream stream = connection.open("shell:logcat");
这里虽然创建了 RSA Key,但不等于每次都会出现授权弹窗。AdbCrypto 是 adblib ADB 连接流程的一部分。当目标 adbd 返回 AUTH TOKEN 时,库会使用它签名;如果目标直接返回 CNXN,连接则直接建立。源码中的处理顺序就是 CMD_AUTH 使用 crypto,CMD_CNXN 标记连接成功。
整体流程:
css
开发板启动
↓
adbd 开启 TCP 5555
↓
手机连接 开发板IP:5555
↓
创建 AdbCrypto
↓
创建 AdbConnection
↓
connection.connect()
↓
CNXN / AUTH
↓
ADB 连接建立
↓
connection.open("shell:xxx")
↓
读取 WRTE 返回的数据
执行一个简单命令:
ini
Socket socket = new Socket("192.168.1.42", 5555);
AdbCrypto crypto = AdbCrypto.generateAdbKeyPair(new AdbBase64() {
@Override
public String encodeToString(byte[] data) {
return Base64.encodeToString(data, Base64.NO_WRAP);
}
});
AdbConnection connection = AdbConnection.create(socket, crypto);
connection.connect();
AdbStream stream = connection.open("shell:getprop ro.product.model");
while (!stream.isClosed()) {
byte[] data = stream.read();
if (data != null) Log.d("ADB", new String(data, StandardCharsets.UTF_8));
}
stream.close();
connection.close();
整个手机端的核心就是:
rust
Socket -> AdbCrypto -> AdbConnection -> connect -> open("shell:xxx") -> read
5. 执行 ADB Shell
连接建立后通过 OPEN shell:xxx 执行命令:
ini
AdbStream stream = connection.open("shell:getprop ro.product.model");
while (!stream.isClosed()) {
byte[] data = stream.read();
if (data != null) Log.d("ADB", new String(data, StandardCharsets.UTF_8));
}
stream.close();
connection.close();
它对应:
adb shell getprop ro.product.model
只是手机上没有真正运行 adb。
更换 shell: 后面的命令即可执行其他操作:
ini
AdbStream stream = connection.open("shell:getprop ro.build.version.release");
ini
AdbStream stream = connection.open("shell:ps -A");
ini
AdbStream stream = connection.open("shell:ip addr");
ini
AdbStream stream = connection.open("shell:pm list packages");
ini
AdbStream stream = connection.open("shell:am start -n com.example.demo/.MainActivity");
ini
AdbStream stream = connection.open("shell:am force-stop com.example.demo");