Android 硬件通讯之 蓝牙,USB,WIFI(二.WIFI)

WIFI通讯:双网络通讯

随着公司产品的不断迭代,发现上位机(App)与下位机(Vci)通讯,在收发长包数据时,蓝牙通道出现了丢字节,丢包现象。而这时候会提示用户使用USB串口通讯方式。但是USB串口通讯,必须局限于线束的连接。

为了优化这个问题,增加引入了WIFI上下位机通讯通道。

应用场景

App与硬件使用WIFI(无网)通道进行命令收到,并在App内强制使用4G通道进行Http请求

 

1,接收到WIFI接连的广播后,执行连接操作

    /**
     * 发现wifi设备
     *
     * @param wifiScanResultEvent
     */
    @Subscribe(threadMode = ThreadMode.BACKGROUND)
    public void onRecivedBluetoothDevice(WifiScanResultEvent wifiScanResultEvent) {
        if (deviceInfo == null) {
            Log.e(TAG, "WIFI信息为空,停止扫描");
            return;
        }

        if (isConnecting()) {
            Log.e(TAG, "WIFI连接中,停止此次扫描");
            return;
        }
        //如果WIFI已连接上则退出
        if (isConnected()) {
            Log.e(TAG, "WIFI已连接上,不在扫描");
            return;
        }
        if (addNetworkConfiguration(deviceInfo.getBssid())) {
            stopScanDevice();
            connecting();
            if (connectToNetwork(deviceInfo.getSsid(), deviceInfo.getPassword(), deviceInfo.getWifiType()) < 0) {
                connectionError(-3, "无法添加wifi配置信息");
            } else {
                stopDiscovery();
                Observable<String> observable = Observable.create(new ObservableOnSubscribe<String>() {
                    @Override
                    public void subscribe(ObservableEmitter<String> emitter) {
                        createScokect();
                        emitter.onNext("");
                    }
                });

                Consumer<String> consumer = new Consumer<String>() {
                    @Override
                    public void accept(String bean) {

                    }
                };
                observable.subscribeOn(Schedulers.newThread())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe(consumer);
            }
        }
    }


  /**
     * 添加wifi链接配置
     *
     * @param bssid 下位机wifi mac地址
     * @return 有该wifi设备信号
     */
    private boolean addNetworkConfiguration(String bssid) {
        //获取当前wifi 列表,有可能要链接的wifi不处于当前列表中
        List<ScanResult> wifiList = mWifiManager.getScanResults();
        if (wifiList == null || wifiList.size() == 0) {
            return false;
        }

        ScanResult scanResul;
        for (int i = 0, size = wifiList.size(); i < size; i++) {
            scanResul = wifiList.get(i);
            Log.e(TAG, "wifi name " + scanResul.SSID + " mac " + scanResul.BSSID);
            Loger.printLog("scanWifiList", "wifi name " + scanResul.SSID + " mac " + scanResul.BSSID);
            if (scanResul.BSSID.equals(bssid)) {
                Log.e(TAG, "匹配到wifi name " + scanResul.SSID + " mac " + scanResul.BSSID);
                return true;
            }
        }
        return false;
    }

    /**
     * 链接到wifi网络
     *
     * @param ssid
     * @param password
     * @param type
     * @return 成功链接到wifi后返回
     */
    private int connectToNetwork(String ssid, String password, @WifiType int type) {
        WifiConfiguration configuration = createWifiInfo(ssid, password, type);
        int networkId = mWifiManager.addNetwork(configuration);  // 添加WIFI网络
        if (networkId < 0) {
            Log.e(TAG, "WIFI已连接失败 networkId:" + networkId);
            return -1;
        }
        // 使WIFI网络有效
        mWifiManager.enableNetwork(networkId, true);
        Log.e(TAG, "WIFI已连接成功 networkId:" + networkId);
        return networkId;
    }

    /**
     * 创建WiFi配置
     *
     * @param ssid
     * @param password
     * @param type
     * @return
     */
    private WifiConfiguration createWifiInfo(String ssid, String password, @WifiType int type) {

        clearOldConfiguration(ssid);

        WifiConfiguration config = new WifiConfiguration();
        config.allowedAuthAlgorithms.clear();
        config.allowedGroupCiphers.clear();
        config.allowedKeyManagement.clear();
        config.allowedPairwiseCiphers.clear();
        config.allowedProtocols.clear();
        config.SSID = "\"" + ssid + "\"";

        // 分为三种情况:1没有密码2用wep加密3用wpa加密
        if (type == WifiType.TYPE_NO_PASSWD) {// WIFICIPHER_NOPASS
            config.wepKeys[0] = "";
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
            config.wepTxKeyIndex = 0;

        } else if (type == WifiType.TYPE_WEP) {  //  WIFICIPHER_WEP
            config.hiddenSSID = true;
            config.wepKeys[0] = "\"" + password + "\"";
            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
            config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
            config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
            config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);
            config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
            config.wepTxKeyIndex = 0;
        } else if (type == WifiType.TYPE_WPA) {   // WIFICIPHER_WPA
            config.preSharedKey = "\"" + password + "\"";
            config.hiddenSSID = true;
            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
            config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
            config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
            config.allowedProtocols.set(WifiConfiguration.Protocol.WPA);
            config.status = WifiConfiguration.Status.ENABLED;
            config.status = WifiConfiguration.Status.ENABLED;
        } else if (type == WifiType.TYPE_WPA2) {
            config.preSharedKey = "\"" + password + "\"";
            config.hiddenSSID = true;
            config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
            config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
            config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
            config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
            config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
            config.status = WifiConfiguration.Status.ENABLED;
        }
        return config;
    }

    /**
     * 删除之前的链接配置信息
     *
     * @param ssid
     */
    private void clearOldConfiguration(String ssid) {

        List<WifiConfiguration> existingConfigs = mWifiManager.getConfiguredNetworks();
        if (existingConfigs == null || existingConfigs.size() == 0) {
            return;
        }

        WifiConfiguration wiifConfig;
        for (int i = 0, size = existingConfigs.size(); i < size; i++) {
            wiifConfig = existingConfigs.get(i);
            if (wiifConfig.SSID.equals("\"" + ssid + "\"")) {
                mWifiManager.removeNetwork(wiifConfig.networkId);        //TODO 删除之前保存的wifi链接信息 ?
            }
        }
    }


2.连接成功WIFI后创建Socket连接(这里是启动ServerSocket,等待下位机连接),下位机连接成功创建IO

    private void createScokect() {
        closeSocket();
        try {
            Log.i(TAG, "server已启动,等待客户端连接");
            int port = 8084;
            serverSocket = new ServerSocket(port);
            Log.i(TAG, serverSocket.toString());
            //侦听对此套接字的连接并接受它。 该方法将阻塞,直到建立连接。
            mWifiSocket = serverSocket.accept();
            Log.i(TAG, "client 已连接");
            //设置服务器等待超时时间,0:表示无限等待
            mWifiSocket.setSoTimeout(30000);
            if (mWifiSocket.isConnected()) {
                mOutputStream = mWifiSocket.getOutputStream();
                mInputStream = mWifiSocket.getInputStream();
                connected();
                Log.e(TAG, "Socket连接成功");
            } else {
                Log.e(TAG, "Socket连接失败1");
            }
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "ServerSocket" + e.getMessage());
        }
    }

3.数据收发

public class WifiTransfer extends BaseWifiConnceter {

    @Override
    public boolean sendData(byte[] data) {

        if (isConnected()) {

            if (mOutputStream != null) {

                try {
                    mOutputStream.write(data);
                    mOutputStream.flush();
                    onDataSended(data);
                    return true;

                } catch (IOException e) {
                    onSendDataError(-3, "写入时IO异常:" + e.getLocalizedMessage());
                    return false;
                }
            }

            onSendDataError(-2, "程序异常,数据流断开");
            return false;
        } else {
            onSendDataError(-1, "连接已断开");
            return false;
        }
    }

    @Override
    protected byte[] readData() {

        if (mInputStream != null) {

            try {
                int length = mInputStream.available();
                if (length > 1) {
                    byte[] temp = new byte[length];
                    mInputStream.read(temp);
                    return temp;
                }
            } catch (IOException e) {
                onRecivedDataErrror(-4, "读取数据时IO异常");
            }
        }

        return null;
    }
}

4.开启V&P&N

通常Android设备连接了一个无法联网的WIFI时,即使4G打开的情况下,应用内的网络请求也是优先走WIFI通道,导致无法进行常规的Http连接请求。

解决办法:开启V&P&N

if (ret.getDeviceType() == DeviceType.WIFI) {
                journal.setCatalog1("WIFI连接");
                hideBluetoothConnectDialog();
                if (VpnUtils.lacksPermission(VpnUtils.permissions, BaseTopBarActivity.this)) {//判断是否拥有权限
                    //请求权限,第二参数权限String数据,第三个参数是请求码便于在onRequestPermissionsResult 方法中根据code进行判断
                    ActivityCompat.requestPermissions(BaseTopBarActivity.this, permissions, OPEN_SET_REQUEST_CODE);
                } else if (VpnUtils.lacksPermission(VpnUtils.PERMISSIONS_STORAGE, BaseTopBarActivity.this)) {//判断是否拥有权限
                    ActivityCompat.requestPermissions(BaseTopBarActivity.this, VpnUtils.PERMISSIONS_STORAGE, VpnUtils.REQUEST_PERMISSION_CODE);
                } else {
                    //拥有权限执行操作
                    Intent vpnIntent = VpnService.prepare(BaseTopBarActivity.this);
                    if (vpnIntent != null)
                        startActivityForResult(vpnIntent, VpnUtils.VPN_REQUEST_CODE);
                    else {
                        startService(new Intent(BaseTopBarActivity.this, LocalVPNService.class));
                    }
                }
            }


public class LocalVPNService extends VpnService {
    private static final String TAG = LocalVPNService.class.getSimpleName();
    private static final String VPN_ADDRESS = "10.0.0.2"; // Only IPv4 support for now
    private static final String VPN_ROUTE = "0.0.0.0"; // Intercept everything

    public static final String BROADCAST_VPN_STATE = "com.commonrail.mft.decoder.localvpn.VPN_STATE";

    private static boolean isRunning = false;

    private ParcelFileDescriptor vpnInterface = null;
    private ConcurrentLinkedQueue<Packet> deviceToNetworkUDPQueue;
    private ConcurrentLinkedQueue<Packet> deviceToNetworkTCPQueue;
    private ConcurrentLinkedQueue<ByteBuffer> networkToDeviceQueue;
    private ExecutorService executorService;

    private Selector udpSelector;
    private Selector tcpSelector;

    public static Network networkId;

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    public void onCreate() {
        super.onCreate();
        isRunning = true;
        setupVPN();
        try {
            udpSelector = Selector.open();
            tcpSelector = Selector.open();
            deviceToNetworkUDPQueue = new ConcurrentLinkedQueue<>();
            deviceToNetworkTCPQueue = new ConcurrentLinkedQueue<>();
            networkToDeviceQueue = new ConcurrentLinkedQueue<>();

            executorService = Executors.newFixedThreadPool(5);
            executorService.submit(new UDPInput(networkToDeviceQueue, udpSelector));
            executorService.submit(new UDPOutput(deviceToNetworkUDPQueue, udpSelector, this));
            executorService.submit(new TCPInput(networkToDeviceQueue, tcpSelector));
            executorService.submit(new TCPOutput(deviceToNetworkTCPQueue, networkToDeviceQueue, tcpSelector, this));
            executorService.submit(new VPNRunnable(vpnInterface.getFileDescriptor(),
                    deviceToNetworkUDPQueue, deviceToNetworkTCPQueue, networkToDeviceQueue));
            LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(BROADCAST_VPN_STATE).putExtra("running", true));
            requestWorkNetForHigh(this);
        } catch (IOException e) {
            Log.e(TAG, "Error starting service", e);
            cleanup();
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void setupVPN() {
        if (vpnInterface == null) {
            Builder builder = new Builder();
            builder.addAddress(VPN_ADDRESS, 32);
            builder.addRoute(VPN_ROUTE, 0);
            builder.addDnsServer("8.8.8.8");
            builder.addDnsServer("8.8.4.4");
            builder.addDnsServer("208.67.222.222");
            builder.addDnsServer("114.114.114.114");
            builder.setMtu(1280);
            vpnInterface = builder.setSession(getString(R.string.app_name)).establish();
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_STICKY;
    }

    public static boolean isRunning() {
        return isRunning;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        isRunning = false;
        executorService.shutdownNow();
        cleanup();
        Log.i(TAG, "Stopped");
    }

    private void cleanup() {
        deviceToNetworkTCPQueue = null;
        deviceToNetworkUDPQueue = null;
        networkToDeviceQueue = null;
        ByteBufferPool.clear();
        closeResources(udpSelector, tcpSelector, vpnInterface);
    }

    // TODO: Move this to a "utils" class for reuse
    private static void closeResources(Closeable... resources) {
        for (Closeable resource : resources) {
            try {
                resource.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static class VPNRunnable implements Runnable {
        private static final String TAG = VPNRunnable.class.getSimpleName();

        private FileDescriptor vpnFileDescriptor;

        private ConcurrentLinkedQueue<Packet> deviceToNetworkUDPQueue;
        private ConcurrentLinkedQueue<Packet> deviceToNetworkTCPQueue;
        private ConcurrentLinkedQueue<ByteBuffer> networkToDeviceQueue;

        public VPNRunnable(FileDescriptor vpnFileDescriptor,
                           ConcurrentLinkedQueue<Packet> deviceToNetworkUDPQueue,
                           ConcurrentLinkedQueue<Packet> deviceToNetworkTCPQueue,
                           ConcurrentLinkedQueue<ByteBuffer> networkToDeviceQueue) {
            this.vpnFileDescriptor = vpnFileDescriptor;
            this.deviceToNetworkUDPQueue = deviceToNetworkUDPQueue;
            this.deviceToNetworkTCPQueue = deviceToNetworkTCPQueue;
            this.networkToDeviceQueue = networkToDeviceQueue;
        }

        @Override
        public void run() {
            Log.i(TAG, "Started");

            FileChannel vpnInput = new FileInputStream(vpnFileDescriptor).getChannel();
            FileChannel vpnOutput = new FileOutputStream(vpnFileDescriptor).getChannel();

            try {
                ByteBuffer bufferToNetwork = null;
                boolean dataSent = true;
                boolean dataReceived;
                while (!Thread.interrupted()) {
                    if (dataSent)
                        bufferToNetwork = ByteBufferPool.acquire();
                    else
                        bufferToNetwork.clear();

                    // TODO: Block when not connected
                    int readBytes = vpnInput.read(bufferToNetwork);
                    if (readBytes > 0) {
                        dataSent = true;
                        bufferToNetwork.flip();
                        Packet packet = new Packet(bufferToNetwork);
                        if (packet.isUDP()) {
                            deviceToNetworkUDPQueue.offer(packet);
                        } else if (packet.isTCP()) {
                            deviceToNetworkTCPQueue.offer(packet);
                        } else {
                            Log.w(TAG, "Unknown packet type");
                            Log.w(TAG, packet.ip4Header.toString());
                            dataSent = false;
                        }
                    } else {
                        dataSent = false;
                    }

                    ByteBuffer bufferFromNetwork = networkToDeviceQueue.poll();
                    if (bufferFromNetwork != null) {
                        bufferFromNetwork.flip();
                        while (bufferFromNetwork.hasRemaining())
                            vpnOutput.write(bufferFromNetwork);
                        dataReceived = true;

                        ByteBufferPool.release(bufferFromNetwork);
                    } else {
                        dataReceived = false;
                    }

                    // TODO: Sleep-looping is not very battery-friendly, consider blocking instead
                    // Confirm if throughput with ConcurrentQueue is really higher compared to BlockingQueue
                    if (!dataSent && !dataReceived)
                        Thread.sleep(10);
                }
            } catch (InterruptedException e) {
                Log.i(TAG, "Stopping");
            } catch (IOException e) {
                Log.w(TAG, e.toString(), e);
            } finally {
                closeResources(vpnInput, vpnOutput);
            }
        }
    }

    @SuppressLint("WrongConstant")
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public void requestWorkNetForHigh(Context context) {
        final ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        if (cm == null) {
            Log.i(TAG, "ConnectivityManager is null, cannot try to force a mobile connection");
            return;
        }
        Log.i(TAG, "post50MobileEnable, requesting Cellular network");

        NetworkRequest.Builder builder = new NetworkRequest.Builder();
        builder.addCapability(NET_CAPABILITY_INTERNET);
        //强制使用蜂窝数据网络-移动数据
        builder.addTransportType(TRANSPORT_CELLULAR);
        NetworkRequest build = builder.build();

        try {
            Log.i(TAG, "Requesting Connectivity Manager");
            cm.requestNetwork(build, new ConnectivityManager.NetworkCallback() {
                @Override
                public void onAvailable(Network network) {
                    super.onAvailable(network);
                    networkId = network;
                    Log.i(TAG, TAG + "已根据功能和传输类型找到合适的网络 NetWorkCallBack " + network);
                    if (Build.VERSION.SDK_INT >= 23) {
                        cm.bindProcessToNetwork(network);
                    } else {// 23后这个方法舍弃了
                        ConnectivityManager.setProcessDefaultNetwork(network);
                    }
                }
            });

        } catch (SecurityException e) {
            e.printStackTrace();
            Log.i(TAG, "com.speedify.speedifyAndroid.MobileController Cannot write settings. Requesting Permission");
        } catch (Exception e2) {
            e2.printStackTrace();
            Log.e(TAG, "Unknown Exception", e2);
        }
    }
}

 

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值