IOException:读取失败,套接字可能关闭 - Android 4.3 上的蓝牙 [英] IOException: read failed, socket might closed - Bluetooth on Android 4.3

查看:37
本文介绍了IOException:读取失败,套接字可能关闭 - Android 4.3 上的蓝牙的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

目前,我正在尝试在使用 Android 4.3(构建 JWR66Y,我猜是第二个 4.3 更新)的 Nexus 7(2012)上打开 BluetoothSocket 时处理一个奇怪的异常.我看过一些相关的帖子(例如 https://stackoverflow.com/questions/13648373/bluetoothsocket-connect-throwing-exception-read-failed),但似乎都没有提供解决此问题的方法.此外,正如这些线程中所建议的,重新配对无济于事,不断尝试连接(通过愚蠢的循环)也没有效果.

我正在处理一个嵌入式设备(一个无名 OBD-II 汽车适配器,类似于 http://images04.olx.com/ui/15/53/76/1316534072_254254776_2-OBD-II-BLUTOOTH-ADAPTERSCLEAR-CHECK-ENGINE-LIGHTS-With-YOUR-PHONE-Oceanside.jpg).我的 Android 2.3.7 手机连接没有任何问题,同事的 Xperia(Android 4.1.2)也可以使用.另一个 Google Nexus(我不知道是One"还是S",但不是4")在 Android 4.3 上也失败了.

这里是连接建立的片段.它在自己的线程中运行,在服务中创建.

私有类 ConnectThread extends Thread {私有静态最终 UUID EMBEDDED_BOARD_SPP = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");私人蓝牙适配器;私有布尔安全;私人蓝牙设备;私人列表uuid候选人;私人 int 候选人;受保护的布尔值开始;公共连接线程(蓝牙设备设备,布尔安全){logger.info("初始化连接到设备"+device.getName() +"/"+ device.getAddress());适配器 = BluetoothAdapter.getDefaultAdapter();this.secure = 安全;this.device = 设备;setName("蓝牙连接线程");如果 (!startQueryingForUUIDs()) {this.uuidCandidates = Collections.singletonList(EMBEDDED_BOARD_SPP);this.start();} 别的{logger.info("使用UUID发现机制.");}/** 否则将在广播接收时开始*/}私有布尔 startQueryingForUUIDs() {类cl = BluetoothDevice.class;类[] par = {};方法 fetchUuidsWithSdpMethod;尝试 {fetchUuidsWithSdpMethod = cl.getMethod("fetchUuidsWithSdp", par);} catch (NoSuchMethodException e) {logger.warn(e.getMessage());返回假;}对象[] args = {};尝试 {广播接收器接收器 = 新广播接收器(){@覆盖public void onReceive(上下文上下文,意图意图){BluetoothDevice deviceExtra = intent.getParcelableExtra("android.bluetooth.device.extra.DEVICE");Parcelable[] uuidExtra = intent.getParcelableArrayExtra("android.bluetooth.device.extra.UUID");uuidCandidates = new ArrayList();for (Parcelable uuid : uuidExtra) {uuidCandidates.add(UUID.fromString(uuid.toString()));}同步(ConnectThread.this){如果(!ConnectThread.this.started){ConnectThread.this.start();ConnectThread.this.started = true;取消注册接收器(这个);}}}};registerReceiver(receiver, new IntentFilter("android.bleutooth.device.action.UUID"));registerReceiver(receiver, new IntentFilter("android.bluetooth.device.action.UUID"));fetchUuidsWithSdpMethod.invoke(device, args);} catch (IllegalArgumentException e) {logger.warn(e.getMessage());返回假;} catch (IllegalAccessException e) {logger.warn(e.getMessage());返回假;} catch (InvocationTargetException e) {logger.warn(e.getMessage());返回假;}返回真;}公共无效运行(){布尔成功 = 假;而(选择套接字()){if (bluetoothSocket == null) {logger.warn("套接字为空!取消!");设备断开();openTroubleshootingActivity(TroubleshootingActivity.BLUETOOTH_EXCEPTION);}//总是取消发现,因为它会减慢连接速度适配器.cancelDiscovery();//连接到BluetoothSocket尝试 {//这是一个阻塞调用,只会在//连接成功或异常蓝牙Socket.connect();成功=真;休息;} catch (IOException e) {//关闭套接字尝试 {关闭套接字();} catch (IOException e2) {logger.warn(e2.getMessage(), e2);}}}如果(成功){设备连接();} 别的 {设备断开();openTroubleshootingActivity(TroubleshootingActivity.BLUETOOTH_EXCEPTION);}}私有布尔选择套接字(){if (candidate >= uuidCandidates.size()) {返回假;}BluetoothSocket tmp;UUID uuid = uuidCandidates.get(candidate++);logger.info("尝试连接到SDP"+ uuid);尝试 {如果(安全){tmp = device.createRfcommSocketToServiceRecord(用户名);} 别的 {tmp = device.createInsecureRfcommSocketToServiceRecord(用户名);}蓝牙套接字 = tmp;返回真;} catch (IOException e) {logger.warn(e.getMessage(),e);}返回假;}}

代码在 bluetoothSocket.connect() 处失败.我收到一个 java.io.IOException: 读取失败,套接字可能已关闭,读取 ret: -1.这是 GitHub 上的相应来源:https://github.com/android/platform_frameworks_base/blob/android-4.3_r2/core/java/android/bluetooth/BluetoothSocket.java#L504它通过 readInt() 调用,从 https://github.com/android/platform_frameworks_base/blob/android-4.3_r2/core/java/android/bluetooth/BluetoothSocket.java#L319>

使用的套接字的一些元数据转储导致以下信息.这些在 Nexus 7 和我的 2.3.7 手机上完全相同.

蓝牙设备'OBDII'地址:11:22:33:DD:EE:FF保税状态:12(保税)类型:1类主要版本:7936类次要版本:7936课程内容:0内容:0

我有一些其他的 OBD-II 适配器(更广泛的),它们都可以工作.有没有可能是我遗漏了什么,或者这可能是 Android 中的一个错误?

解决方案

我终于找到了解决方法.魔法隐藏在 BluetoothDevice 类的引擎盖下(参见 https://github.com/android/platform_frameworks_base/blob/android-4.3_r2/core/java/android/bluetooth/BluetoothDevice.java#L1037).

现在,当我收到该异常时,我会实例化一个回退 BluetoothSocket,类似于下面的源代码.如您所见,通过反射调用隐藏方法 createRfcommSocket.我不知道为什么这个方法是隐藏的.源代码将其定义为 public 虽然...

clazz = tmp.getRemoteDevice().getClass();Class[] paramTypes = new Class[] {Integer.TYPE};Method m = clazz.getMethod("createRfcommSocket", paramTypes);Object[] params = new Object[] {Integer.valueOf(1)};fallbackSocket = (BluetoothSocket) m.invoke(tmp.getRemoteDevice(), params);fallbackSocket.connect();

connect() 然后不再失败.我仍然遇到了一些问题.基本上,这有时会阻塞并失败.在这种情况下,重新启动 SPP 设备(拔下/插入)会有所帮助.有时,即使设备已经绑定,我也会在 connect() 之后收到另一个配对请求.

更新:

这是一个完整的类,包含一些嵌套类.对于真正的实现,这些可以作为单独的类进行.

import java.io.IOException;导入 java.io.InputStream;导入 java.io.OutputStream;导入 java.lang.reflect.Method;导入 java.util.List;导入 java.util.UUID;导入 android.bluetooth.BluetoothAdapter;导入 android.bluetooth.BluetoothDevice;导入 android.bluetooth.BluetoothSocket;导入 android.util.Log;公共类蓝牙连接器{私有 BluetoothSocketWrapper 蓝牙套接字;私人蓝牙设备;私有布尔安全;私人蓝牙适配器;私人列表uuid候选人;私人 int 候选人;/*** @param device 设备* @param secure 如果连接应该通过安全套接字完成* @param 适配器 Android BT 适配器* @param uuidCandidates 一个 UUID 列表.如果为 null 或为空,则使用串行 PP id*/公共蓝牙连接器(蓝牙设备设备,布尔安全,蓝牙适配器适配器,列表uuidCandidates) {this.device = 设备;this.secure = 安全;this.adapter = 适配器;this.uuidCandidates = uuidCandidates;if (this.uuidCandidates == null || this.uuidCandidates.isEmpty()) {this.uuidCandidates = new ArrayList();this.uuidCandidates.add(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));}}public BluetoothSocketWrapper connect() 抛出 IOException {布尔成功 = 假;而(选择套接字()){适配器.cancelDiscovery();尝试 {蓝牙Socket.connect();成功=真;休息;} catch (IOException e) {//尝试回退尝试 {bluetoothSocket = new FallbackBluetoothSocket(bluetoothSocket.getUnderlyingSocket());线程睡眠(500);蓝牙Socket.connect();成功=真;休息;} catch (FallbackException e1) {Log.w("BT", "无法初始化 FallbackBluetoothSocket 类.", e);} catch (InterruptedException e1) {Log.w("BT", e1.getMessage(), e1);} catch (IOException e1) {Log.w("BT", "回退失败.取消.", e1);}}}如果(!成功){throw new IOException("无法连接到设备:"+ device.getAddress());}返回蓝牙套接字;}私有布尔 selectSocket() 抛出 IOException {if (candidate >= uuidCandidates.size()) {返回假;}BluetoothSocket tmp;UUID uuid = uuidCandidates.get(candidate++);Log.i("BT", "尝试连接协议:"+ uuid);如果(安全){tmp = device.createRfcommSocketToServiceRecord(uuid);} 别的 {tmp = device.createInsecureRfcommSocketToServiceRecord(uuid);}bluetoothSocket = new NativeBluetoothSocket(tmp);返回真;}公共静态接口 BluetoothSocketWrapper {InputStream getInputStream() 抛出 IOException;OutputStream getOutputStream() 抛出 IOException;String getRemoteDeviceName();void connect() 抛出 IOException;String getRemoteDeviceAddress();void close() 抛出 IOException;BluetoothSocket getUnderlyingSocket();}公共静态类 NativeBluetoothSocket 实现 BluetoothSocketWrapper {私人蓝牙插座;公共 NativeBluetoothSocket(BluetoothSocket tmp) {this.socket = tmp;}@覆盖public InputStream getInputStream() 抛出 IOException {返回 socket.getInputStream();}@覆盖公共输出流 getOutputStream() 抛出 IOException {返回 socket.getOutputStream();}@覆盖公共字符串 getRemoteDeviceName() {返回 socket.getRemoteDevice().getName();}@覆盖public void connect() 抛出 IOException {套接字连接();}@覆盖公共字符串 getRemoteDeviceAddress() {返回 socket.getRemoteDevice().getAddress();}@覆盖public void close() 抛出 IOException {socket.close();}@覆盖public BluetoothSocket getUnderlyingSocket() {返回套接字;}}公共类 FallbackBluetoothSocket 扩展 NativeBluetoothSocket {私有 BluetoothSocket fallbackSocket;公共 FallbackBluetoothSocket(BluetoothSocket tmp) 抛出 FallbackException {超级(tmp);尝试{类clazz = tmp.getRemoteDevice().getClass();Class[] paramTypes = new Class[] {Integer.TYPE};Method m = clazz.getMethod("createRfcommSocket", paramTypes);Object[] params = new Object[] {Integer.valueOf(1)};fallbackSocket = (BluetoothSocket) m.invoke(tmp.getRemoteDevice(), params);}捕获(例外 e){抛出新的 FallbackException(e);}}@覆盖public InputStream getInputStream() 抛出 IOException {返回 fallbackSocket.getInputStream();}@覆盖公共输出流 getOutputStream() 抛出 IOException {返回 fallbackSocket.getOutputStream();}@覆盖public void connect() 抛出 IOException {fallbackSocket.connect();}@覆盖public void close() 抛出 IOException {fallbackSocket.close();}}公共静态类 FallbackException 扩展异常 {/****/private static final long serialVersionUID = 1L;公共回退异常(异常 e){超级(e);}}}

Currently I am trying to deal with a strange Exception when opening a BluetoothSocket on my Nexus 7 (2012), with Android 4.3 (Build JWR66Y, I guess the second 4.3 update). I have seen some related postings (e.g. https://stackoverflow.com/questions/13648373/bluetoothsocket-connect-throwing-exception-read-failed), but none seems to provide a workaround for this issue. Also, as suggested in these threads, re-pairing does not help, and constantly trying to connect (through a stupid loop) also has no effect.

I am dealing with an embedded device (a noname OBD-II car adapter, similar to http://images04.olx.com/ui/15/53/76/1316534072_254254776_2-OBD-II-BLUTOOTH-ADAPTERSCLEAR-CHECK-ENGINE-LIGHTS-WITH-YOUR-PHONE-Oceanside.jpg). My Android 2.3.7 phone does not have any issues connecting, and the Xperia of a colleague (Android 4.1.2) also works. Another Google Nexus (I dont know if 'One' or 'S', but not '4') also fails with Android 4.3.

Here is the Snippet of the connection establishment. It is running in its own Thread, created within a Service.

private class ConnectThread extends Thread {

    private static final UUID EMBEDDED_BOARD_SPP = UUID
        .fromString("00001101-0000-1000-8000-00805F9B34FB");

    private BluetoothAdapter adapter;
    private boolean secure;
    private BluetoothDevice device;
    private List<UUID> uuidCandidates;
    private int candidate;
    protected boolean started;

    public ConnectThread(BluetoothDevice device, boolean secure) {
        logger.info("initiliasing connection to device "+device.getName() +" / "+ device.getAddress());
        adapter = BluetoothAdapter.getDefaultAdapter();
        this.secure = secure;
        this.device = device;

        setName("BluetoothConnectThread");

        if (!startQueryingForUUIDs()) {
            this.uuidCandidates = Collections.singletonList(EMBEDDED_BOARD_SPP);
            this.start();
        } else{
            logger.info("Using UUID discovery mechanism.");
        }
        /*
         * it will start upon the broadcast receive otherwise
         */
    }

    private boolean startQueryingForUUIDs() {
        Class<?> cl = BluetoothDevice.class;

        Class<?>[] par = {};
        Method fetchUuidsWithSdpMethod;
        try {
            fetchUuidsWithSdpMethod = cl.getMethod("fetchUuidsWithSdp", par);
        } catch (NoSuchMethodException e) {
            logger.warn(e.getMessage());
            return false;
        }

        Object[] args = {};
        try {
            BroadcastReceiver receiver = new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    BluetoothDevice deviceExtra = intent.getParcelableExtra("android.bluetooth.device.extra.DEVICE");
                    Parcelable[] uuidExtra = intent.getParcelableArrayExtra("android.bluetooth.device.extra.UUID");

                    uuidCandidates = new ArrayList<UUID>();
                    for (Parcelable uuid : uuidExtra) {
                        uuidCandidates.add(UUID.fromString(uuid.toString()));
                    }

                    synchronized (ConnectThread.this) {
                        if (!ConnectThread.this.started) {
                            ConnectThread.this.start();
                            ConnectThread.this.started = true;
                            unregisterReceiver(this);
                        }

                    }
                }

            };
            registerReceiver(receiver, new IntentFilter("android.bleutooth.device.action.UUID"));
            registerReceiver(receiver, new IntentFilter("android.bluetooth.device.action.UUID"));

            fetchUuidsWithSdpMethod.invoke(device, args);
        } catch (IllegalArgumentException e) {
            logger.warn(e.getMessage());
            return false;
        } catch (IllegalAccessException e) {
            logger.warn(e.getMessage());
            return false;
        } catch (InvocationTargetException e) {
            logger.warn(e.getMessage());
            return false;
        }           

        return true;
    }

    public void run() {
        boolean success = false;
        while (selectSocket()) {

            if (bluetoothSocket == null) {
                logger.warn("Socket is null! Cancelling!");
                deviceDisconnected();
                openTroubleshootingActivity(TroubleshootingActivity.BLUETOOTH_EXCEPTION);
            }

            // Always cancel discovery because it will slow down a connection
            adapter.cancelDiscovery();

            // Make a connection to the BluetoothSocket
            try {
                // This is a blocking call and will only return on a
                // successful connection or an exception
                bluetoothSocket.connect();
                success = true;
                break;

            } catch (IOException e) {
                // Close the socket
                try {
                    shutdownSocket();
                } catch (IOException e2) {
                    logger.warn(e2.getMessage(), e2);
                }
            }
        }

        if (success) {
            deviceConnected();
        } else {
            deviceDisconnected();
            openTroubleshootingActivity(TroubleshootingActivity.BLUETOOTH_EXCEPTION);
        }
    }

    private boolean selectSocket() {
        if (candidate >= uuidCandidates.size()) {
            return false;
        }

        BluetoothSocket tmp;
        UUID uuid = uuidCandidates.get(candidate++);
        logger.info("Attempting to connect to SDP "+ uuid);
        try {
            if (secure) {
                tmp = device.createRfcommSocketToServiceRecord(
                        uuid);
            } else {
                tmp = device.createInsecureRfcommSocketToServiceRecord(
                        uuid);
            }
            bluetoothSocket = tmp;
            return true;
        } catch (IOException e) {
            logger.warn(e.getMessage() ,e);
        }

        return false;
    }

}

The code is failing at bluetoothSocket.connect(). I am getting a java.io.IOException: read failed, socket might closed, read ret: -1. This is the corresponding source at GitHub: https://github.com/android/platform_frameworks_base/blob/android-4.3_r2/core/java/android/bluetooth/BluetoothSocket.java#L504 Its called through readInt(), called from https://github.com/android/platform_frameworks_base/blob/android-4.3_r2/core/java/android/bluetooth/BluetoothSocket.java#L319

Some metadata dump of the used socket resulted in the following information. These are exactly the same on Nexus 7 and my 2.3.7 phone.

Bluetooth Device 'OBDII'
Address: 11:22:33:DD:EE:FF
Bond state: 12 (bonded)
Type: 1
Class major version: 7936
Class minor version: 7936
Class Contents: 0
Contents: 0

I have some other OBD-II adapters (more expansives) and they all work. Is there any chance, that I am missing something or might this be a bug in Android?

解决方案

I have finally found a workaround. The magic is hidden under the hood of the BluetoothDevice class (see https://github.com/android/platform_frameworks_base/blob/android-4.3_r2/core/java/android/bluetooth/BluetoothDevice.java#L1037).

Now, when I receive that exception, I instantiate a fallback BluetoothSocket, similar to the source code below. As you can see, invoking the hidden method createRfcommSocket via reflections. I have no clue why this method is hidden. The source code defines it as public though...

Class<?> clazz = tmp.getRemoteDevice().getClass();
Class<?>[] paramTypes = new Class<?>[] {Integer.TYPE};

Method m = clazz.getMethod("createRfcommSocket", paramTypes);
Object[] params = new Object[] {Integer.valueOf(1)};

fallbackSocket = (BluetoothSocket) m.invoke(tmp.getRemoteDevice(), params);
fallbackSocket.connect();

connect() then does not fail any longer. I have experienced a few issues still. Basically, this sometimes blocks and fails. Rebooting the SPP-Device (plug off / plug in) helps in such cases. Sometimes I also get another Pairing request after connect() even when the device is already bonded.

UPDATE:

here is a complete class, containing some nested classes. for a real implementation these could be held as seperate classes.

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.List;
import java.util.UUID;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.util.Log;

public class BluetoothConnector {

    private BluetoothSocketWrapper bluetoothSocket;
    private BluetoothDevice device;
    private boolean secure;
    private BluetoothAdapter adapter;
    private List<UUID> uuidCandidates;
    private int candidate;


    /**
     * @param device the device
     * @param secure if connection should be done via a secure socket
     * @param adapter the Android BT adapter
     * @param uuidCandidates a list of UUIDs. if null or empty, the Serial PP id is used
     */
    public BluetoothConnector(BluetoothDevice device, boolean secure, BluetoothAdapter adapter,
            List<UUID> uuidCandidates) {
        this.device = device;
        this.secure = secure;
        this.adapter = adapter;
        this.uuidCandidates = uuidCandidates;

        if (this.uuidCandidates == null || this.uuidCandidates.isEmpty()) {
            this.uuidCandidates = new ArrayList<UUID>();
            this.uuidCandidates.add(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
        }
    }

    public BluetoothSocketWrapper connect() throws IOException {
        boolean success = false;
        while (selectSocket()) {
            adapter.cancelDiscovery();

            try {
                bluetoothSocket.connect();
                success = true;
                break;
            } catch (IOException e) {
                //try the fallback
                try {
                    bluetoothSocket = new FallbackBluetoothSocket(bluetoothSocket.getUnderlyingSocket());
                    Thread.sleep(500);                  
                    bluetoothSocket.connect();
                    success = true;
                    break;  
                } catch (FallbackException e1) {
                    Log.w("BT", "Could not initialize FallbackBluetoothSocket classes.", e);
                } catch (InterruptedException e1) {
                    Log.w("BT", e1.getMessage(), e1);
                } catch (IOException e1) {
                    Log.w("BT", "Fallback failed. Cancelling.", e1);
                }
            }
        }

        if (!success) {
            throw new IOException("Could not connect to device: "+ device.getAddress());
        }

        return bluetoothSocket;
    }

    private boolean selectSocket() throws IOException {
        if (candidate >= uuidCandidates.size()) {
            return false;
        }

        BluetoothSocket tmp;
        UUID uuid = uuidCandidates.get(candidate++);

        Log.i("BT", "Attempting to connect to Protocol: "+ uuid);
        if (secure) {
            tmp = device.createRfcommSocketToServiceRecord(uuid);
        } else {
            tmp = device.createInsecureRfcommSocketToServiceRecord(uuid);
        }
        bluetoothSocket = new NativeBluetoothSocket(tmp);

        return true;
    }

    public static interface BluetoothSocketWrapper {

        InputStream getInputStream() throws IOException;

        OutputStream getOutputStream() throws IOException;

        String getRemoteDeviceName();

        void connect() throws IOException;

        String getRemoteDeviceAddress();

        void close() throws IOException;

        BluetoothSocket getUnderlyingSocket();

    }


    public static class NativeBluetoothSocket implements BluetoothSocketWrapper {

        private BluetoothSocket socket;

        public NativeBluetoothSocket(BluetoothSocket tmp) {
            this.socket = tmp;
        }

        @Override
        public InputStream getInputStream() throws IOException {
            return socket.getInputStream();
        }

        @Override
        public OutputStream getOutputStream() throws IOException {
            return socket.getOutputStream();
        }

        @Override
        public String getRemoteDeviceName() {
            return socket.getRemoteDevice().getName();
        }

        @Override
        public void connect() throws IOException {
            socket.connect();
        }

        @Override
        public String getRemoteDeviceAddress() {
            return socket.getRemoteDevice().getAddress();
        }

        @Override
        public void close() throws IOException {
            socket.close();
        }

        @Override
        public BluetoothSocket getUnderlyingSocket() {
            return socket;
        }

    }

    public class FallbackBluetoothSocket extends NativeBluetoothSocket {

        private BluetoothSocket fallbackSocket;

        public FallbackBluetoothSocket(BluetoothSocket tmp) throws FallbackException {
            super(tmp);
            try
            {
              Class<?> clazz = tmp.getRemoteDevice().getClass();
              Class<?>[] paramTypes = new Class<?>[] {Integer.TYPE};
              Method m = clazz.getMethod("createRfcommSocket", paramTypes);
              Object[] params = new Object[] {Integer.valueOf(1)};
              fallbackSocket = (BluetoothSocket) m.invoke(tmp.getRemoteDevice(), params);
            }
            catch (Exception e)
            {
                throw new FallbackException(e);
            }
        }

        @Override
        public InputStream getInputStream() throws IOException {
            return fallbackSocket.getInputStream();
        }

        @Override
        public OutputStream getOutputStream() throws IOException {
            return fallbackSocket.getOutputStream();
        }


        @Override
        public void connect() throws IOException {
            fallbackSocket.connect();
        }


        @Override
        public void close() throws IOException {
            fallbackSocket.close();
        }

    }

    public static class FallbackException extends Exception {

        /**
         * 
         */
        private static final long serialVersionUID = 1L;

        public FallbackException(Exception e) {
            super(e);
        }

    }
}

这篇关于IOException:读取失败,套接字可能关闭 - Android 4.3 上的蓝牙的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆