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

前言

使用 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.csdn.net/u013553529/article/details/80296870
https://blog.csdn.net/merrylilili/article/details/74641369
adb常用命令

  • 1
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
要在Android Studio中安装ADB,你需要按照以下步骤进行操作: 1. 首先,确保你已经安装了Android Studio和Android SDK。 2. 打开Android Studio,并点击菜单栏中的"File",然后选择"Settings"。 3. 在设置窗口中,找到"System Settings"并点击"Android SDK"。 4. 在"SDK Platforms"选项卡下,选择你想要的Android版本,并点击"Apply"。 5. 在"SDK Tools"选项卡下,找到"Android SDK Platform-Tools"并确保它已经被选中。 6. 点击"Apply"并等待Android Studio下载和安装ADB。 7. 安装完成后,你可以通过在命令行中输入"adb"来验证ADB是否成功安装。如果显示了ADB的相关命令列表,则说明安装成功。 请注意,这些步骤假设你已经正确安装了Android Studio和Android SDK,并且使用了默认的安装路径。如果你的安装路径不同,你需要相应地修改步骤中的路径信息。引用<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Android代码-AndroidWiFiADB](https://download.csdn.net/download/weixin_39840650/11488279)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [Android studio配置ADB](https://blog.csdn.net/duhena0384/article/details/111146597)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [AndroidStudio配置adb环境变量和adb的使用](https://blog.csdn.net/qq_58156721/article/details/125896623)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值