Android 手机通过 adblib 使用 ADB Wi-Fi 控制 Android 9 开发板

21 阅读3分钟

Android 手机通过 adblib 使用 ADB Wi-Fi 控制 Android 9 开发板

项目中需要让 Android 手机通过 Wi-Fi 直接连接 Android 9 开发板的 adbd,不经过电脑,也不在手机端安装 adb。这里使用 tananaev/adblib 建立 ADB TCP 通信。

通信链路:

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 会话。

开发板启动 -> 开启 adbd TCP 5555 -> 手机获取开发板 IP -> Socket 连接 IP:5555 -> CNXN -> ADB 连接建立 -> OPEN shell:xxx -> WRTE 返回结果

实际通信:

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 环境可以直接执行:

Runtime.getRuntime().exec(new String[]{"su", "-c", "setprop service.adb.tcp.port 5555; stop adbd; start adbd"});

如果希望重启开发板后仍保持 TCP 5555,可以写 persist.adb.tcp.port

Runtime.getRuntime().exec(new String[]{"su", "-c", "setprop persist.adb.tcp.port 5555; stop adbd; start adbd"});

Android 9 的 adbd 启动时读取:

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

检查结果:

Process process = Runtime.getRuntime().exec(new String[]{"su", "-c", "getprop service.adb.tcp.port"});

或者直接检查监听端口:

netstat -tunlp | grep 5555

这里的关键不是启动另一个 ADB 程序,而是让开发板原本运行的 adbd 增加 TCP listener:

adbd
 ├─ USB ADB
 └─ TCP :5555

3. 手机引入 adblib

项目加入:

implementation 'com.tananaev:adblib:1.3'

AndroidManifest 添加网络权限:

<uses-permission android:name="android.permission.INTERNET" />

手机端不需要 adb 可执行文件,也不需要 USB Host,adblib 直接通过 Socket 与开发板 adbd 通信。

4. 手机连接开发板

手机取得开发板 IP 后,通过 Socket 连接开发板的 5555 端口,再创建 AdbCryptoAdbConnectionadblib 官方示例也是这套流程:Socket -> AdbCrypto -> AdbConnection -> connect -> open

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,但不等于每次都会出现授权弹窗。AdbCryptoadblib ADB 连接流程的一部分。当目标 adbd 返回 AUTH TOKEN 时,库会使用它签名;如果目标直接返回 CNXN,连接则直接建立。源码中的处理顺序就是 CMD_AUTH 使用 cryptoCMD_CNXN 标记连接成功。

整体流程:

开发板启动
    ↓
adbd 开启 TCP 5555
    ↓
手机连接 开发板IP:5555
    ↓
创建 AdbCrypto
    ↓
创建 AdbConnection
    ↓
connection.connect()
    ↓
CNXN / AUTH
    ↓
ADB 连接建立
    ↓
connection.open("shell:xxx")
    ↓
读取 WRTE 返回的数据

执行一个简单命令:

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();

整个手机端的核心就是:

Socket -> AdbCrypto -> AdbConnection -> connect -> open("shell:xxx") -> read

5. 执行 ADB Shell

连接建立后通过 OPEN shell:xxx 执行命令:

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: 后面的命令即可执行其他操作:

AdbStream stream = connection.open("shell:getprop ro.build.version.release");
AdbStream stream = connection.open("shell:ps -A");
AdbStream stream = connection.open("shell:ip addr");
AdbStream stream = connection.open("shell:pm list packages");
AdbStream stream = connection.open("shell:am start -n com.example.demo/.MainActivity");
AdbStream stream = connection.open("shell:am force-stop com.example.demo");