You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Android Java中实现无需桌面ADB服务器的ADB环回功能?

实现无需桌面ADB服务器的本地ADB操作(Android Java)

一、核心原理

ADB无线调试支持设备通过本地环回地址与自身的ADB守护进程通信。要实现无桌面服务器的操作,核心逻辑是:

  1. 生成符合ADB规范的自签名密钥对,完成设备与自身的配对
  2. 应用内部建立Socket连接到本地ADB服务(默认端口5555)
  3. 按照ADB协议封装命令发送与响应解析逻辑

二、关键步骤与代码实现

1. 生成ADB配对密钥对

ADB配对需要adbkey(私钥)和adbkey.pub(公钥),可在应用内部生成:

import java.io.File;
import java.io.FileOutputStream;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.RSAKeyGenParameterSpec;

public class AdbKeyGenerator {
    private static final String ALGORITHM = "RSA";
    private static final int KEY_SIZE = 2048;

    public static void generateKeys(File keyDir) throws Exception {
        keyDir.mkdirs();
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM);
        keyGen.initialize(new RSAKeyGenParameterSpec(KEY_SIZE, RSAKeyGenParameterSpec.F4));
        KeyPair keyPair = keyGen.generateKeyPair();

        // 保存私钥到adbkey
        PrivateKey privateKey = keyPair.getPrivate();
        try (FileOutputStream fos = new FileOutputStream(new File(keyDir, "adbkey"))) {
            fos.write(privateKey.getEncoded());
        }

        // 保存公钥到adbkey.pub(ADB要求格式:ssh-rsa <base64编码> user@host)
        PublicKey publicKey = keyPair.getPublic();
        String pubContent = "ssh-rsa " + android.util.Base64.encodeToString(publicKey.getEncoded(), android.util.Base64.DEFAULT) + " android@local";
        try (FileOutputStream fos = new FileOutputStream(new File(keyDir, "adbkey.pub"))) {
            fos.write(pubContent.getBytes());
        }
    }
}

2. 完成本地ADB配对

需要将生成的密钥对放到ADB认可的系统目录(通常为/data/misc/adb),这一步需要ROOT权限;若无ROOT,可引导用户手动开启无线调试并完成本地配对:

public class AdbPairingHelper {
    public static boolean pairWithLocalAdb(File keyDir) {
        String copyCmd = String.format(
            "cp %s/adbkey* /data/misc/adb/ && chmod 600 /data/misc/adb/adbkey* && chown adb:adb /data/misc/adb/adbkey*",
            keyDir.getAbsolutePath()
        );
        try {
            Process suProcess = Runtime.getRuntime().exec("su");
            try (OutputStream os = suProcess.getOutputStream()) {
                os.write((copyCmd + "\n").getBytes());
                os.flush();
            }
            return suProcess.waitFor() == 0;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}

3. 连接本地ADB服务并执行命令

ADB基于自定义TCP协议,通过Socket连接到localhost:5555后,按照协议格式发送命令:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;

public class LocalAdbClient {
    private Socket adbSocket;
    private OutputStream outputStream;
    private BufferedReader inputReader;

    public boolean connect() throws IOException {
        adbSocket = new Socket("localhost", 5555);
        outputStream = adbSocket.getOutputStream();
        inputReader = new BufferedReader(new InputStreamReader(adbSocket.getInputStream()));
        
        // 发送ADB握手标识(固定为"ADB0")
        outputStream.write(new byte[]{0x41, 0x44, 0x42, 0x30});
        outputStream.flush();
        
        // 验证服务器响应
        byte[] respBuffer = new byte[4];
        inputReader.read(respBuffer);
        return new String(respBuffer).equals("OKAY");
    }

    public String executeCommand(String command) throws IOException {
        if (!adbSocket.isConnected()) {
            throw new IOException("ADB socket not connected");
        }
        
        // ADB命令格式:4字节十六进制长度 + 命令内容
        String cmdWithNewLine = command + "\n";
        String lengthHex = String.format("%04x", cmdWithNewLine.length());
        outputStream.write(lengthHex.getBytes());
        outputStream.write(cmdWithNewLine.getBytes());
        outputStream.flush();

        // 解析响应
        StringBuilder response = new StringBuilder();
        String line;
        while ((line = inputReader.readLine()) != null) {
            if (line.startsWith("OKAY")) {
                continue;
            }
            if (line.startsWith("FAIL")) {
                throw new IOException("Command failed: " + line.substring(4));
            }
            response.append(line).append("\n");
        }
        return response.toString().trim();
    }

    public void disconnect() throws IOException {
        if (adbSocket != null) {
            adbSocket.close();
        }
    }
}

4. 整合使用示例

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        File keyDir = new File(getFilesDir(), "adb_keys");
        try {
            // 生成密钥对
            AdbKeyGenerator.generateKeys(keyDir);
            // 完成配对(需ROOT)
            if (AdbPairingHelper.pairWithLocalAdb(keyDir)) {
                LocalAdbClient adbClient = new LocalAdbClient();
                if (adbClient.connect()) {
                    // 执行示例命令:获取窗口信息
                    String result = adbClient.executeCommand("shell dumpsys window");
                    Log.d("ADB_OUTPUT", result);
                    adbClient.disconnect();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

三、注意事项

  • 权限要求:复制密钥到系统目录需要ROOT权限;无ROOT时,需引导用户手动开启「无线调试」并完成本地配对(路径:设置 -> 开发者选项 -> 无线调试 -> 配对设备)。
  • 协议兼容性:不同Android版本的ADB守护进程行为可能有差异,需多设备测试;复杂操作(如文件传输)需扩展协议解析逻辑。
  • 权限申请:应用需声明INTERNET权限(即使是本地连接)。
  • 安全性:生成的密钥对需妥善存储,避免泄露。

内容的提问来源于stack exchange,提问作者Adam Taylor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 13:10:34