Android -- adb forward实现PC和App的通讯

本文介绍如何使用ADB Forward建立PC与Android设备之间的数据通信,通过USB连接实现两端的交互,包括原理说明、步骤指导及代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

使用 adb forward 打造一个通过USB连接建立的PC端与手机端交互的工具,类似于银行里的服务评价器,由PC端发出指令,在手机端(或终端)接收指令并显示页面。

1、 adb forward 原理概述

adb forward 的功能是建立一个转发
如:adb forward tcp:8000 tcp:9000 的意思是,将PC端的 8000 端口收到的数据,转发给手机中的 9000 端口。
但是光执行这个命令还不能转发数据,还需要完成下面两个步骤才能传数据:

  • (a)在手机端,建立一个端口为9000的 server,并打开 server 到监听状态。
  • (b)在PC端,建立一个 socket client 端,连接到端口为8000的 server 上。

adb forward tcp:8000 tcp:9000 原理示意图:
在这里插入图片描述

  • PC端应用与手机端应用 通信建立过程:
    (1)执行 adb forward tcp:8000 tcp:9000
    (2)启动手机端应用,建立端口为9000的 server,并处于监听状态(LISTENING)
    (3)启动PC端应用,连接端口为8000的 server(adb 创建的)
    之后,就可以传输数据了

  • PC端应用与手机端应用之间传输数据的过程:
    (1)PC端应用将数据发送给端口为8000的 server(adb 创建的)
    (2)adb 将数据转发给手机端 adbd 进程(通过USB传输)
    (3)adbd 进程将数据发送给端口为9000的 server(手机端应用创建的)
    传递是双向的,第(1)和第(3)步是通过socket实现的,所以通过 socket 的读和写就完成了PC端和手机端的数据传递。

2、 实现PC端和手机端的通信

步骤:
1、确保 Android设备通过USB连接到PC上,在PC上运行 adb forward tcp:8000 tcp:9000,将PC端8000端口的数据, 转发到Android端的9000端口上。
2、Android 设备上编写APP,作为网络通信端的 Server 端,建立 ServerSocket,打开端口9000。
3、PC上编写程序,作为网络通信的 Client 端,打开端口8000。
4、PC端与Android端即可建立 Socket 连接进行通信。

实现:

2.1 PC端
package com.example.usbpc;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;

public class MyClass {
    private static boolean mRunning = true;

    public static void main(String[] args) {
        if (!setupAdbForward()) {
            System.out.println("设置端口转发失败");
            return;
        }

        System.out.println("任意字符, 回车键发送");
        startClient();
    }

    private static boolean setupAdbForward() {
        try {
            //将PC端8000端口的数据, 转发到Android端的9000端口上
            Runtime.getRuntime().exec("adb forward tcp:8000 tcp:9000");
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }

        return false;
    }

    private static void startClient() {
        try {
            //设置pc端的端口为8000
            Socket socket = new Socket("127.0.0.1", 8000);

            // InThread 接收手机端传来的数据
            new Thread(new InThread(socket)).start();

            // OutThread 用于从PC端输入并传给手机端
            Scanner scanner = new Scanner(System.in);
            while (true) {
                String msg = scanner.next();
                sendToServer(socket, msg);
            }
//            new Thread(new OutThread(socket, msg)).start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 接收手机端传来的数据
     */
    static class InThread implements Runnable {
        Socket socket = null;

        public InThread(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            while (mRunning) {
                if (socket.isClosed()) {
                    mRunning = false;
                    break;
                }

                try {
                    DataInputStream dis = new DataInputStream(socket.getInputStream());
                    byte[] buffer = new byte[256];
                    int len = dis.read(buffer);
                    if (len > 0) {
                        System.out.println("\n接收到:" + new String(buffer, 0, len, "UTF-8"));
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }
    }

    /**
     * 从PC端输入并传给手机端
     *
     * @param msg 信息
     * @throws IOException 异常
     */
    public static void sendToServer(Socket socket, String msg) throws IOException {
        DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
        dos.writeUTF(msg);
        dos.flush();

        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}
2.2 Android端

1、声明网络权限
一定要声明 android.permission.INTERNET 权限,否则会抛出异常 java.net.SocketException: Permission denied

在AndroidManifest.xml中声明权限:

<uses-permission android:name="android.permission.INTERNET" />
2.2.1 接收PC端传来的数据

1、启动 Server
在 Application 中启动 Server

package com.example.rs.risenevaluatorpad;

import android.app.Application;
import android.content.Context;

import com.example.rs.risenevaluatorpad.server.ServerThread;

/**
 * Created by sgll on 2018/12/10.
 */
public class MyApplication extends Application {
    public static Context mContext;
    public static ServerThread mServer;

    @Override
    public void onCreate() {
        super.onCreate();
        mContext = getApplicationContext();
        mServer = new ServerThread();
        mServer.start();
//        new ServerThread().start();
    }
}

2、接受PC端传来的数据
ServerThread (单线程)

package com.example.rs.risenevaluatorpad.server;

import android.text.TextUtils;
import android.util.Log;

import com.example.rs.risenevaluatorpad.model.DataHolder;
import com.example.rs.risenevaluatorpad.util.ActivityCollector;
import com.example.rs.risenevaluatorpad.util.Constants;
import com.example.rs.risenevaluatorpad.util.OperationUtil;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * Created by sgll on 2018/12/10.
 */
public class ServerThread extends Thread {
    private ServerSocket serverSocket;
    private Socket socket;
    boolean isLoop = true;

    public void setIsLoop(boolean isLoop) {
        this.isLoop = isLoop;
    }

    public Socket getSocket() {
        return socket;
    }

    @Override
    public void run() {
        super.run();
        try {
            //设置Android端口为9000
            serverSocket = new ServerSocket(9000);

            while (isLoop) {
                try {
                    //从连接队列中取出一个连接,如果没有则等待
                    socket = serverSocket.accept();
                    Log.v(Constants.SERVER_TAG, "从连接队列中取出一个连接");
//                    new ProcessClientRequestThread(socket).start();

                    while (true) {

                        /**
                         * isClosed()、isConnected()、isInputStreamShutdown()、isOutputStreamShutdown()
                         * 这些方法无法判断服务端是否断开,只能判断本地的状态
                         */
                        // 发送心跳包,单线程中使用,判断socket是否断开
                        socket.sendUrgentData(0xFF);
                        DataInputStream inputStream = new DataInputStream(socket.getInputStream());
//                        InputStreamReader reader = new InputStreamReader(inputStream);
//                        InputStream inputStream = socket.getInputStream();
                        byte[] buffer = new byte[1024];
//                        char[] buffer = new char[1024];
                        int bt;
                        String text = "";
//                        StringBuffer stringBuffer = new StringBuffer();

                        int index = 0;

                        Log.v(Constants.SERVER_TAG, "inputStream.read 之前");
                        while ((bt = inputStream.read(buffer)) != -1) {
                            Log.v(Constants.SERVER_TAG, "bt:" + bt + ",," + (index++));
//                            stringBuffer.append(buffer, 0, bt);
                            text += new String(buffer, 0, bt).trim();
//                            String aaa = new String(buffer, 0, bt).trim();
//                            text = text + aaa;
                            Log.e(Constants.SERVER_TAG, "------------");
                            Log.e(Constants.SERVER_TAG, "text: " + text.trim());

                            /**
                             * 当接收到大数据,inputStream读取完后,由于inputStream通道是一直在的,
                             * inputStream.read(buffer)处在堵塞状态,所以并不会跳出当前循环,
                             * 导致无法判断数据是否传输完,所以PC端传输数据时需要在数据中
                             * 加入报尾,Android设备上接收到报尾,代表此次数据传输完毕,
                             * 也可以加上报头,来表示数据的开始
                             *
                             */
                            if (text.endsWith("-vvv")) {
                                text = text.substring(0, text.lastIndexOf("-vvv"));
//                                OperationUtil.getOperation(text);
//                                DataHolder.getInstance().setData("msg", text);
//                                ActivityCollector.startActivity(text);
                                Log.v(Constants.SERVER_TAG, "内容长度:" + text.length());
                                Log.v(Constants.SERVER_TAG, "读取结束,内容为:" + text);
                                Log.v(Constants.SERVER_TAG, "inputStream.read 之后");
                                break;
                            }
                        }
                    }

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

如何判断socket是否连接,异常断开拔出网线

2.2.2 发送消息到PC端

1、BaseActivity

package com.example.rs.risenevaluatorpad.base;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;

import com.example.rs.risenevaluatorpad.MyApplication;
import com.example.rs.risenevaluatorpad.R;
import com.example.rs.risenevaluatorpad.util.ActivityCollector;
import com.example.rs.risenevaluatorpad.util.ToastUtils;

import java.net.Socket;

import butterknife.ButterKnife;
import butterknife.Unbinder;

/**
 * Created by sgll on 2018/12/11.
 */
public abstract class BaseActivity extends AppCompatActivity {
    //反注销
    private Unbinder unbinder;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getLayoutResId());
        unbinder = ButterKnife.bind(this);
        ActivityCollector.addActivity(this);
        setServer();
        onCreateView(savedInstanceState);
        processLogic();
        setListener();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbinder.unbind();
        ActivityCollector.removeActivity(this);
    }

    /**
     * 设置服务
     */
    private void setServer(){
        //TODO:重启服务
        if (MyApplication.mServer == null) {
            ToastUtils.toast(this, getString(R.string.error_server_socket));
            return;
        }
    }

    public Socket getSocket(){
        Socket mSocket = null;
        if (MyApplication.mServer.getSocket() != null) {
            mSocket = MyApplication.mServer.getSocket();
        }
        return mSocket;
    }

    /**
     * 加载页面 Layout
     * @return R.layout.xxx
     */
    protected abstract int getLayoutResId();

    /**
     * 初始化控件
     */
    protected abstract void onCreateView(Bundle savedInstanceState);

    /**
     * 设置各种事件的监听器
     */
    protected abstract void setListener();

    /**
     * 业务逻辑处理,主要与后端交互(网络请求)
     */
    protected abstract void processLogic();
}

2、Main1Activity

package com.example.rs.risenevaluatorpad.activity;

import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import com.example.rs.risenevaluatorpad.R;
import com.example.rs.risenevaluatorpad.base.BaseActivity;
import com.example.rs.risenevaluatorpad.server.SendThread;
import com.example.rs.risenevaluatorpad.util.ToastUtils;

import java.net.Socket;

import butterknife.BindView;
import butterknife.OnClick;

/**
 * Created by sgll on 2019/1/14.
 */
public class Main1Activity extends BaseActivity {
    @BindView(R.id.bt_submit)
    Button btSubmit;
    @BindView(R.id.et_content)
    EditText etContent;

    @Override
    protected int getLayoutResId() {
        return R.layout.activity_main;
    }

    @Override
    protected void onCreateView(Bundle savedInstanceState) {
    }


    @Override
    protected void setListener() {

    }

    @Override
    protected void processLogic() {

    }

    @OnClick(R.id.bt_submit)
    void onSubmit(View view) {
        Socket mSocket = getSocket();
        if (mSocket == null) {
            ToastUtils.toast(this, getString(R.string.error_socket));
            return;
        }

        String content = etContent.getText().toString().trim();
        if (TextUtils.isEmpty(content)) {
            ToastUtils.toast(this, "内容不能为空");
            return;
        }
        new SendThread(content, mSocket).start();
    }

}

3、SendThread 发送消息到PC端

package com.example.rs.risenevaluatorpad.server;

import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

/**
 * Created by sgll on 2018/12/11.
 * 发送消息到PC端
 */
public class SendThread extends Thread {
    private String data;
    private Socket socket;

    public SendThread(String data, Socket socket){
        this.data = data;
        this.socket = socket;
    }

    @Override
    public void run() {
        super.run();
        sendDatas();
    }

    private void sendDatas(){
        try {
            DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream());
            outputStream.write(data.getBytes("UTF-8"));
            outputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行PC端和Android端,即可进行通信

参考资料
https://blog.youkuaiyun.com/u013553529/article/details/80296870
https://blog.youkuaiyun.com/merrylilili/article/details/74641369
adb常用命令

Android Debug Bridge version 1.0.39 Revision 3db08f2c6889-android Installed as D:\adb\adb.exe global options: -a listen on all network interfaces, not just localhost -d use USB device (error if multiple devices connected) -e use TCP/IP device (error if multiple TCP/IP devices available) -s SERIAL use device with given serial number (overrides $ANDROID_SERIAL) -p PRODUCT name or path ('angler'/'out/target/product/angler'); default $ANDROID_PRODUCT_OUT -H name of adb server host [default=localhost] -P port of adb server [default=5037] -L SOCKET listen on given socket for adb server [default=tcp:localhost:5037] general commands: devices [-l] list connected devices (-l for long output) help show this help message version show version num networking: connect HOST[:PORT] connect to a device via TCP/IP [default port=5555] disconnect [HOST[:PORT]] disconnect from given TCP/IP device [default port=5555], or all forward --list list all forward socket connections forward [--no-rebind] LOCAL REMOTE forward socket connection using: tcp:<port> (<local> may be "tcp:0" to pick any open port) localabstract:<unix domain socket name> localreserved:<unix domain socket name> localfilesystem:<unix domain socket name> dev:<character device name> jdwp:<process pid> (remote only) forward --remove LOCAL remove specific forward socket connection forward --remove-all remove all forward socket connections ppp TTY [PARAMETER...] run PPP over USB reverse --list list all reverse socket connections from device reverse [--no-rebind] REMOTE LOCAL reverse socket connection using: tcp:<port> (<remote> may be "tcp:0" to pick any open port) localabstract:<unix domain socket name> localreserved:<unix domain socket name> localfilesystem:<unix domain socket name> r
最新发布
07-04
<think>我们正在处理用户关于ADB使用手册及常用命令详解的请求。根据提供的引用资料,ADBAndroid DebugBridge的缩写,它是一个多功能命令行工具,用于与Android设备或模拟器通信。主要功能包括设备通信、应用调试、系统控制、日志捕获、文件操作自动化测试等。用户需求明确:希望了解ADB的使用手册常用命令详解。因此,我们将按照ADB的核心功能分类,详细解释常用命令及其用法。根据引用资料,特别是引用[4]中提到的核心能力,我们可以将命令分为以下几类:1.设备连接管理2.应用管理(安装、卸载、调试)3.文件操作4.系统控制5.日志捕获6. Shell命令此外,引用[5]提到了termux-adb,这是一个可以在Termux中运行adb的项目,但用户主要需求是ADB命令本身,因此我们重点放在标准ADB命令上。注意:回答中需要引用时,在引用内容末尾添加引用标识,如[^1]。同时,按照系统指令,行内数学表达式用$...$,独立公式用$$...$$,但本问题可能较少涉及数学公式,因此重点放在命令格式说明上。我们将按照以下结构组织回答:1.介绍ADB及其工作原理(简要)2.分类详解常用命令(核心部分)3.提供一些使用示例最后,生成相关问题。开始回答:</think>### Android Debug Bridge (ADB) 使用手册及常用命令详解 #### 一、ADB 基础概念 ADBAndroid Debug Bridge)是 Android SDK 提供的命令行工具,充当 PCAndroid 设备/模拟器的通信桥梁,支持 USB/Wi-Fi 连接[^4]。核心功能包括: - 🔌 **设备通信**:管理设备连接状态 - 📲 **应用管理**:安装/卸载 APK、调试应用 - ⚙️ **系统控制**:重启设备、进入 Recovery 模式 - 📝 **日志操作**:捕获实时系统日志(Logcat) - 📁 **文件传输**:设备与 PC 间文件推送/拉取 #### 二、核心命令详解 ##### 1. 设备连接管理 ```bash # 查看已连接设备 adb devices # 输出示例:List of devices attached # emulator-5554 device # 通过 Wi-Fi 连接设备(需先 USB 配对) adb tcpip 5555 # 设置设备监听端口 adb connect 192.168.1.100:5555 # PC 连接设备 IP ``` ##### 2. 应用管理 ```bash # 安装 APK adb install app-debug.apk # 卸载应用(需包名) adb uninstall com.example.app # 启动 Activity adb shell am start -n com.example.app/.MainActivity ``` ##### 3. 文件操作 ```bash # PC → 设备 adb push local.txt /sdcard/ # 设备 → PC adb pull /sdcard/remote.txt ./ # 查看设备文件 adb shell ls /sdcard/ ``` ##### 4. 系统控制 ```bash # 重启设备 adb reboot # 进入 Recovery 模式 adb reboot recovery # 获取设备信息 adb shell getprop ro.product.model ``` ##### 5. 日志捕获 ```bash # 实时查看日志 adb logcat # 按标签过滤日志 adb logcat -s "MyAppTag" # 清除日志缓冲区 adb logcat -c ``` ##### 6. Shell 高级操作 ```bash # 进入设备 Shell 环境 adb shell # 执行单条 Shell 命令 adb shell dumpsys battery # 查看电池状态 # 屏幕截图(需设备 root) adb shell screencap /sdcard/screen.png ``` #### 三、实用技巧 1. **无线调试**: 通过 `adb pair` 实现免 USB 调试(Android 11+),需先在开发者选项中启用无线调试[^4]。 2. **多设备管理**: 使用 `-s <设备ID>` 指定操作设备: ```bash adb -s emulator-5554 install app.apk ``` 3. **端口转发**: 将设备端口映射到 PC: ```bash adb forward tcp:8080 tcp:8080 # 访问 PC 的 8080 即访问设备 8080 ``` #### 四、环境配置 1. 下载 ADB 工具包:[Android SDK Platform-Tools](https://developer.android.com/studio/releases/platform-tools) 或 [ADB Kits](https://adb.clockworkmod.com/) 2. 添加环境变量: ```bash # Linux/macOS export PATH=$PATH:/path/to/platform-tools ``` 3. 设备端启用: 设置 → 开发者选项 → 开启 **USB 调试**(首次需授权 PC) > 注:部分命令需要设备 **root 权限**(如系统文件访问),非 root 设备可能受限[^2]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值