zoukankan      html  css  js  c++  java
  • Android - 传统蓝牙通信聊天

    Android -传统蓝牙通信聊天

    技术:java+Android4.4+jdk1.8

    概述

    Android 传统蓝牙的使用,包括开关蓝牙、搜索设备、蓝牙连接、通信等。

    详细

    原文地址

    Android 蓝牙开发(一)蓝牙通信 CSDN

    Android 蓝牙开发(一)蓝牙通信 简书

    一、准备工作

    开发环境:

        jdk1.8 

        Eclipse Luna Service Release 1 (4.4.1)

    运行环境:

        华为荣耀6(Android4.4)、华为p9(Android7.0)

    实现功能:

    • Android 蓝牙开发 (开关蓝牙、搜索设备、蓝牙配对、连接、通信、断开连接等)。

    二、代码结构

    代码包里面,有两个部分,一个是源码,一个是V7支持包。

    屏幕快照 2017-07-20 下午5.54.54.png

    三、程序实现-蓝牙通信

    1 蓝牙基本操作

     

    随着可穿戴设备的流行,研究蓝牙是必不可少的一门技术了。

    总结了下蓝牙开发使用的一些东西分享一下。

     

    蓝牙权限

    首先需要AndroidManifest.xml文件中添加操作蓝牙的权限。

    1
    2
    3
    4
    <uses-permissionandroid:name="Android.permission.BLUETOOTH" />
    //允许程序连接到已配对的蓝牙设备。
    <uses-permissionandroid:name="android.permission.BLUETOOTH_ADMIN" />
    //允许程序发现和配对蓝牙设备。

     

    BluetoothAdapter

    操作蓝牙主要用到的类 BluetoothAdapter类,使用时导包
    import android.bluetooth.BluetoothAdapter;
    源码具体位置frameworks/base/core/Java/android/bluetooth/BluetoothAdapter.java

    BluetoothAdapter 代表本地设备的蓝牙适配器。该BluetoothAdapter可以执行基本的蓝牙任务,例如启
    动设备发现,查询配对的设备列表,使用已知的MAC地址实例化一个BluetoothDevice类,并创建一个
    BluetoothServerSocket监听来自其他设备的连接请求。

    获取蓝牙适配器

    1
    BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

    开启蓝牙

    1
    2
    3
    4
    5
    6
    7
    if(!mBluetoothAdapter.isEnabled()){  
    //弹出对话框提示用户是后打开  
    Intent enabler = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);  
    startActivityForResult(enabler, REQUEST_ENABLE);  
          //不做提示,直接打开,不建议用下面的方法,有的手机会有问题。  
          // mBluetoothAdapter.enable();  
    }

     

    获取本地蓝牙信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //获取本机蓝牙名称  
    String name = mBluetoothAdapter.getName();  
    //获取本机蓝牙地址  
    String address = mBluetoothAdapter.getAddress();  
    Log.d(TAG,"bluetooth name ="+name+" address ="+address);  
    //获取已配对蓝牙设备  
    Set<BluetoothDevice> devices = mBluetoothAdapter.getBondedDevices();  
    Log.d(TAG, "bonded device size ="+devices.size());  
    for(BluetoothDevice bonddevice:devices){  
        Log.d(TAG, "bonded device name ="+bonddevice.getName()+" address"+bonddevice.getAddress());  
    }

    搜索设备

    1
    mBluetoothAdapter.startDiscovery();

     

    停止搜索

    1
    mBluetoothAdapter.cancelDiscovery();

    搜索蓝牙设备,该过程是异步的,通过下面注册广播接受者,可以监听是否搜到设备。

    1
    2
    3
    4
    5
    6
    7
    8
    IntentFilter filter = new IntentFilter();  
    //发现设备  
    filter.addAction(BluetoothDevice.ACTION_FOUND);  
    //设备连接状态改变  
    filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);  
    //蓝牙设备状态改变  
    filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);  
    registerReceiver(mBluetoothReceiver, filter);

    监听扫描结果

    通过广播接收者查看扫描到的蓝牙设备,每扫描到一个设备,系统都会发送此广播(BluetoothDevice.ACTION_FOUNDE)。其中参数intent可以获取蓝牙设备BluetoothDevice

    该demo中是连接指定名称的蓝牙设备,BLUETOOTH_NAME为"Galaxy Nexus",如果扫描不到,记得改这个蓝牙名称。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    private BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver(){  
            @Override  
            public void onReceive(Context context, Intent intent) {  
                String action = intent.getAction();  
                Log.d(TAG,"mBluetoothReceiver action ="+action);  
                if(BluetoothDevice.ACTION_FOUND.equals(action)){//每扫描到一个设备,系统都会发送此广播。  
                    //获取蓝牙设备  
                    BluetoothDevice scanDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);  
                    if(scanDevice == null || scanDevice.getName() == nullreturn;  
                    Log.d(TAG, "name="+scanDevice.getName()+"address="+scanDevice.getAddress());  
                    //蓝牙设备名称  
                    String name = scanDevice.getName();  
                    if(name != null && name.equals(BLUETOOTH_NAME)){  
                        mBluetoothAdapter.cancelDiscovery();  
                        //取消扫描  
                        mProgressDialog.setTitle(getResources().getString(R.string.progress_connecting));                   //连接到设备。  
                        mBlthChatUtil.connect(scanDevice);  
                    }  
                }else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)){  
       
                }
            }          
    };

     

     

    设置蓝牙可见性

    有时候扫描不到某设备,这是因为该设备对外不可见或者距离远,需要设备该蓝牙可见,这样该才能被搜索到。

    可见时间默认值为120s,最多可设置300。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    if (mBluetoothAdapter.isEnabled()) {  
        if (mBluetoothAdapter.getScanMode() !=   
                BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {  
            Intent discoverableIntent = new Intent(  
                    BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);  
            discoverableIntent.putExtra(  
                    BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 120);  
            startActivity(discoverableIntent);  
        }  
    }

     

    2 服务端

     

     

    android 蓝牙之间可以通过SDP协议建立连接进行通信,通信方式类似于平常使用socket。

    首先创建BluetoothServerSocket ,BluetoothAdapter中提供了两种创建BluetoothServerSocket 方式,如下图所示为创建安全的RFCOMM Bluetooth socket,该连接是安全的需要进行配对。而通过listenUsingInsecureRfcommWithServiceRecord创建的RFCOMM Bluetooth socket是不安全的,连接时不需要进行配对。

    其中的uuid需要服务器端和客户端进行统一。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    private class AcceptThread extends Thread {  
            // 本地服务器套接字  
            private final BluetoothServerSocket mServerSocket;  
            public AcceptThread() {           
                BluetoothServerSocket tmp = null;  
                // 创建一个新的侦听服务器套接字  
                try {  
                    tmp = mAdapter.listenUsingRfcommWithServiceRecord(  
                            SERVICE_NAME, SERVICE_UUID);  
                    //tmp = mAdapter.listenUsingInsecureRfcommWithServiceRecord(SERVICE_NAME, SERVICE_UUID);  
                catch (IOException e) {  
                    Log.e(TAG, "listen() failed", e);  
                }  
                mServerSocket = tmp;  
            }  
       
            public void run() {  
                BluetoothSocket socket = null;  
                // 循环,直到连接成功  
                while (mState != STATE_CONNECTED) {  
                    try {  
                        // 这是一个阻塞调用 返回成功的连接  
                        // mServerSocket.close()在另一个线程中调用,可以中止该阻塞  
                        socket = mServerSocket.accept();  
                    catch (IOException e) {  
                        Log.e(TAG, "accept() failed", e);  
                        break;  
                    }  
                    // 如果连接被接受  
                    if (socket != null) {  
                        synchronized (BluetoothChatUtil.this) {  
                            switch (mState) {  
                            case STATE_LISTEN:  
                            case STATE_CONNECTING:  
                                // 正常情况。启动ConnectedThread。  
                                connected(socket, socket.getRemoteDevice());  
                                break;  
                            case STATE_NONE:  
                            case STATE_CONNECTED:  
                                // 没有准备或已连接。新连接终止。  
                                try {  
                                    socket.close();  
                                catch (IOException e) {  
                                    Log.e(TAG, "Could not close unwanted socket", e);  
                                }  
                                break;  
                            }  
                        }  
                    }  
                }  
                if (D) Log.i(TAG, "END mAcceptThread");  
            }  
       
            public void cancel() {  
                if (D) Log.d(TAG, "cancel " this);  
                try {  
                    mServerSocket.close();  
                catch (IOException e) {  
                    Log.e(TAG, "close() of server failed", e);  
                }  
            }  
    }

    mServerSocket通过accept()等待客户端的连接(阻塞),直到连接成功或失败。

     

    3 客户端

     

    客户端主要用来创建RFCOMM socket,并连接服务端。

    先扫描周围的蓝牙设备,如果扫描到指定设备则进行连接。mBlthChatUtil.connect(scanDevice)连接到设备,

    连接过程主要在ConnectThread线程中进行,先创建socket,方式有两种,

    如下代码中是安全的(createRfcommSocketToServiceRecord)。另一种不安全连接对应的函数是createInsecureRfcommSocketToServiceRecord

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    private class ConnectThread extends Thread {  
            private BluetoothSocket mmSocket;  
            private final BluetoothDevice mmDevice;  
            public ConnectThread(BluetoothDevice device) {  
                mmDevice = device;  
                BluetoothSocket tmp = null;  
                // 得到一个bluetoothsocket  
                try {  
                    mmSocket = device.createRfcommSocketToServiceRecord  
                            (SERVICE_UUID);  
                catch (IOException e) {  
                    Log.e(TAG, "create() failed", e);  
                    mmSocket = null;  
                }  
            }  
       
            public void run() {  
                Log.i(TAG, "BEGIN mConnectThread");  
                try {   
                    // socket 连接,该调用会阻塞,直到连接成功或失败  
                    mmSocket.connect();  
                catch (IOException e) {  
                    connectionFailed();  
                    try {//关闭这个socket  
                        mmSocket.close();  
                    catch (IOException e2) {  
                        e2.printStackTrace();  
                    }  
                    return;  
                }  
                // 启动连接线程  
                connected(mmSocket, mmDevice);  
            }  
       
            public void cancel() {  
                try {  
                    mmSocket.close();  
                catch (IOException e) {  
                    Log.e(TAG, "close() of connect socket failed", e);  
                }  
            }  
    }

    接着客户端socket主动连接服务端。连接过程中会自动进行配对,需要双方同意才可以连接成功。

     

    4 数据传输

    客户端与服务端连接成功后都会调用connected(mmSocket, mmDevice),创建一个ConnectedThread线程()。

    该线程主要用来接收和发送数据。客户端和服务端处理方式一样。该线程通过socket获得输入输出流。

     

    private  InputStream mmInStream = socket.getInputStream();

    private  OutputStream mmOutStream =socket.getOutputStream();

    发送数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public void write(byte[] buffer) {  
        try {  
            mmOutStream.write(buffer);  
            // 分享发送的信息到Activity  
            mHandler.obtainMessage(MESSAGE_WRITE, -1, -1, buffer)  
                    .sendToTarget();  
        catch (IOException e) {  
            Log.e(TAG, "Exception during write", e);  
        }  
    }

     

    接收数据

     

    线程循环进行接收数据。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public void run() {  
        // 监听输入流  
        while (true) {  
            try {  
                byte[] buffer = new byte[1024];  
                // 读取输入流  
                int bytes = mmInStream.read(buffer);  
                // 发送获得的字节的ui activity  
                Message msg = mHandler.obtainMessage(MESSAGE_READ);  
                Bundle bundle = new Bundle();  
                bundle.putByteArray(READ_MSG, buffer);  
                msg.setData(bundle);  
                mHandler.sendMessage(msg);            
            catch (IOException e) {  
                Log.e(TAG, "disconnected", e);  
                    connectionLost();  
                    break;  
                }  
            }  
    }

    四、运行效果

    1、运行,右键项目:Run as -》Android Application   (备注:Eclipse需要配置Android开发环境)

    2、运行效果如下: 

    客户端 

    1500550538898087682.jpeg

    服务端

    Screenshot_2017-07-20-19-26-57.png

    出处:http://www.demodashi.com/demo/10676.html

  • 相关阅读:
    IE6IE9兼容性问题列表及解决办法总结
    正则表达式笔记:提取C#代码中的中文信息,双引号,尖括号间的信息
    IE6IE9兼容性问题列表及解决办法_补遗漏之一:button的type默认值改变为submit了。
    IE6IE9兼容性问题列表及解决办法_补充之三:Table的Col不再支持style及align,需要到th, td中去设置
    再谈IE的浏览器模式和文档模式
    WebRequest, WebResponse , HttpWebResponse, HttpWebResponse , WebClient笔记
    IE6IE9兼容性问题列表及解决办法_补遗漏之二:CSS区分大小写,Style中height需要加px
    VS2010的智能提示没有了的可能原因
    IE6IE9兼容性问题列表及解决办法_补充之四:HTC (Html Components) 功能逐渐被IE抛弃
    LINQ to XML 笔记,确实比DOM好用,方便。
  • 原文地址:https://www.cnblogs.com/mq0036/p/8483076.html
Copyright © 2011-2022 走看看