天天看点

无网络PC通过USB与多个Android设备通讯

通过ADB将USB模拟为网卡,创建Socket进行通讯

    • 前言
        • 应用场景
        • 实现效果
        • 实现思路
    • Android服务端实现
        • MainActivity.java
        • TcpConnectRunnable.java
    • PC客户端实现
        • FrmClient.cs
        • SocketClient.cs
        • DriverDetector.cs
        • ADB操作
    • 运行效果
        • Android服务端
        • PC客户端
    • 参考资料

前言

应用场景

适用于工作环境无网络,只能通过USB将多台Android端数据上传到一台PC端的情况。

实现效果

  1. 启动PC端工作站
  2. 自动检测通过USB连接的Android端设备
  3. 自动启动Android端数据上传app
  4. 通过Socket向Android端发送命令
  5. Android端通过Socket上传数据
  6. 上传完成后自动关闭app

PC端工作站可长时间开启,外业人员工作回来后,将设备插到电脑上即可,上传操作将会自动执行,可同时插入多台Android设备。

实现思路

  • 通过ADB将USB模拟为网卡
  • Android端作为服务端,创建ServerSocket,监听客户端命令
  • PC端作为客户端,请求建立Socket连接,向Android端发送命令

Android服务端实现

MainActivity.java

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private TextView tvText;

    private TcpConnectRunnable tcpConnectRunnable = new TcpConnectRunnable(new TcpConnectRunnable.Callback() {
        @Override
        public void call(final String msg) {
            // 回调信息显示到消息输出窗口
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    tvText.append(msg + "\r\n");
                    if (msg.contains("quit")) {
                        finish();
                    }
                }
            });
        }
    });

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tvText = findViewById(R.id.tv_message);

        // 清空消息输出窗口
        findViewById(R.id.btn_clear).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                tvText.setText("");
            }
        });

        // 开启Socket服务器
        new Thread(tcpConnectRunnable).start();
    }

    @Override
    protected void onDestroy() {
        tcpConnectRunnable.stop();
        super.onDestroy();
    }
}
           

TcpConnectRunnable.java

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

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * Socket服务器,持续监听客户端连接
 */
public class TcpConnectRunnable implements Runnable {

    private static final String TAG = TcpConnectRunnable.class.getSimpleName();
    private final int SERVER_PORT = 10086;
    private ServerSocket serverSocket;
    private Socket client;
    private Callback callback;

    private boolean isRun = false;

    public TcpConnectRunnable(Callback callback) {
        this.callback = callback;
    }

    /**
     * 停止
     */
    public void stop() {
        isRun = false;
    }

    @Override
    public void run() {
        isRun = true;

        try {
            String ip = InetAddress.getLocalHost().getHostAddress();
            serverSocket = new ServerSocket(SERVER_PORT);
            callback.call("建立服务器:[" + ip + ":" + SERVER_PORT + "]");
        }catch (IOException e) {
            callback.call("建立服务器异常:" + e.getMessage());
        }

        while (isRun) {
            BufferedOutputStream out = null;
            BufferedReader in = null;
            try {
                client = serverSocket.accept();
                callback.call("建立连接:" + client.getInetAddress().toString() + ":" + client.getPort());
                out = new BufferedOutputStream(client.getOutputStream());
                in = new BufferedReader(new InputStreamReader(client.getInputStream()));

                if (isRun == false) {
                    break;
                }
                String request = receive(in);
                if (TextUtils.isEmpty(request))
                {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    continue;
                }
                callback.call("client: " + request);

                if ("quit".equals(request)) {
                    callback.call("origin request: " + request);
                    break;
                }

                if (isRun == false) {
                    break;
                }
                send(out, request);
            } catch (IOException e) {
                Log.e(TAG, "run: ", e);
                callback.call(e.getMessage());
            } finally {
                close(out);
                close(in);
                close(client);
            }
        }
    }

    /**
     * 向客户端发送数据
     * @param out
     * @param msg
     * @throws IOException
     */
    private void send(OutputStream out, String msg) throws IOException {
        msg += "\n";
        out.write(msg.getBytes("utf-8"));
    }

    /**
     * 接收客户端的请求,并返回应答
     * @param in
     * @return
     * @throws IOException
     */
    private String receive(BufferedReader in) throws IOException {
        String r = in.readLine();
        if (TextUtils.isEmpty(r)) {
            return "";
        }
        callback.call("origin request: " + r);
        if (r.contains("upload")) {
            r = r + " finished.";
        }
        return r;
    }

    private void close(OutputStream out) {
        if (out != null) {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void close(BufferedReader in) {
        if (in != null) {
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void close(Socket socket) {
        if (socket != null) {
            try {
                socket.close();
            } catch (IOException e) {
                Log.e(TAG, "run: ", e);
            }
        }
    }

    /**
     * 消息回调,用于更新UI
     */
    public interface Callback {
        /**
         * 回调
         * @param msg
         */
        void call(String msg);
    }
}
           

PC客户端实现

FrmClient.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Windows.Forms;

namespace PhoneAidClient
{
    public partial class FrmClient : Form
    {
        /// <summary>
        /// 设备插拔检测的最小间隔(毫秒)
        /// 用于避免插入设备触发多次事件的情况
        /// </summary>
        private static long DEVICE_DETECT_MIN_INTERVAL = 1000;

        /// <summary>
        /// 用于管理ADB命令等
        /// </summary>
        private CmdHelp cmdHelp = new CmdHelp();

        /// <summary>
        /// adb接收到数据的事件,一般只用在adb的开启关闭上,其他时候数据都由其他地方处理
        /// </summary>
        private CmdHelp.DelegateReceiveThreadData m_receiveDelegate = null;

        /// <summary>
        /// 系统信息的处理类
        /// </summary>
        private DriveDetector detector = new DriveDetector();

        /// <summary>
        /// 插入的设备列表
        /// </summary>
        private List<string> devices = new List<string>();

        /// <summary>
        /// 上次检测到设备插拔的时间(毫秒)
        /// </summary>
        private long lastDeviceDetectTime = -1;

        /// <summary>
        /// 用于锁住ADB的变量,否则一边作业一遍连接usb等操作,可能导致ADB崩溃
        /// </summary>
        private object m_lock = string.Empty;

        /// <summary>
        /// 本地端口列表
        /// </summary>
        private List<int> localPorts = new List<int>();

        /// <summary>
        /// 随机端口
        /// </summary>
        private Random randomPort = new Random();

        public FrmClient()
        {
            InitializeComponent();
        }

        private void FrmClient_Load(object sender, EventArgs e)
        {
            //防止adb未完全关闭
            Process[] adbList = Process.GetProcessesByName("adb");
            foreach (Process adb in adbList)
            {
                adb.Kill();
                adb.Close();
                adb.Dispose();
            }

            treeDevice.Nodes.Clear();
            treeDevice.Nodes.Add("已连接设备");
            treeDevice.ExpandAll();
            
            //注册事件
            CmdHelp.EventReceiveData += new CmdHelp.DelegateReceiveData(CmdHelp_EventReceiveData);
            m_receiveDelegate = new CmdHelp.DelegateReceiveThreadData(CmdHelp_EventReceiveThreadData);
            CmdHelp.EventReceiveThreadData += m_receiveDelegate;
            CmdHelp.EventReceiveThreadErrorData += new CmdHelp.DelegateReceiveThreadErrorData(CmdHelp_EventReceiveThreadErrorData);
            
            //启动ADB服务
            Thread openADBThread = new Thread(new ThreadStart(() =>
            {
                //先关闭,再开启,防止上一次的意外退出
                cmdHelp.SendAdbCmd(CmdAdbInfo.adb_start_server);
            }));
            openADBThread.Start();

            //搜索软件启动前插入的设备
            StartSearchDevice();

            //对系统的设备拔入拔出进行监听
            detector.DeviceArrived += new EventHandler<DriveDetector.DriveDectctorEventArgs>((obj, ee) =>
            {
                long now = (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000;
                if (lastDeviceDetectTime > 0)
                {
                    if (now - lastDeviceDetectTime < DEVICE_DETECT_MIN_INTERVAL)
                    {
                        return;
                    }
                }
                lastDeviceDetectTime = now;

                AppendText("设备插入或拔出");
                StartSearchDevice();
            });

            detector.DeviceRemoved += new EventHandler<DriveDetector.DriveDectctorEventArgs>((obj, ee) =>
            {
                long now = (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000;
                if (lastDeviceDetectTime > 0)
                {
                    if (now - lastDeviceDetectTime < DEVICE_DETECT_MIN_INTERVAL)
                    {
                        return;
                    }
                }
                lastDeviceDetectTime = now;

                AppendText("设备插入或拔出");
                StartSearchDevice();
            });
        }
               
        private void FrmClient_FormClosed(object sender, FormClosedEventArgs e)
        {
            // 退出时关闭adb
            Process[] adbList = Process.GetProcessesByName("adb");
            foreach (Process adb in adbList)
            {
                adb.Kill();
                adb.Close();
                adb.Dispose();
            }
        }
        
        /// <summary>
        /// 接收到数据(阻塞线程)
        /// </summary>
        /// <param name="data"></param>
        private void CmdHelp_EventReceiveData(string data)
        {
            AppendTextInvoke(data);
        }

        /// <summary>
        /// 接收错误数据
        /// </summary>
        /// <param name="data"></param>
        private void CmdHelp_EventReceiveThreadErrorData(string data)
        {
            AppendTextInvoke(data);
        }

        /// <summary>
        /// 从线程接收到数据(不阻塞线程)
        /// </summary>
        /// <param name="data"></param>
        private void CmdHelp_EventReceiveThreadData(string data)
        {
            AppendTextInvoke(data);
        }
        
        /// <summary>
        /// 接收系统的消息(可以捕获设备连接断开的消息)
        /// </summary>
        /// <param name="m"></param>
        protected override void WndProc(ref Message m)
        {
            bool habled = false;
            detector.WndProc(m.HWnd, m.Msg, m.WParam, m.LParam, ref habled);
            base.WndProc(ref m);
        }

        /// <summary>
        /// 开线程后台进行检索
        /// </summary>
        private void StartSearchDevice()
        {
            Thread searchThread = new Thread(new ThreadStart(() =>
            {
                //如果马上搜索设备,可能搜索不到
                Thread.Sleep(1000);
                SearchDevice();
            }));
            searchThread.Start();
        }

        /// <summary>
        /// 通过ADB查找设备
        /// 一般只在开启ADB成功之后,以及电脑检测到设备接入的时候运行
        /// </summary>
        private void SearchDevice()
        {            
            lock (m_lock)
            {
                string[] deviceInfoList = cmdHelp.GetDevices(CmdAdbInfo.adb_devices);
                // 一次插拔可能会触发多次事件,只在第一次时查找设备,
                // 但第一次时查找可能查不到,因此等待一下后再次查询
                if (deviceInfoList == null || deviceInfoList.Length < 1)
                {
                    Thread.Sleep(500);
                    deviceInfoList = cmdHelp.GetDevices(CmdAdbInfo.adb_devices);
                }
                this.Invoke(new Action<string[]>(RefreshTree), new object[1] { deviceInfoList });
            }
        }

        /// <summary>
        /// 刷新设备列表
        /// </summary>
        /// <param name="deviceInfoList"></param>
        private void RefreshTree(string[] deviceInfoList)
        {
            // 是否有新插入的设备
            for (int i = 0; i < deviceInfoList.Length; i++)
            {
                string deviceNo = deviceInfoList[i];
                if (devices.Contains(deviceNo) == false)
                {
                    treeDevice.Nodes[0].Nodes.Add(cmdHelp.GetDeviceIMEI(deviceNo), deviceNo);
                    devices.Add(deviceNo);

                    // 新插入设备,开始工作
                    int port = randomPort.Next(10000, 20000);
                    while (localPorts.Contains(port))
                    {
                        port = randomPort.Next(10000, 20000);
                    }
                    localPorts.Add(port);
                    StartWork(deviceNo, port.ToString());
                }
            }

            // 是否有拔出的设备
            for (int i = devices.Count-1; i >= 0; i--)
            {
                if (deviceInfoList.Contains(devices[i]) == false)
                {
                    treeDevice.Nodes[0].Nodes.RemoveAt(i);

                    // 移除设备,停止工作
                    StopWork(devices[i]);

                    devices.RemoveAt(i);                    
                }
            }

            treeDevice.ExpandAll();
        }

        /// <summary>
        /// 开始工作
        /// 1.检查软件是否有安装,若有安装,则获取版本
        /// 2.检查是否为新版本,不是的话,安装新版本
        /// 3.启动软件
        /// 4.创建socket,请求数据
        /// 5.关闭软件
        /// </summary>
        /// <param name="deviceNo"></param>
        private void StartWork(string deviceNo, string localPort)
        {
            // 启动软件
            cmdHelp.RunApp(deviceNo, "com.zhd.phoneaid/com.zhd.phoneaid.MainActivity");

            // 端口映射
            cmdHelp.Forward(deviceNo, localPort, "10086");

            // 创建socket,请求数据
            BackgroundWorker worker = new BackgroundWorker();
            worker.WorkerReportsProgress = false;
            worker.DoWork += Worker_DoWork;
            worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
            worker.RunWorkerAsync(localPort);
        }

        private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            // 退出软件
            //cmdHelp.ExitApp(deviceNo, "com.zhd.phoneaid");
        }

        private void Worker_DoWork(object sender, DoWorkEventArgs e)
        {
            SocketClient socketClient = new SocketClient(e.Argument.ToString());
            string result = socketClient.Request("upload file1");
            AppendTextInvoke(result);
            Thread.Sleep(10000);
            result = socketClient.Request("upload file2");
            AppendTextInvoke(result);
            Thread.Sleep(100);
            // 通知Android服务端退出
            socketClient.Request("quit");

            localPorts.Remove(int.Parse(socketClient.GetPort()));
        }

        private void StopWork(string deviceNo)
        {
            AppendText(deviceNo + " : 已拔出");
        }

        /// <summary>
        /// 输出消息
        /// </summary>
        /// <param name="data"></param>
        private void AppendText(string data)
        {
            if (string.IsNullOrEmpty(data))
            {
                return;
            }
            if (data.EndsWith("\r\n") == false)
            {
                data = data + "\r\n";
            }
            txtMessage.AppendText(data);
        }

        /// <summary>
        /// 输出消息(子线程调用)
        /// </summary>
        /// <param name="data"></param>
        private void AppendTextInvoke(string data)
        {
            this.Invoke(new Action<string>(AppendText), new object[1] { data });
        }
    }
}
           

SocketClient.cs

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace PhoneAidClient
{
    public class SocketClient
    {
        /// <summary>
        /// 客户端端口
        /// </summary>
        private string localPort;
        public string GetPort()
        {
            return localPort;
        }

        public SocketClient(string local_port)
        {
            this.localPort = local_port;
        }

        /// <summary>
        /// 发送请求
        /// </summary>
        /// <param name="msg"></param>
        /// <returns></returns>
        public string Request(string msg)
        {
            Socket client = null;
            try
            {
                // 创建客户端
                client = Create();
                // 连接到服务端
                Connect(client, localPort);
                if (client.Connected)
                {
                    // 发送请求
                    Send(client, msg);
                    // 接收应答
                    return Receive(client);
                }
                else
                {
                    return "连接失败";
                }
            }
            catch (Exception e)
            {
                return $"Error:{e.Message}";
            }
            finally
            {
                // 断开连接
                if (client.Connected)
                {
                    client.Shutdown(SocketShutdown.Both);
                    client?.Close();
                }
                client = null;
            }
        }
        
        /// <summary>
        /// 创建Socket客户端
        /// </summary>
        /// <returns></returns>
        private static Socket Create()
        {
            return new Socket(AddressFamily.InterNetwork,
                SocketType.Stream,
                ProtocolType.Tcp);
        }

        /// <summary>
        /// 连接到服务端
        /// </summary>
        /// <param name="socket"></param>
        /// <param name="port"></param>
        private static void Connect(Socket socket, string port)
        {
            IPAddress iPAddress = IPAddress.Parse("127.0.0.1");
            IPEndPoint iPEndPoint = new IPEndPoint(iPAddress, int.Parse(port));
            socket.Connect(iPEndPoint);
        }

        /// <summary>
        /// 发送请求
        /// </summary>
        /// <param name="socket"></param>
        /// <param name="msg"></param>
        private static void Send(Socket socket, string msg)
        {
            msg += "\n";
            byte[] data = Encoding.UTF8.GetBytes(msg);
            socket.Send(data);
        }

        /// <summary>
        /// 接收应答
        /// </summary>
        /// <param name="socket"></param>
        /// <returns></returns>
        private static string Receive(Socket socket)
        {
            string str = "";
            byte[] data = new byte[1024];
            int len = 0;
            int i = 0;
            while ((i = socket.Receive(data, 1024, SocketFlags.None)) != 0)
            {
                len += i;
                string piece = Encoding.UTF8.GetString(data, 0, i);
                str += piece;
            }
            return str;
        }
    }
}
           

DriverDetector.cs

using System;
using System.Runtime.InteropServices;

namespace PhoneAidClient
{
    /// <summary>
    /// 监听设备的插入和拔出
    /// </summary>
    public class DriveDetector
    {
        /// <summary>
        /// 设备插入事件
        /// </summary>
        public event EventHandler<DriveDectctorEventArgs> DeviceArrived = null;

        /// <summary>
        /// 设备拔出事件
        /// </summary>
        public event EventHandler<DriveDectctorEventArgs> DeviceRemoved = null;

        /// <summary>
        /// 消息处理(HwndSourceHook委托的签名)
        /// </summary>
        /// <param name="hwnd"></param>
        /// <param name="msg"></param>
        /// <param name="wParam"></param>
        /// <param name="lParam"></param>
        /// <param name="handled"></param>
        /// <returns></returns>
        public IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            if (msg == NativeConstants.WM_DEVICECHANGE)
            {
                switch (wParam.ToInt32())
                {
                    case NativeConstants.DBT_DEVICEARRIVAL:
                        {
                            var devType = Marshal.ReadInt32(lParam, 4);
                            if (devType == NativeConstants.DBT_DEVTYP_VOLUME)
                            {
                                if (DeviceArrived != null)
                                {
                                    DeviceArrived(this, null); //触发设备插入事件
                                }
                            }
                        }
                        break;
                    case NativeConstants.DBT_DEVICEREMOVECOMPLETE:
                        {
                            var devType = Marshal.ReadInt32(lParam, 4);
                            if (devType == NativeConstants.DBT_DEVTYP_VOLUME)
                            {
                                if (DeviceRemoved != null)
                                {
                                    DeviceRemoved(this, null);
                                }
                            }
                        }
                        break;
                    //64位win10系统下,设备连接断开都发送一样的命令
                    case NativeConstants.DBT_DEVICEARRIVAL64:
                        DeviceArrived(this, null); //触发设备插入事件
                        break;
                }
            }
            return IntPtr.Zero;
        }

        /// <summary>
        /// 设备插入或拔出事件
        /// </summary>
        public class DriveDectctorEventArgs : EventArgs
        {
            /// <summary>
            /// 获得设备卷标
            /// </summary>
            public string Drive { get; private set; }
                public DriveDectctorEventArgs(string drive)
                {
                    Drive = drive ?? string.Empty;
                }
            }
        
        public partial class NativeConstants
        {
            /// WM_DEVICECHANGE -> 0x0219
            public const int WM_DEVICECHANGE = 537;
            /// BROADCAST_QUERY_DENY -> 0x424D5144
            //public const int BROADCAST_QUERY_DENY = 1112363332;
            //public const int DBT_DEVTYP_DEVICEINTERFACE = 5;
            //public const int DBT_DEVTYP_HANDLE = 6;
            public const int DBT_DEVICEARRIVAL = 0x8000; // system detected a new device
            //public const int DBT_DEVICEQUERYREMOVE = 0x8001;   // Preparing to remove (any program can disable the removal)
            public const int DBT_DEVICEREMOVECOMPLETE = 0x8004; // removed 
            public const int DBT_DEVTYP_VOLUME = 0x00000002; // drive type is logical volume
            public const int DBT_DEVICEARRIVAL64 = 0x00000007;
        }
    }
}
           

ADB操作

FrmClient中使用的CmdHelp用于执行ADB命令,涉及很多没有使用到的方法,就不附上了,下面只列出使用到的ADB命令。

  • 启动adb服务

    start-server

  • 获取设备列表

    devices

  • 获取指定设备的IMEI

    -s {deviceName} shell getprop gsm.mtk.imei1

  • 启动app

    -s {deviceName} shell am start -n {package/package.activity}

  • 端口映射

    -s {deviceName} forward tcp:{client port} tcp:{server port}

运行效果

Android服务端

无网络PC通过USB与多个Android设备通讯

PC客户端

无网络PC通过USB与多个Android设备通讯

参考资料

  • PC通过USB连接Android通信(Socket)
  • Android通过USB与PC通信