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

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");
相关推荐
撩得Android一次心动5 小时前
Jetpack Compose 知识点整理2【个人用】
android·笔记·学习·kotlin·compose
是真的小外套6 小时前
2024年软考中级信息安全工程师|选择题考点+案例分析真题解析(含答案)
android·网络
用户2018792831678 小时前
一文讲透 Android Native Heap 监控原理:一间"只进不出"的仓库,和总部派来的侦探
android
YB13759 小时前
Jetpack Compose 动画animate*AsState
android
高晶9 小时前
Android 通讯录应用开发实战:从零搭建 SQLite 数据持久化完整项目
android·设计模式·前端框架
隐擎fox10 小时前
深入 Android 移动端风控与协议层对抗:内核级虚拟环境检测、网络堆栈特征伪装与住宅 IP 协同实战
android·网络·python·网络协议·tcp/ip
换元不配限12 小时前
从 AsyncTask 到现代并发:Android 异步方案的演进与替代实践
android·asynctask·异步编程·方案对比·asynctask替代方案
用户20187928316712 小时前
你的 App 为什么是「电量刺客」?——从一条 WakeLock 追到 Linux 内核的 Android 省电原理全解
android
mmsx12 小时前
Android 安全校验下沉到原生层:防 patch 与构建期自动化
android·安全·自动化