为您推荐:
精华内容
最热下载
问答
  • 5星
    6.55MB a876106354 2021-06-28 11:27:06
  • 5星
    11.15MB weixin_46423500 2021-05-31 21:51:13
  • 5星
    8.29MB qq_44526422 2021-02-19 17:01:30
  • 5星
    11.72MB GZXGYZ 2021-03-19 21:21:29
  • 5星
    5.04MB qq_44629109 2020-12-18 21:20:08
  • 5星
    611.1MB qq_42774492 2021-03-13 18:21:19
  • 5星
    1.82MB xiaolong1126626497 2021-05-26 21:00:57
  • 5星
    54.6MB a_zxswer 2021-05-05 14:19:42
  • 5星
    59.05MB weixin_41581125 2021-01-06 14:28:14
  • 这段时间一直在进行手机与BLE的相关项目开发,其中对读数据、数据、接收通知消息、接收指示型消息这几种操作有了一些了解,今天贴出来跟大家分享一下。(关于蓝牙的搜索,连接,获取服务,获取特征值等方法这里就...

    这段时间一直在进行手机与BLE的相关项目开发,其中对读数据、写数据、接收通知消息、接收指示型消息这几种操作有了一些了解,今天贴出来跟大家分享一下。(关于蓝牙的搜索,连接,获取服务,获取特征值等方法这里就不再赘述了,网上很多前辈总结的很全面,可以自行搜索。

    这几个操作的共同特性都是通过调用回调方法进行数据的获取和交换,所以进行相关操作之前熟悉每个操作相关的回调方法是很有必要的。

    1.接收通知消息(setCharacteristicNotification):

    前期进行BLE开发或许很容易混淆读操作和接收通知消息,这里我按自己的理解粗糙的讲解一下。通知是BLE终端主动或是相关操作触发而发出的数据,任何一个用于权限的主机都可以读取到这个数据。

    而读操作时谁进行读数据操作,然后BLE终端才会被动的发出一个数据,而这个数据只能是读操作的对象才有资格获得到这个数据。

    //首先必须获取通知通道(这里的UUID根据自己BLE终端,自己进行读取操作获得)

    service = mBluetoothGatt.getService(uuid_s);

    characteristic = service.getCharacteristic(uuid_c);

    BluetoothGattDescriptor descriptor = characteristic.getDescriptor(uuid_d);

    if (descriptor != null) {

    descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);

    mBluetoothGatt.writeDescriptor(descriptor);

    }

    mBluetoothGatt.setCharacteristicNotification(characteristic, true);

    //然后重写回调方法,跟根据通知数据的类型进行解析操作,判断是否接收到通知利用Log日志可以观察到。

    @Override

    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {

    Log.i(TAG, "onCharacteristicChanged!");

    //进行接收数据的相关操作

    String data = characteristic.getValue();

    }

    super.onCharacteristicChanged(gatt, characteristic);

    }

    2.读数据(readCharacteristic):

    读数据,顾名思义是对BLE进行读取数据的操作,这个数据

    //首先获取读数据相关的通道

    service = mBluetoothGatt.getService(uuid_s);

    characteristic = service.getCharacteristic(uuid_c);

    mBluetoothGatt.readCharacteristic(characteristic);

    3.写数据(readCharacteristic):

    if (mBluetoothGatt != null){

    if (characteristic != null){

    characteristic.setValue(data);

    mBluetoothGatt.writeCharacteristic(characteristic);

    Log.i(TAG, "writeData:写了一次数据!");

    }

    }

    4.指示(readCharacteristic):

    //获取指示通道

    characteristic = service.getCharacteristic(uuid2_c_n);

    if (characteristic != null){

    BluetoothGattDescriptor descriptor = characteristic_notify.getDescriptor(uuid_d);

    if (descriptor != null) {

    descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);

    Log.i(TAG, "Descriptor write: " + mBluetoothGatt.writeDescriptor(descriptor));

    mBluetoothGatt.setCharacteristicNotification(characteristic , true);

    }

    }

    以上几种操作都需要对应的uuid,uuid的值一般对应的蓝牙工程会在接口文档中给出,我们按照文档进行相关的操作。——一篇在草稿中躺了两年的博客,还是发出来日后温习一遍把。

    展开全文
    weixin_29057695 2021-06-07 11:19:38
  • 304KB weixin_38627769 2021-01-04 01:26:57
  • 蓝牙( Bluetooth® ):是一种无线技术标准,可实现固定设备、移动设备和楼宇个人域网之间的短距离数据交换(使用2.4—2.485GHz的ISM波段的UHF无线电波)。蓝牙技术最初由电信巨头爱立信公司于1994年创制,当时是...

     

    蓝牙( Bluetooth® ):是一种无线技术标准,可实现固定设备、移动设备和楼宇个人域网之间的短距离数据交换(使用2.4—2.485GHz的ISM波段的UHF无线电波)。蓝牙技术最初由电信巨头爱立信公司于1994年创制,当时是作为RS232数据线的替代方案。

    蓝牙的基本功能:

    • 扫描其他蓝牙设备
    • 为可配对蓝牙设备查询蓝牙适配器。
    • 建立RFCOMM通道
    • 通过服务搜索来连接其他设备。
    • 与其他设备进行数据传输。
    • 管理多个连接

    使用蓝牙进行通信的必要四步:

    • 打开蓝牙;
    • 查找附近已配对或可用的设备;
    • 连接设备;
    • 设备间数据交换。

    所有蓝牙API都在android.bluetooth 包下.下面有一些类和接口的摘要,可能需要它们来建立蓝牙连接:

    BluetoothAdapter

    代表本地蓝牙适配器(蓝牙无线电)。BluetoothAdapter是所有蓝牙交互的入口。使用这个你可以发现其他蓝牙设备,查询已配对的设备列表,使用一个已知的MAC地址来实例化一个BluetoothDevice,以及创建一个BluetoothServerSocket来为监听与其他设备的通信。

    BluetoothDevice

    代表一个远程蓝牙设备,使用这个来请求一个与远程设备的BluetoothSocket连接,或者查询关于设备名称、地址、类和连接状态等设备信息。

    BluetoothSocket

    代表一个蓝牙socket的接口(和TCP Socket类似)。这是一个连接点,它允许一个应用与其他蓝牙设备通过InputStream和OutputStream交换数据。

    BluetoothServerSocket

    代表一个开放的服务器socket,它监听接受的请求(与TCP ServerSocket类似)。为了连接两台Android设备,一个设备必须使用这个类开启一个服务器socket。当一个远程蓝牙设备开始一个和该设备的连接请求,BluetoothServerSocket将会返回一个已连接的BluetoothSocket,接受该连接。

    BluetoothClass

    描述一个蓝牙设备的基本特性和性能。这是一个只读的属性集合,它定义了设备的主要和次要的设备类以及它的服务。但是,它没有描述所有的蓝牙配置和设备支持的服务,它只是暗示了设备的类型。

    BluetoothProfile

    一个表示蓝牙配置文件的接口。一个Bluetooth profile是一个基于蓝牙的通信无线接口定义。一个例子是Hands-Free profile。更多的讨论请见Working with Profiles。

    BluetoothHeadset

    提供对移动手机使用的蓝牙耳机的支持。它包含了Headset and Hands-Free (v1.5)配置文件。

    BluetoothA2dp

    定义高品质的音频如何通过蓝牙连接从一个设备传输到另一个设备。”A2DP“是Advanced Audio Distribution Profile的缩写。

    BluetoothHealth

    表示一个Health Device Profile代理,它控制蓝牙服务。

    BluetoothHealthCallback

    一个抽象类,你可以使用它来实现BluetoothHealth的回调函数。你必须扩展这个类并实现回调函数方法来接收应用程序的注册状态改变以及蓝牙串口状态的更新。

    BluetoothHealthAppConfiguration

    表示一个应用程序配置,Bluetooth Health第三方应用程序注册和一个远程Bluetooth Health设备通信。

    BluetoothProfile.ServiceListener

    一个接口,当BluetoothProfile IPC客户端从服务器上建立连接或断开连接时,它负责通知它们(也就是,运行在特性配置的内部服务)

    使用蓝牙需要在配置文件Androidmanifest.xml 中注册两种权限:

    //获取蓝牙适配器
    <uses-permission android:name="android.permission.BLUETOOTH" />
    //蓝牙开启或关闭权限
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

    一、蓝牙搜索

    1.获取蓝牙适配器:

    private BluetoothAdapter mBluetoothAdapter;

    mBluetoothAdapter=BluetoothAdapter.getDefaultAdapter();

    Attention:一定要在真机上调试,AndroidStudio上建立的AVD没有蓝牙模块,可以先进行一下判断:

    if(mBluetoothAdapter == null){
        Toast.makeText(this, "Bluetooth is not supported on the device", Toast.LENGTH_LONG).show();
        return;
    }
    

    2.开启蓝牙:

    //判断蓝牙是否开启,如未开启则强制开启
    if (!mBluetoothAdapter.isEnabled()) {
        //注释掉的方法为提示窗口开启,部分系统(如EMUI)开启蓝牙自带提示窗,故而用这种方法可能会有两个提示窗
        /*Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivity(intent);
        startActivityForResult(intent, REQUEST_CODE_BLUETOOTH_ON);*/
        mBluetoothAdapter.enable();
    }

    蓝牙打开有三种方式:

    • enable()直接强制打开
    • 上面注释掉的方法为提示窗打开,本人亲测华为荣耀8这种方式会出现两次提示窗,故而弃用
    • 打开蓝牙并设置可见时间,具体代码就不贴了,到处都是。

    3.蓝牙广播

    首先使用蓝牙广播要注册蓝牙广播接收者,我们用的是动态注册的方式,即在程序中使用Context.registerReceiver注册。还有一种静态注册方式是在AndroidManifest.xml文件中定义。

    IntentFilter filter=new IntentFilter();
    //发现设备
    filter.addAction(BluetoothDevice.ACTION_FOUND);
    //连接断开
    filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
    //完成扫描
    filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
    //设备连接状态改变
    filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
    registerReceiver(mReceiver,filter);

    注册完之后就是定义蓝牙广播接收者啦:

    // Create a BroadcastReceiver for ACTION_FOUND
    private final BroadcastReceiver mReceiver=new BroadcastReceiver(){
    
        private List<BluetoothDevice> bondedlist = new ArrayList <>();
        private List<BluetoothDevice> surroundList = new ArrayList <>();
    
        @Override
        public void onReceive(Context context, Intent intent) {
            switch (intent.getAction()){
                case BluetoothDevice.ACTION_FOUND:
                    BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                    if(device.getBondState() != BluetoothDevice.BOND_BONDED){
                        if (!surroundList.contains(device)) {
                            surroundList.add(device);
                        }
                        System.out.println("設備列表:"+surroundList);
                        // 为listview设置字符换数组适配器,搜索附近未配对设备
                        SimpleAdapter simAdapter = new SimpleAdapter(
                                MainActivity.this, getDeviceList(surroundList), android.R.layout.simple_list_item_2,
                                new String[] { "name" , "address"}, new int[] {
                                android.R.id.text1, android.R.id.text2});
                        surroundDevices.setAdapter(simAdapter);
                        setListViewHeightBasedOnChildren(surroundDevices);
                    }
    
                    //已配对设备信息
                    bondedlist = new ArrayList <>(mBluetoothAdapter.getBondedDevices());
                    System.out.println("BondedList:"+bondedlist);
                    // 为listview设置字符换数组适配器
                    SimpleAdapter simAdapter = new SimpleAdapter(
                            MainActivity.this, getDeviceList(bondedlist), android.R.layout.simple_list_item_2,
                            new String[] { "name" , "address"}, new int[] {
                            android.R.id.text1, android.R.id.text2});
                    // 为listView绑定适配器
                    bondedDevices.setAdapter(simAdapter);
                    setListViewHeightBasedOnChildren(bondedDevices);
                    break;
                case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
                    statusText.setText("DISCOVERY_FINISHED");
                    break;
                case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
                    BluetoothDevice device1 = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                    Log.e("STATE:","address:"+device1.getAddress()+"\nname:"+device1.getName());
                    switch (device1.getBondState()){
                        case BluetoothDevice.BOND_BONDING:
                            Log.i("STATE", "正在与" + device1.getName() + "__" + device1.getAddress() + "配对");
                            statusText.setText("正在与" + device1.getName() +"进行配对");
                            break;
                        case BluetoothDevice.BOND_BONDED:
                            Log.i("STATE", "与" + device1.getName() + "__" + device1.getAddress() + "完成配对");
                            statusText.setText("与" + device1.getName() + "配对完成");
                            break;
                        case BluetoothDevice.BOND_NONE:
                            Log.i("STATE", "取消与" + device1.getName() + "__" + device1.getAddress() + "配对");
                            statusText.setText("与" + device1.getName() + "配对取消");
                        default:
                            break;
                    }
                    break;
                case  BluetoothDevice.ACTION_ACL_DISCONNECTED:
                    statusText.setText("Disconnected");
                    break;
            }
        }
    };

    4.开始搜索,触发广播

    通过  BluetoothAdapter中startDiscovery( )方法来开始广播。当广播的事件是我们刚刚注册的事件时就会触发广播接收器,并且触发广播接收器中的onReceiver()方法。

    二、蓝牙通信

    蓝牙配对我偷了个懒,直接在系统设置蓝牙里面配对的,因为做这个的时候主要实现的就是通信啦~

    1.蓝牙连接和通信,我把它们写到了一起~

    /**
         * 发送信息到另一个蓝牙设备
         *
         * @param message 信息
         */
        private void sendMessage(String message) {
            if (address==null||"".equals(address)){
                Toast.makeText(MainActivity.this,"未连接任何设备",Toast.LENGTH_SHORT).show();
                return;
            }
            try {
                mBluetoothAdapter.cancelDiscovery();
                Log.e(TAG, "sendMessage: 1");
                if (null == bluetoothDevice) {
                    bluetoothDevice = mBluetoothAdapter.getRemoteDevice(address);
                }
                Log.e(TAG, "sendMessage: 2");
    
                if (bluetoothSocket == null) {
                    bluetoothSocket = bluetoothDevice.createRfcommSocketToServiceRecord(MY_UUID);
                    bluetoothSocket.connect();
                    outputStream = bluetoothSocket.getOutputStream();
                    Message msg1 = new Message();
                    msg1.obj = new String(bluetoothSocket.getRemoteDevice().getName().getBytes("utf-8"), "utf-8");
                    msg1.what = 3;
                    mHandler.sendMessage(msg1);
                }
                Log.e(TAG, "sendMessage: 3");
    
                if (!bluetoothSocket.isConnected()) {
                    resetSocket();
                }
                Log.e(TAG, "sendMessage: 4");
    
                if (outputStream != null) {
                    try {
                        outputStream.write((message.getBytes("utf-8")));
                        Log.e(TAG, "onItemClick: " + mBluetoothAdapter.getName() + ":" + message);
                        Message msg = new Message();
                        msg.obj = new String(message.getBytes("utf-8"), "utf-8");
                        msg.what = 0;
                        mHandler.sendMessage(msg);
                    } catch (Exception e) {
                        resetSocket();
                        sendMessage(message);
                    }
                }
            } catch (Exception e) {
                Toast.makeText(MainActivity.this,"建立连接失败",Toast.LENGTH_SHORT).show();
                e.printStackTrace();
            }
        }
    
    
    
        private void resetSocket() {
            try {
                bluetoothSocket.close();
                bluetoothSocket = bluetoothDevice.createRfcommSocketToServiceRecord(MY_UUID);
                bluetoothSocket.connect();
                outputStream = bluetoothSocket.getOutputStream();
                Message msg1 = new Message();
                msg1.obj = new String(bluetoothSocket.getRemoteDevice().getName().getBytes("utf-8"), "utf-8");
                msg1.what = 3;
                mHandler.sendMessage(msg1);
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }

    2.服务端接收线程:

    private class AcceptThread extends Thread {
            private BluetoothServerSocket mBluetoothServerSocket;
            private BluetoothSocket bluetoothSocket;
            private InputStream is;
            private OutputStream os;
            private boolean isContinue;
    
            AcceptThread() {
                try {
                    mBluetoothServerSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord("bluetooth_socket", MY_UUID);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void run() {
                while (true) {
                    try {
                        if (mBluetoothServerSocket == null) {
                            return;
                        }
                        bluetoothSocket = mBluetoothServerSocket.accept();
                        serverBleName = bluetoothSocket.getRemoteDevice().getName();
                        Log.e(TAG, "run: accept");
                        is = bluetoothSocket.getInputStream();
                        os = bluetoothSocket.getOutputStream();
                        Message msg = new Message();
                        msg.obj = new String(serverBleName.getBytes("utf-8"), "utf-8");
                        msg.what = 4;
                        mHandler.sendMessage(msg);
                        isContinue = true;
                        while (isContinue) {
                            byte[] buffer = new byte[128];
                            int count = is.read(buffer);
                            Message message = new Message();
                            message.obj = new String(buffer, 0, count, "utf-8");
                            message.what = 1;
                            mHandler.sendMessage(message);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                        isContinue = false;
                    } finally {
                        try {
                            if (bluetoothSocket != null) {
                                bluetoothSocket.close();
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
    
                }
            }
        }

    3.更新界面UI线程

    private Handler mHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                //Toast.makeText(MainActivity.this, String.valueOf(msg.obj), Toast.LENGTH_SHORT).show();
                switch (msg.what){
                    //send message
                    case 0:
                        receiveText.append("Me : "+String.valueOf(msg.obj)+"\n");
                        break;
                    //receive message
                    case 1:
                        receiveText.append(serverBleName + " : "+String.valueOf(msg.obj)+"\n");
                        break;
                    //connect server
                    case 3:
                        statusText.setText("Connnected to "+String.valueOf(msg.obj));
                        break;
                    //connectclient
                    case 4:
                        statusText.setText("Connnected to "+String.valueOf(msg.obj));
                        break;
                }
                return false;
            }
        });

    三、结果展示

    下面是各个阶段的截图

                  

       

     

     

     

    展开全文
    u012667477 2018-08-16 11:00:53
  • 3星
    1.39MB ayst_shen 2018-08-23 16:49:59
  • 关注微信公众号(文强的技术小屋),学习更多技术知识,一起遨游知识海洋~ 蓝牙( Bluetooth®):是一种无线技术标准,可实现...行通信,当然并不是每一个蓝牙都可以达到最大值。下面,我们从蓝牙的基本概念...

    关注微信公众号(文强的技术小屋),学习更多技术知识,一起遨游知识海洋~

            蓝牙( Bluetooth®):是一种无线技术标准,可实现固定设备、移动设备和楼宇个人域网之间的短距离数据

    交换(使用2.4—2.485GHz的ISM波段的UHF无线电波)。蓝牙设备最多可以同时和7个其它蓝牙设备建立连接,进

    行通信,当然并不是每一个蓝牙都可以达到最大值。下面,我们从蓝牙的基本概念开始,一步一步开始了解蓝牙。

     

    (尊重劳动成果,转载请注明出处http://blog.csdn.net/qq_25827845/article/details/52997523

     源码下载地址:https://github.com/chaohuangtianjie994/BlueTooth-AutoPair

     

    基本概念: 

             安卓平台提供对蓝牙的通讯栈的支持,允许设别和其他的设备进行无线传输数据。应用程序层通过安卓API来调用蓝牙的相关功

    能,这些API使程序无线连接到蓝牙设备,并拥有P2P或者多端无线连接的特性。

     

    蓝牙的功能:

    1、扫描其他蓝牙设备

    2、为可配对的蓝牙设备查询蓝牙适配器

    3、建立RFCOMM通道

    4、通过服务搜索来链接其他的设备

    5、与其他的设备进行数据传输

    6、管理多个连接



    蓝牙建立连接必须要求:

    1、打开蓝牙

    2、查找附近已配对或可用设备

    3、连接设备

    4、设备间数据交换

     

     

    常用的蓝牙API如下:

     

    BluetoothAdapter

    代表本地蓝牙适配器(蓝牙无线电)。BluetoothAdapter是所有蓝牙交互的入口。使用这个你可以发现其他蓝牙设备,查询已配对的设备列表,使用一个已知的MAC地址来实例化一个BluetoothDevice,以及创建一个BluetoothServerSocket来为监听与其他设备的通信。

     

    BlueDevice代表一个远程蓝牙设备,使用这个来请求一个与远程设备的BluetoothSocket连接,或者查询关于设备名称、地址、类和连接状态等设备信息。
    BluetoothSocket代表一个蓝牙socket的接口(和TCP Socket类似)。这是一个连接点,它允许一个应用与其他蓝牙设备通过InputStream和OutputStream交换数据。
    BluetoothServerSocket代表一个开放的服务器socket,它监听接受的请求(与TCP ServerSocket类似)。为了连接两台Android设备,一个设备必须使用这个类开启一个服务器socket。当一个远程蓝牙设备开始一个和该设备的连接请求,BluetoothServerSocket将会返回一个已连接的BluetoothSocket,接受该连接。

     

    BluetoothAdapter 中常用方法如下所示:

    booleancancelDiscovery()

    Cancel the current device discovery process.

    static booleancheckBluetoothAddress(String address)Validate a String Bluetooth address, such as "00:43:A8:23:10:F0"

    Alphabetic characters must be uppercase to be valid.

    voidcloseProfileProxy(int profile, BluetoothProfile proxy)

    Close the connection of the profile proxy to the Service.

    booleandisable()

    Turn off the local Bluetooth adapter—do not use without explicit user action to turn off Bluetooth.

    booleanenable()

    Turn on the local Bluetooth adapter—do not use without explicit user action to turn on Bluetooth.

    StringgetAddress()

    Returns the hardware address of the local Bluetooth adapter.

    Set<BluetoothDevice>getBondedDevices()

    Return the set of BluetoothDevice objects that are bonded (paired) to the local adapter.

    synchronized static BluetoothAdaptergetDefaultAdapter()

    Get a handle to the default local Bluetooth adapter.

    StringgetName()

    Get the friendly Bluetooth name of the local Bluetooth adapter.

    intgetProfileConnectionState(int profile)

    Get the current connection state of a profile.

    booleangetProfileProxy(Context context, BluetoothProfile.ServiceListener listener, int profile)

    Get the profile proxy object associated with the profile.

    BluetoothDevicegetRemoteDevice(byte[] address)

    Get a BluetoothDevice object for the given Bluetooth hardware address.

    BluetoothDevicegetRemoteDevice(String address)

    Get a BluetoothDevice object for the given Bluetooth hardware address.

    intgetScanMode()

    Get the current Bluetooth scan mode of the local Bluetooth adapter.

    intgetState()

    Get the current state of the local Bluetooth adapter.

    booleanisDiscovering()

    Return true if the local Bluetooth adapter is currently in the device discovery process.

    booleanisEnabled()

    Return true if Bluetooth is currently enabled and ready for use.

    BluetoothServerSocketlistenUsingInsecureRfcommWithServiceRecord(String name, UUID uuid)

    Create a listening, insecure RFCOMM Bluetooth socket with Service Record.

    BluetoothServerSocketlistenUsingRfcommWithServiceRecord(String name, UUID uuid)

    Create a listening, secure RFCOMM Bluetooth socket with Service Record.

    booleansetName(String name)

    Set the friendly Bluetooth name of the local Bluetooth adapter.

    booleanstartDiscovery()

    Start the remote device discovery process.

    booleanstartLeScan(BluetoothAdapter.LeScanCallback callback)

    Starts a scan for Bluetooth LE devices.

    booleanstartLeScan(UUID[] serviceUuids, BluetoothAdapter.LeScanCallback callback)

    Starts a scan for Bluetooth LE devices, looking for devices that advertise given services.

    voidstopLeScan(BluetoothAdapter.LeScanCallback callback)

    Stops an ongoing Bluetooth LE device scan.

     

    BluetoothDevice 中常用方法如下所示:

    BluetoothGattconnectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback)

    Connect to GATT Server hosted by this device.

    booleancreateBond()

    Start the bonding (pairing) process with the remote device.

    BluetoothSocketcreateInsecureRfcommSocketToServiceRecord(UUID uuid)

    Create an RFCOMM BluetoothSocket socket ready to start an insecure outgoing connection to this remote device using SDP lookup of uuid.

    BluetoothSocketcreateRfcommSocketToServiceRecord(UUID uuid)

    Create an RFCOMM BluetoothSocket ready to start a secure outgoing connection to this remote device using SDP lookup of uuid.

    intdescribeContents()

    Describe the kinds of special objects contained in this Parcelable's marshalled representation.

    booleanequals(Object o)

    Compares this instance with the specified object and indicates if they are equal.

    booleanfetchUuidsWithSdp()

    Perform a service discovery on the remote device to get the UUIDs supported.

    StringgetAddress()

    Returns the hardware address of this BluetoothDevice.

    BluetoothClassgetBluetoothClass()

    Get the Bluetooth class of the remote device.

    intgetBondState()

    Get the bond state of the remote device.

    StringgetName()

    Get the friendly Bluetooth name of the remote device.

    intgetType()

    Get the Bluetooth device type of the remote device.

    ParcelUuid[]getUuids()

    Returns the supported features (UUIDs) of the remote device.

    inthashCode()

    Returns an integer hash code for this object.

    booleansetPairingConfirmation(boolean confirm)

    Confirm passkey for PAIRING_VARIANT_PASSKEY_CONFIRMATION pairing.

    booleansetPin(byte[] pin)Set the pin during pairing when the pairing method is PAIRING_VARIANT_PIN

    Requires BLUETOOTH_ADMIN.

    StringtoString()

    Returns a string representation of this BluetoothDevice.

    voidwriteToParcel(Parcel out, int flags)

    Flatten this object in to a Parcel.

     

    BluetoothSocket 中常用方法如下所示:

    voidclose()

    Closes the object and release any system resources it holds.

    voidconnect()

    Attempt to connect to a remote device.

    InputStreamgetInputStream()

    Get the input stream associated with this socket.

    OutputStreamgetOutputStream()

    Get the output stream associated with this socket.

    BluetoothDevicegetRemoteDevice()

    Get the remote device this socket is connecting, or connected, to.

    booleanisConnected()

    Get the connection status of this socket, ie, whether there is an active connection with remote device.

     

    BluetoothServerSocket 中常用方法如下所示:

    BluetoothSocketaccept(int timeout)

    Block until a connection is established, with timeout.

    BluetoothSocketaccept()

    Block until a connection is established.

    voidclose()

    Immediately close this socket, and release all associated resources.

     

     以上四个类贯穿于我们蓝牙通信的全过程,包括蓝牙搜索、配对、连接以及通信。

     

     

    使用蓝牙需要在配置文件Androidmanifest.xml 中注册两种权限:

    <uses-permission android:name="android.permission.BLUETOOTH" />

    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

    其中,权限1在得到默认蓝牙适配器时需要,即BluetoothAdapter  mBluetoothAdapter=BluetoothAdapter.getDefaultAdapter( )

    权限2在mBluetoothAdapter.enable( )或者mBluetoothAdapter.disable( ) 时需要使用到。

     


     

     一、蓝牙搜索功能的实现:

     

    1、得到蓝牙适配器:

    BluetoothAdapter mBluetoothAdapter= BluetoothAdapter.getDefaultAdapter();

    若mBluetoothAdapter为 null,则说明当前手机不支持蓝牙功能(现在几乎所有手机都支持了吧。。。)

     

    2、判断蓝牙是否打开:

    if (!mBluetoothAdapter.isEnabled()) {
           //若没打开则打开蓝牙
           mBluetoothAdapter.enable();
      
    }

    值得注意的是,强制打开蓝牙设备的情况有三种:

    (1)没有任何提示,直接打开了蓝牙。如Nexus 5 Android 4.4.4 手机。

    (2)会弹出提示框,提示安全警告 “ ***应用尝试开启蓝牙”,可以选择“拒绝”或“允许”。大多数手机都是这样的。

    (3)强制打开蓝牙失败,并且没有任何提示。

     

     

    3、注册蓝牙搜索广播接收者:

    (1)Android 的广播机制:

         Adnroid的广播机制(以intent对象的形式广播出去),Android系统广播的时候不会关心你是否收得到消息、只负责广播出去,而

    且广播的对象只是在应用程序中注册了的广播接收器。我们要做的就是自定义广播接收器并将其注册给应用程序,在广播接收器中

    将接收到广播事件作出相应的处理。如果广播的事件并不是我们定义的广播接收器需要的事件类型,一般是会过滤掉不被接收。只

    有当广播事件和我们写的接收器定义的接收的事件类型一致的时候才会触发广播接收器。并且触发广播接收器的onReceive方法。当

    然我们自定义的广播接收器需要接受事件的类型是在XML清单文件的<intent-filter>中自己定义声明的或者自己在程序代码中定义一

    个IntentFilter对象然后通过对象的addAction()方法来自定义接收事件类型。然后我们需要将接收到的事件的处理代码写在onReceive

    方法中。

    (2)注册分为两种:静态注册和动态注册。

    • 静态注册就是在AndroidManifest.xml文件中定义,注册的广播接收器必须继承BroadReceiver
    • 动态注册就是在程序中使用Context.registerReceiver注册。

     

    我们先演示动态注册:

    //注册设备被发现时的广播
    IntentFilter filter=new IntentFilter(BluetoothDevice.ACTION_FOUND);
            registerReceiver(mReceiver,filter);
    //注册一个搜索结束时的广播
    IntentFilter filter2=new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
            registerReceiver(mReceiver,filter2);


    对应的静态注册如下:

    <!-- 广播接收 -->
            <receiver android:name="包名.类名" >
        		<intent-filter android:priority="1000">
            		<action android:name="android.bluetooth.adapter.action.DISCOVERY_FINISHED"/>
            		<action android:name="android.bluetooth.device.action.FOUND" />
        		</intent-filter>
    	</receiver>


    我们如何知道BluetoothAdapter.ACTION_DISCOVERY_FINISHED对应着android.bluetooth.adapter.action.DISCOVERY_FINISHED呢?

    这就要看强大的API了。如图就是一种对应关系:

    此处推荐别人上传的中文API:

                                      点我打开Android中文API

     

     

    4、定义广播接收:

    自定义的广播接收器对象必须要继承BroadcastReceiver,然后重写onReceive方法,处理接收的数据的代码就写在这个方法里面。

    两种方法:

    • 自定义一个类实现BroadcastReceiver抽象类,并且实现其onReceiver(Context context, Intent intent )方法。
    • 直接new BroadcastReceiver()来搞定。

    方法1如下:

    public class BluetoothReceiver extends BroadcastReceiver{
            @Override
    	public void onReceive(Context context, Intent intent) {
    
              ...................
           }
    
    }

    方法2如下:

     //定义广播接收
        private BroadcastReceiver mReceiver=new BroadcastReceiver(){
    
            @Override
            public void onReceive(Context context, Intent intent) {
                  .......................
                       }
        };


     5、开始广播:

           通过  mBluetoothAdapter.startDiscovery( ); 来开始广播。当广播的事件是我们刚刚注册的事件时就会触发广播接收器,并且触

    发广播接收器中的onReceiver()方法。

     

    6、解除注册:

    通过 unregisterReceiver(mReceiver); 来解除刚刚的注册。

     

    至此我们完成了蓝牙通信的第一步:蓝牙搜索。

    下边给出一个完整Demo实例。

    功能为:点击按钮将搜索附近的蓝牙设备,并且判断是否与本设备已经配对,分类显示。

    代码如下:

    mainActivity.java

    package com.example.administrator.myapplication;
    
    import android.bluetooth.BluetoothAdapter;
    import android.bluetooth.BluetoothDevice;
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    import android.content.IntentFilter;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;
    import android.widget.Toast;
    
    public class MainActivity extends AppCompatActivity {
    
        //定义
        private BluetoothAdapter mBluetoothAdapter;
        private TextView text,text2,text3;
        private Button botton;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
    
            text=(TextView) this.findViewById(R.id.textView);  //已配对
            text2= (TextView) this.findViewById(R.id.textView2); //状态信息
            text3= (TextView) this.findViewById(R.id.textView3); //未配对
            botton=(Button) this.findViewById(R.id.button);
    
            mBluetoothAdapter=BluetoothAdapter.getDefaultAdapter();
    
            IntentFilter filter=new IntentFilter(BluetoothDevice.ACTION_FOUND);
            registerReceiver(mReceiver,filter);
            IntentFilter filter2=new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
            registerReceiver(mReceiver,filter2);
    
            botton.setOnClickListener(new View.OnClickListener(){
    
                @Override
                public void onClick(View arg0) {
    
                    if(!mBluetoothAdapter.isEnabled())
                    {
                        mBluetoothAdapter.enable();
    
                    }
    
                        mBluetoothAdapter.startDiscovery();
                        text2.setText("正在搜索...");
    
                }
    
    
            });
    
    
        }
    
    
        public void onDestroy() {
    
            super.onDestroy();
            //解除注册
            unregisterReceiver(mReceiver);
            Log.e("destory","解除注册");
        }
    
    
    
        //定义广播接收
        private BroadcastReceiver mReceiver=new BroadcastReceiver(){
    
    
    
            @Override
            public void onReceive(Context context, Intent intent) {
    
                String action=intent.getAction();
    
                Log.e("ywq", action);
                if(action.equals(BluetoothDevice.ACTION_FOUND))
                {
                    BluetoothDevice device=intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    
                    if(device.getBondState()==BluetoothDevice.BOND_BONDED)
                    {    //显示已配对设备
                        text.append("\n"+device.getName()+"==>"+device.getAddress()+"\n");
                    }else if(device.getBondState()!=BluetoothDevice.BOND_BONDED)
                    {
                        text3.append("\n"+device.getName()+"==>"+device.getAddress()+"\n");
                    }
    
                }else if(action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)){
    
                    text2.setText("搜索完成...");
    
    
                }
    
            }
    
    
        };
    
    }
    


    AndroidManifest.xml代码如下:

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.administrator.myapplication">
    
        <uses-permission android:name="android.permission.BLUETOOTH" />
        <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            <activity android:name=".MainActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    
    </manifest>

     

    布局文件activity_main.xml代码如下:

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context="com.example.administrator.myapplication.MainActivity">
    
        <Button
            android:text="搜索蓝牙"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="19dp"
            android:id="@+id/button"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true" />
    
        <TextView
            android:text="初始状态"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignBottom="@+id/button"
            android:layout_toRightOf="@+id/button"
            android:layout_toEndOf="@+id/button"
            android:layout_marginLeft="45dp"
            android:layout_marginStart="45dp"
            android:layout_marginBottom="14dp"
            android:id="@+id/textView2" />
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/textView"
            android:text="已配对蓝牙设备如下:"
            android:layout_marginLeft="12dp"
            android:layout_marginStart="12dp"
            android:layout_marginTop="53dp"
            android:layout_below="@+id/button"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true" />
    
        <TextView
            android:text="未配对蓝牙设备如下:"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="107dp"
            android:id="@+id/textView3"
            android:layout_below="@+id/textView"
            android:layout_alignLeft="@+id/textView"
            android:layout_alignStart="@+id/textView" />
    
    </RelativeLayout>
    


    程序运行结果如下:

     

     

    二、蓝牙自动配对功能实现:

     

    蓝牙配对是建立连接的基础和前提。为什么不配对便无法建立连接?

            任何无线通信技术都存在被监听和破解的可能,蓝牙SIG为了保证蓝牙通信的安全性,采用认证的方式进行数据交互。同时为

    了保证使用的方便性,以配对的形式完成两个蓝牙设备之间的首次通讯认证,经配对之后,随后的通讯连接就不必每次都要做确

    认。所以认证码的产生是从配对开始的,经过配对,设备之间以PIN码建立约定的link key用于产生初始认证码,以用于以后建立的

    连接。

           所以如果不配对,两个设备之间便无法建立认证关系,无法进行连接及其之后的操作,所以配对在一定程度上保证了蓝牙通信

    的安全,当然这个安全保证机制是比较容易被破解的,因为现在很多个人设备没有人机接口,所以PIN码都是固定的而且大都设置为

    通用的0000或者1234之类的,所以很容易被猜到并进而建立配对和连接。

     

    关于蓝牙的自动配对,大家可以参考我的这篇博客:Android蓝牙自动配对Demo,亲测好使!!!

    这里自夸一下,这篇博客还是受到了大家的一些好评。该自动配对方法,博主在魅蓝、华为、联想、红米以及Nexus手机上都有测

    试过,使用的Android系统包括4.0+和5.0+,所以各位可以仔细阅读该博客。

     

     

     

    三、蓝牙通信的实现:

     

    本文所述的蓝牙通信为:Android 端蓝牙设备与其他蓝牙设备之间的通信

     

       下边讲述  Android手机端蓝牙与Arduino外接蓝牙模块之间进行通信。

     

    (1)Arduino 端蓝牙模块

            蓝牙模块在Arduino 端只是一个串口,将蓝牙模块的Tx、Rx接在Arduino开发板上。

            初始化与Android蓝牙通信的串口,使用串口.read()来读取来自手机蓝牙的信息;使用串口.println(“XXXXXX”)来向手机

    端蓝牙发送信息。

     

    Demo代码如下:

    void setup() {
     Serial.begin(9600);   //初始化原有串口
    
     SerialBT.begin(9600);  //初始化一个串口用来作为蓝牙通信
    
    }
    
    void loop() {
    
     if(SerialBT.available()){  //如果串口可用,即串口中有数据传过来
          char rece=SerialBT.read();   //rece是来自手机蓝牙的信息
          Serial.println("已经接收到来自Android蓝牙的信息"); //这句话将打印在Arduino自带的串口监视窗里
      if(rece=='A'){
           SerialBT.println("这是来自Arduino的信息");   //这句话的内容将显示在Android手机端
        }
      
      }
    
    }


    (2)手机端蓝牙模块:

     Demo运行结果如下所示:

     

     

    Demo代码如下:

    MainActivity.java

    package com.ywq;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.util.UUID;
    import com.example.alltest.R;
    import android.os.AsyncTask;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.app.Activity;
    import android.bluetooth.BluetoothAdapter;
    import android.bluetooth.BluetoothDevice;
    import android.bluetooth.BluetoothSocket;
    import android.util.Log;
    import android.view.Menu;
    import android.view.View;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.TextView;
    import android.widget.Toast;
    
    public class MainActivity extends Activity {
    
    	//定义组件
    	TextView statusLabel; 
    	Button btnConnect,btnSend,btnQuit;
    	EditText etReceived,etSend;
    	
    	//device var
    	private BluetoothAdapter mBluetoothAdapter = null;  
    	  
        private BluetoothSocket btSocket = null;  
    	 
        private OutputStream outStream = null;  
    	      
    	private InputStream inStream = null;  
      
    	//这条是蓝牙串口通用的UUID,不要更改  
    	private static final UUID MY_UUID = 
    			UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");  
    	  
        private static String address = "20:16:07:26:18:46"; // <==要连接的目标蓝牙设备MAC地址  
    
        
        private ReceiveThread rThread=null;  //数据接收线程
        
        //接收到的字符串
        String ReceiveData="";
        
        MyHandler handler;
        
        
          
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		//首先调用初始化函数
    		Init(); 
    		InitBluetooth();
    		
    		handler=new MyHandler();		
    		
    		btnConnect.setOnClickListener(new View.OnClickListener() {  
    			@Override
    			public void onClick(View v) {
    				//判断蓝牙是否打开
    				if(!mBluetoothAdapter.isEnabled())   
    		        {  
    		           mBluetoothAdapter.enable();
    		        }
    		    	 mBluetoothAdapter.startDiscovery();		    	
    	
    			//创建连接	
    			new ConnectTask().execute(address);
    				
    			}
    		});
    		
    		
    		btnQuit.setOnClickListener(new View.OnClickListener() {
    			
    			@Override
    			public void onClick(View v) {
    				// TODO Auto-generated method stub
    				
    				if(btSocket!=null)
    				{
    					try {
    						btSocket.close();
    						btSocket=null;
    						if(rThread!=null)
    						{
    							rThread.join();
    						}	
    						statusLabel.setText("当前连接已断开");
    //						etReceived.setText("");
    					} catch (IOException e) {
    						
    						e.printStackTrace();
    					} catch (InterruptedException e) {
    
    						e.printStackTrace();
    					}
    				}
    				
    				
    				
    			}
    		});
    		
    		btnSend.setOnClickListener(new View.OnClickListener() {
    			
    			@Override
    			public void onClick(View v) {
    				// TODO Auto-generated method stub
    				new SendInfoTask().execute(etSend.getText().toString());
    								
    			}
    		});
    	}
    		
    	public void Init()
    	{
    		statusLabel=(TextView)this.findViewById(R.id.textView1);
    		btnConnect=(Button)this.findViewById(R.id.button1);
    		btnSend=(Button)this.findViewById(R.id.button2);
    		btnQuit=(Button)this.findViewById(R.id.button3);
    		etSend=(EditText)this.findViewById(R.id.editText1);
    		etReceived=(EditText)this.findViewById(R.id.editText2);
    	}
    
    	public void InitBluetooth()
    	{
    		//得到一个蓝牙适配器
    		mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();  
    	        if(mBluetoothAdapter == null)   
    		        {  
    		            Toast.makeText(this, "你的手机不支持蓝牙", Toast.LENGTH_LONG).show();  
    		            finish();  
    		            return;  
    		        }  
    
    	}
    	
    	@Override
    	public boolean onCreateOptionsMenu(Menu menu) {
    		// Inflate the menu; this adds items to the action bar if it is present.
    		getMenuInflater().inflate(R.menu.main, menu);
    		return true;
    	}
    	
    	//连接蓝牙设备的异步任务
    	class ConnectTask extends AsyncTask<String,String,String>
    		{
    			
    
    			@Override
    			protected String doInBackground(String... params) {
    				// TODO Auto-generated method stub
    			 BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(params[0]);  
    				 
    			        try {  
    			  
    			            btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);  
    		  
    			       		  
    			            btSocket.connect();  
    			  
    			            Log.e("error", "ON RESUME: BT connection established, data transfer link open.");  
    			  
    			        } catch (IOException e) {  
    			 
    			           try {  
    			                btSocket.close(); 
    			                return "Socket 创建失败";
    	  
    			            } catch (IOException e2) {  
    			            	
    			               Log .e("error","ON RESUME: Unable to close socket during connection failure", e2);
    			               return "Socket 关闭失败";
    			           }  
    			  
    			        } 
    			        //取消搜索
    			        mBluetoothAdapter.cancelDiscovery();  
    			        
    			        try { 
    			             outStream = btSocket.getOutputStream(); 
    			 			        	       
    			           } catch (IOException e) { 
    			             Log.e("error", "ON RESUME: Output stream creation failed.", e);
    			             return "Socket 流创建失败";
    			           } 
                          
                        
    			        return "蓝牙连接正常,Socket 创建成功";
    			}
    
    			@Override    //这个方法是在主线程中运行的,所以可以更新界面
    			protected void onPostExecute(String result) {
    				// TODO Auto-generated method stub
    				
    				//连接成功则启动监听	
    				rThread=new ReceiveThread();
    				
    				rThread.start();
    				
    				statusLabel.setText(result);
    				
    				super.onPostExecute(result);
    			}
    			
    			
    			
    		}
    	
    	//发送数据到蓝牙设备的异步任务
    	class SendInfoTask extends AsyncTask<String,String,String>
    	{
    
    		@Override
    		protected void onPostExecute(String result) {
    			// TODO Auto-generated method stub
    			super.onPostExecute(result);
    			
    			statusLabel.setText(result);
    			
    			//将发送框清空
    			etSend.setText("");
    		}
    
    		@Override
    		protected String doInBackground(String... arg0) {
    			// TODO Auto-generated method stub
    			
    			if(btSocket==null)
    			{
    				return "还没有创建连接";
    			}
    			
    			if(arg0[0].length()>0)//不是空白串
    			{
    				     //String target=arg0[0];
    			
    				      byte[] msgBuffer = arg0[0].getBytes(); 
    				
    				      try { 
                          //  将msgBuffer中的数据写到outStream对象中
    			          outStream.write(msgBuffer); 
    				 
    				       } catch (IOException e) { 
    				           Log.e("error", "ON RESUME: Exception during write.", e);
    				           return "发送失败";
    			       } 
    
    			}
    			
    			return "发送成功";
    		}
    		
    	}
    	
    	
    	//从蓝牙接收信息的线程
    	class ReceiveThread extends Thread
    	{
    
    		String buffer="";
    		
    		@Override
    		public void run() {
    			
    			while(btSocket!=null )
    			{	   
    				    //定义一个存储空间buff
                        byte[] buff=new byte[1024];
                        try {
                        	inStream = btSocket.getInputStream(); 
                        	System.out.println("waitting for instream"); 
                            inStream.read(buff); //读取数据存储在buff数组中
    //                        System.out.println("buff receive :"+buff.length);
                            
                              
                             processBuffer(buff,1024); 
                             
                            //System.out.println("receive content:"+ReceiveData);
                        } catch (IOException e) {
                            
                            e.printStackTrace();
                        }
    			}		
    		}	
    		
    		private void processBuffer(byte[] buff,int size)
    		{
    			int length=0;
    			for(int i=0;i<size;i++)
    			{
    				if(buff[i]>'\0')
    				{
    					length++;
    				}
    				else
    				{
    					break;
    				}
    			}
    			
    //			System.out.println("receive fragment size:"+length); 
    			
    			byte[] newbuff=new byte[length];  //newbuff字节数组,用于存放真正接收到的数据
    			
    			for(int j=0;j<length;j++)
    			{
    				newbuff[j]=buff[j];
    			}		
    			
    			ReceiveData=ReceiveData+new String(newbuff);
    			Log.e("Data",ReceiveData);
    //			System.out.println("result :"+ReceiveData);
    						Message msg=Message.obtain();
    	        msg.what=1; 
    	        handler.sendMessage(msg);  //发送消息:系统会自动调用handleMessage( )方法来处理消息  
    			
    		}
    		
    	}
    	
    
    	
    	//更新界面的Handler类
    	class MyHandler extends Handler{
    
    		@Override
    		public void handleMessage(Message msg) {
    
    			switch(msg.what){
    			case 1:
    				etReceived.setText(ReceiveData);
    				break;
    			}
    		}	
    	}
    
    	@Override
    	protected void onDestroy() {
    		// TODO Auto-generated method stub
    		super.onDestroy();
    		
    		try {
    			if(rThread!=null)
    			{
    				
    				btSocket.close();
    				btSocket=null;
    				
    				rThread.join();
    			}
    			
    			this.finish();
    			
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}		
    		
    	}
    
    }
    

     

    BluetoothReceiver.java

    package com.ywq.broadcast;
    
    import com.ywq.tools.ClsUtils;
    import android.bluetooth.BluetoothDevice;
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    import android.util.Log;
    
    
    public class BluetoothReceiver extends BroadcastReceiver{
    
    	String pin = "1234";  //此处为你要连接的蓝牙设备的初始密钥,一般为1234或0000
    	public BluetoothReceiver() {
    		
    	}
    
    	//广播接收器,当远程蓝牙设备被发现时,回调函数onReceiver()会被执行 
    	@Override
    	public void onReceive(Context context, Intent intent) {
    		
    		String action = intent.getAction(); //得到action
    		Log.e("action1=", action);
    		BluetoothDevice btDevice=null;  //创建一个蓝牙device对象
    		 // 从Intent中获取设备对象
    		btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 
    		
    		if(BluetoothDevice.ACTION_FOUND.equals(action)){  //发现设备
    			Log.e("发现设备:", "["+btDevice.getName()+"]"+":"+btDevice.getAddress());
    			
    			if(btDevice.getName().contains("HC-05"))//HC-05设备如果有多个,第一个搜到的那个会被尝试。
    			{
    				if (btDevice.getBondState() == BluetoothDevice.BOND_NONE) {  
    					
    					Log.e("ywq", "attemp to bond:"+"["+btDevice.getName()+"]");
    					try {
    						//通过工具类ClsUtils,调用createBond方法
    						ClsUtils.createBond(btDevice.getClass(), btDevice);
    					} catch (Exception e) {
    						// TODO Auto-generated catch block
    						e.printStackTrace();
    					}
                    }
    			}else
    				Log.e("error", "Is faild");
    		}else if(action.equals("android.bluetooth.device.action.PAIRING_REQUEST")) //再次得到的action,会等于PAIRING_REQUEST
    		{
    			Log.e("action2=", action);
    			if(btDevice.getName().contains("HC-05"))
    			{
    				Log.e("here", "OKOKOK");
    				
    				try {
    					
    					//1.确认配对
    					ClsUtils.setPairingConfirmation(btDevice.getClass(), btDevice, true);
    					//2.终止有序广播
    					Log.i("order...", "isOrderedBroadcast:"+isOrderedBroadcast()+",isInitialStickyBroadcast:"+isInitialStickyBroadcast());
    					abortBroadcast();//如果没有将广播终止,则会出现一个一闪而过的配对框。
    					//3.调用setPin方法进行配对...
    					boolean ret = ClsUtils.setPin(btDevice.getClass(), btDevice, pin);
    				
    				} catch (Exception e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    			}else
    				Log.e("提示信息", "这个设备不是目标蓝牙设备");
    			
    		}
    	}
    }


    配对工具类ClsUtils.java如下:

    package com.ywq.tools;
    
    /************************************ 蓝牙配对函数 * **************/
    
    import java.lang.reflect.Method;  
    import java.lang.reflect.Field;  
    import android.bluetooth.BluetoothDevice;  
    import android.util.Log;  
      
    public class ClsUtils   
    {  
        /** 
         * 与设备配对 参考源码:platform/packages/apps/Settings.git 
         * /Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java 
         */  
        static public boolean createBond(Class btClass, BluetoothDevice btDevice)  
        throws Exception  
        {  
            Method createBondMethod = btClass.getMethod("createBond");  
            Boolean returnValue = (Boolean) createBondMethod.invoke(btDevice);  
            return returnValue.booleanValue();  
        }  
       
        /** 
         * 与设备解除配对 参考源码:platform/packages/apps/Settings.git 
         * /Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java 
         */  
        static public boolean removeBond(Class<?> btClass, BluetoothDevice btDevice)  
                throws Exception  
        {  
            Method removeBondMethod = btClass.getMethod("removeBond");  
            Boolean returnValue = (Boolean) removeBondMethod.invoke(btDevice);  
            return returnValue.booleanValue();  
        }  
       
        static public boolean setPin(Class<? extends BluetoothDevice> btClass, BluetoothDevice btDevice,  
                String str) throws Exception  
        {  
            try  
            {  
                Method removeBondMethod = btClass.getDeclaredMethod("setPin",  
                        new Class[]  
                        {byte[].class});  
                Boolean returnValue = (Boolean) removeBondMethod.invoke(btDevice,  
                        new Object[]  
                        {str.getBytes()});  
                Log.e("returnValue", "" + returnValue);  
            }  
            catch (SecurityException e)  
            {  
                // throw new RuntimeException(e.getMessage());  
                e.printStackTrace();  
            }  
            catch (IllegalArgumentException e)  
            {  
                // throw new RuntimeException(e.getMessage());  
                e.printStackTrace();  
            }  
            catch (Exception e)  
            {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
            return true;  
       
        }  
       
        // 取消用户输入  
        static public boolean cancelPairingUserInput(Class<?> btClass,  
                BluetoothDevice device)  throws Exception  
        {  
            Method createBondMethod = btClass.getMethod("cancelPairingUserInput");  
    //        cancelBondProcess(btClass, device);
            Boolean returnValue = (Boolean) createBondMethod.invoke(device);  
            return returnValue.booleanValue();  
        }  
       
        // 取消配对  
        static public boolean cancelBondProcess(Class<?> btClass,  
                BluetoothDevice device)  
       
        throws Exception  
        {  
            Method createBondMethod = btClass.getMethod("cancelBondProcess");  
            Boolean returnValue = (Boolean) createBondMethod.invoke(device);  
            return returnValue.booleanValue();  
        } 
        
        //确认配对
        
        static public void setPairingConfirmation(Class<?> btClass,BluetoothDevice device,boolean isConfirm)throws Exception 
        {
        	Method setPairingConfirmation = btClass.getDeclaredMethod("setPairingConfirmation",boolean.class); 
        	setPairingConfirmation.invoke(device,isConfirm);
        }
        
       
        /** 
         * 
         * @param clsShow 
         */  
        static public void printAllInform(Class clsShow)  
        {  
            try  
            {  
                // 取得所有方法  
                Method[] hideMethod = clsShow.getMethods();  
                int i = 0;  
                for (; i < hideMethod.length; i++)  
                {  
                    Log.e("method name", hideMethod[i].getName() + ";and the i is:"  
                            + i);  
                }
                // 取得所有常量  
                Field[] allFields = clsShow.getFields();  
                for (i = 0; i < allFields.length; i++)  
                {  
                    Log.e("Field name", allFields[i].getName());  
                }
            }  
            catch (SecurityException e)  
            {  
                // throw new RuntimeException(e.getMessage());  
                e.printStackTrace();  
            }  
            catch (IllegalArgumentException e)  
            {  
                // throw new RuntimeException(e.getMessage());  
                e.printStackTrace();  
            }  
            catch (Exception e)  
            {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
        }  
    }  
    


    配置文件AndroidManifest.xml如下:

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.alltest"
        android:versionCode="1"
        android:versionName="1.0" >
    
        <uses-sdk
            android:minSdkVersion="8"
            android:targetSdkVersion="21" />
        
        <!-- 蓝牙使用权限 -->
        <uses-permission android:name="android.permission.BLUETOOTH"/>
        <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
        
        
        <application
            android:allowBackup="true"
            android:icon="@drawable/ic_launcher"
            android:label="@string/app_name"
            android:theme="@style/AppTheme" >
            
            <activity
                android:name="com.ywq.MainActivity"
                android:label="@string/app_name" >
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
            
            <!-- 广播接收 -->
            <receiver android:name="com.ywq.broadcast.BluetoothReceiver" >
        		<intent-filter android:priority="1000">
            		<action android:name="android.bluetooth.device.action.PAIRING_REQUEST"/>
            		<action android:name="android.bluetooth.device.action.FOUND" />
        		</intent-filter>
    		</receiver>
            
        </application>
     
    </manifest>
    


    布局文件如下:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/LinearLayout1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context=".MainActivity" >
           
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >
    
            <Button
                android:id="@+id/button1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="连接" />
            
    
            <TextView
                android:id="@+id/textView1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="当前没有连接任何设备" />
    
        </LinearLayout>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >
    
            <EditText
                android:id="@+id/editText1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:ems="10" >
    
                <requestFocus />
            </EditText>
    
            <Button
                android:id="@+id/button2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="发送" />
    
        </LinearLayout>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >
    
            <TextView
                android:id="@+id/textView2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="接收到的数据" />
    
        </LinearLayout>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >
    
            <EditText
                android:id="@+id/editText2"
                android:layout_width="wrap_content"
                android:layout_height="300dp"
                android:layout_weight="1"
                android:ems="10"
                android:inputType="textMultiLine" />
    
        </LinearLayout>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >
    
            <Button
                android:id="@+id/button3"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="断开连接" />
    
        </LinearLayout>
    
    </LinearLayout>
    


     程序分析:

           程序主要分为:自动配对==>>建立连接==>>开启线程监听是否收到信息==>>向Arduino 端发送信息==>>断开连接


     自动配对:通过mBluetoothAdapter.startDiscovery();来实现。我们来看一下API中对该方法的描述:

     

    public boolean startDiscovery ()

    开始对远程设备进行查找的进程

    它通常牵涉到一个大概需时12秒的查询扫描过程,紧跟着是一个对每个获取到自身蓝牙名称的新设备的页面扫描。

    这是一个异步调用方法:该方法将马上获得返回值,注册ACTION_DISCOVERY_STARTED and

    ACTION_DISCOVERY_FINISHED意图准确地确定该探索是处于开始阶段或者完成阶段。注册ACTION_FOUND以活动远程蓝牙设

    备 已找到的通知。

    设备查找是一个重量级过程。当查找正在进行的时候,用户不能尝试对新的远程蓝牙设备进行连接,同时存在的连接将获得有限制

    的带宽以 及高等待时间。用户可用cencelDiscovery()类来取消正在执行的查找进程。发现的过程不会由活动来进行管理,但是它会

    作为一个系统服务来运 行,因此即使它不能直接请求这样的一个查询动作,也必需取消该搜索进程。

    设备搜寻只寻找已经被连接的远程设备。许多蓝牙设备默认不会被搜寻到,并且需要进入到一个特殊的模式当中。

    如果蓝牙状态不是STATE_ON,这个API将返回false。蓝牙打开后,等待ACTION_STATE_CHANGED更新成STATE_ON。

    需要BLUETOOTH_ADMIN权限。

    返回值

    成功返回true,错误返回false。

     

     由上面我们可以看出,当调用该方法并且发现设备时,将执行我们自定义的广播接收类中的onReceiver()会被执行,实现自动配对具体可以参考:

                                                                        Android蓝牙自动配对Demo,亲测好使!!!

     

    建立连接:使用了一个异步AsyncTask任务。关于AsyncTask的使用,可以参考本博客:

                                                                                    android AsyncTask介绍

     我们首先利用远程蓝牙的mac地址得到了远程蓝牙设备:

    BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);

    其次利用UUID得到了一个BluetoothSocket对象:

    btSocket = device.createRfcommSocketToServiceRecord(MY_UUID); 

    然后调用connect()方法建立了socket连接

    最后通过:

    mBluetoothAdapter.cancelDiscovery();

    来取消了搜索。



    开启线程,监听输入:当socket创建成功后,需要监听输入。

    我们首先通过BluetoothSocket对象得到输入流。

    inStream = btSocket.getInputStream(); 

    通过read()方法来读取来自Arduino端的信息。其中,read()方法是一个可以阻塞的方法。阻塞的意思是,当输入流中没有数据

    传来时,该方法被阻塞,程序不会执行下边的内容,直到有数据传来。

    如果有数据传来,则通过Message和Handler来更新UI,实现数据的显示。

     

    向Arduino发送信息:同样适用了AsyncTask类来实现,  android AsyncTask介绍  。

    当我们点击发送按钮时,首先判断socket是否成功创建,成功则使用输出流发送信息。否则,给出提示。

     

     断开连接:

    首先我们需要通过btSocket.close( )来关闭socket,然后调用线程的join( )来将线程停止。实现了蓝牙之间的断开连接操作。

     

    源码下载地址:https://github.com/chaohuangtianjie994/BlueTooth-AutoPair

     至此,我们学习了Android蓝牙的搜索、配对、连接、通信,对蓝牙开发有了一个较为初步的认识。

     

    如果对你有帮助,记得点赞哦~欢迎大家关注我的博客,可以进群366533258一起交流学习哦~

    关注微信公众号(文强的技术小屋),学习更多技术知识,一起遨游知识海洋~

     

    展开全文
    qq_25827845 2016-11-01 17:23:06
  • Bluetooth Low Energy概述Android 4.3(API=18)介绍了内置平台支持蓝牙低能耗(BLE)的中心作用,并提供了API,应用程序可以使用它来发现设备,查询服务和传输信息。Ble通信适合在传输少量数据的场景下使用。它比Classic...

    Bluetooth Low Energy

    概述

    Android 4.3(API=18)介绍了内置平台支持蓝牙低能耗(BLE)的中心作用,并提供了API,应用程序可以使用它来发现设备,查询服务和传输信息。

    Ble通信适合在传输少量数据的场景下使用。它比Classic蓝牙通信方式的能耗要低很多。如接近传感器、心率监视器和健身设备等都是用Ble方式通信。

    一些名词

    GATT:现在低功耗蓝牙(BLE)连接都是建立在 GATT (Generic Attribute Profile) 协议之上。GATT 是一个在蓝牙连接之上的发送和接收很短的数据段的通用规范,这些很
    短的数据段被称为属性(Attribute)。

    Service:通过GATT连接后,可以得到一个Service的集合,每个Service中包含不同的信息,例如当前设备的信息Device Information Service,进行数据传输的service等。

    Characteristic:在每一个service下面有存在这一组Characteristic特征值,这些特征值是最小的逻辑数据单元,读取特征值数据,或者写数据,实现双向的通信。

    他们的关系如下图:
    这里写图片描述

    BLE设备的通信

    操作步骤

    1,配置权限:

    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
    

    如果你想声明你的app必须在支持BLE的设备上使用进行如下配置

    <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

    在我们的代码中通过以下方法进行判断:

    // Use this check to determine whether BLE is supported on the device. Then
    // you can selectively disable BLE-related features.
    if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
        Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
        finish();
    }

    2,获取BluetoothAdapter

    BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
    或者:
    BluetoothManager manager =(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
    BluetoothAdapter adapter = manager.getAdapter();
    

    如果你的环境是:JELLY_BEAN_MR1及以下的话用第一种,否则使用第二种。

    3,搜索周围Ble设备:

    adapter.statLeScan(BluetoothAdapter.LeScanCallback)
    
    // Device scan callback.
        private BluetoothAdapter.LeScanCallback mLeScanCallback =
                new BluetoothAdapter.LeScanCallback() {
    
            @Override
            public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
                //do some thing... 
            }
        };

    Ble通信使用回调的方式来得到搜索到的蓝牙设备。

    4,连接

    BluetoothGatt mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
    

    通过BluetoothGatt接下来我们可以获得GATT中的Service列表和Service中的Characteristic列表。

    mGattCallback:回调接口BluetoothGattCallback的实例,回调Gatt使用过程中的不同操作,和蓝牙状态的回调。如下为自定义的BluetoothGattCallback:

    // Implements callback methods for GATT events that the app cares about.  For example,
        // connection change and services discovered.
        private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
            @Override
            public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
                String intentAction;
                if (newState == BluetoothProfile.STATE_CONNECTED) {
                    Log.e("message","device connected!");
                    //连接上后去搜寻蓝牙设备的所有Services
                    //mBluetoothGatt.discoverServices();
                } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                    Log.e("message","device disconnected !")
                }
            }
    
            @Override
            public void onServicesDiscovered(BluetoothGatt gatt, int status) {
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    Log.e("message","gatt discovery completed!");
                } else {
                    Log.w(TAG, "onServicesDiscovered received: " + status);
                }
            }
    
            @Override
            public void onCharacteristicRead(BluetoothGatt gatt,
                                             BluetoothGattCharacteristic characteristic,
                                             int status) {
                if (status == BluetoothGatt.GATT_SUCCESS) {
    
                }
            }
    
            @Override
            public void onCharacteristicChanged(BluetoothGatt gatt,
                                                BluetoothGattCharacteristic characteristic) {
    
            }
        };

    BluetoothGattCallback的几个回调函数:

    onConnectionStateChange:当蓝牙的连接状态发生改变时回调,常常在连接成功后,去启动搜索Gatt操作:mBluetoothGatt.discoverServices();
    
    onServicesDiscovered:当搜索Gatt操作完成或失败时回调。完成后就可以和设备进行通信了。
    onCharacteristicRead:当我们读取特征值时回调,对应的还有一个Write方法。
    onCharacteristicChanged:当特征值发生改变时回调。
    

    5,通信

    在我们和Ble设备建立好连接,并且扫描完成Gatt服务之后,我们就可以拿到Services列表和每个Service下的Characteristic列表,我们通信就是通过不同的Characteristic来操作的。
    
    每个Characteristic都有自己的特性:可读,可写,可通知等。
    例如:
    Characteristic.PROPERTY_NOTIFY
    Characteristic.PROPERTY_READ
    Characteristic.PROPERTY_WRITE
    
    //Characteristic.PROPERTY_WRITE用来向设备发送数据的特征值。
    //Characteristic.PROPERTY_NOTIFY用来接收来自蓝牙设备发过来的数据。
    //必须进行如下配置才能接收蓝牙设备发来的数据:
    String CLIENT_CHARACTERISTIC_CONFIG = "00002902-0000-1000-8000-00805f9b34fb";
    BluetoothGattDescriptor descriptor = characteristic.getDescriptor(                    UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));           descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
    gatt.writeDescriptor(descriptor);

    好接下来就可以正常的和我们连接上的蓝牙设备进行通信了,

    下发指令(手机向蓝牙设备发送数据):

    Characteristic characteristic使用特性为PROPERTY_WRITE的作为我们进行写时的特征值,

    byte[] commnd1 = new byte[] { (byte) 0xAA, 0x55, 0x0F, 0x02, (byte) 0x83};
    characteristic = Characteristic.setValue(commnd1);
    gatt.write(characteristic)

    上传指令(手机读取来自蓝牙设备的数据):

    当有数据从蓝牙设备发送来手机时会去回调onCharacteristicChanged(BluetoothGatt gatt,
                                           BluetoothGattCharacteristic characteristic)
    

    byte[] data = characteristic.getValue()即可拿到数据。

    以上为Android 蓝牙Ble通信的过程,总结一下就是,我们通信都是在指定的特征值下进行的,一般对接蓝牙设备时都会拿到它的通信协议,帮助我们解析data字节数据数据。

    点击查看官方提供的Demo的github地址。
    如果对你有些许帮助请添加公众号:
    这里写图片描述

    展开全文
    smallyellower 2017-07-11 11:11:01
  • BangNiBonnie 2016-05-26 17:10:28
  • hwx865 2020-03-29 23:21:22
  • qq_41121080 2021-03-31 15:43:24
  • qq_15784961 2016-03-12 14:53:57
  • weixin_42112685 2021-06-07 18:41:00
  • 209KB weixin_42116734 2021-05-18 03:23:34
  • weixin_39858275 2021-06-08 03:25:47
  • Theo_Yan 2020-04-18 16:06:03
  • lovoo 2016-06-03 08:49:25
  • 50.58MB qq_39867051 2019-03-08 22:06:55
  • weixin_28909441 2021-06-03 12:03:06
  • u011747761 2017-12-19 17:26:40
  • qq_41121080 2020-05-04 16:23:57
  • qq_29794419 2017-05-17 15:43:53
  • wang_k516 2019-07-04 14:12:26
  • xiaolong1126626497 2021-05-25 09:54:21
  • weixin_39599081 2021-01-12 11:31:41
  • hshuaijun55 2019-12-08 20:37:07
  • weixin_34094282 2021-05-28 08:51:07
  • feelinghappy 2020-08-23 14:28:38

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,733
精华内容 1,893
关键字:

蓝牙的写特性通信android