美文网首页
初步探索Android的蓝牙实现

初步探索Android的蓝牙实现

作者: 玩毛线的毛线 | 来源:发表于2017-09-16 16:28 被阅读0次

    蓝牙是十世纪的一位国王Harald Bluetooth的绰号(相传他喜欢吃蓝莓,所以牙齿变成了蓝色),他将纷争不断的丹麦部落统一为一个王国,传说中他还引入了基督教。刚好伟大的Jim Kardach在读一本和蓝牙国王有关的书籍,这位开发了允许电话和计算机通讯的系统的员工,就把他公司(瑞典爱立信,蓝牙创始人)做的统一了各种移动电子设备之间的通讯问题的技术叫做了蓝牙。蓝牙统一了王国,而蓝牙技术统一了移动设备之间的通讯方式。

    好的,介绍扯完了,下面讲一下Android上实现蓝牙的方法:

    1.蓝牙的工作机制(参考博文

    首先两个设备上都要有蓝牙设备或者专业一点叫蓝牙适配器,以手机和电脑为例我画了如下流程图。其次在手机上进行扫描,扫描周围蓝蓝牙设备,先找到手机附近的电脑,然后给它发出一个信号需要进行蓝牙的配对,再次返回一个信号说明手机和电脑已经配对成功了,最后配对成功后可以进行文件传输了。这是一个最基本的一个流程。


    2.与蓝牙相关的类

    网上找了一下,最重要的就是两个类:BluetoothAdapter(可以理解为当前设备)和BluetoothDevice(远程设备).

    3.配置蓝牙的Permission

    在AndroidMenifest.xml里面设置蓝牙使用的权限。

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

    4.搭建蓝牙

    在你的机子和别的机子蓝牙配对之前,你首先要知道,蓝牙在你这台机子上支不支持,能不能用,有没有启用。
    你只要通过静态方法BluetoothAdapter.getDefaultAdapter(),就可以获得BluetoothAdapter的一个实例。看看他有没有空就可以知道你的机子能不能用蓝牙了。

    BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if (mBluetoothAdapter == null) {
        //设备不支持蓝牙
    }
    

    然后再看看你机子有没有启动蓝牙,直接通过你BluetoothAdapter的获得的实例的isEnabled方法就可以了。
    如果蓝牙在这台机子上支持,能用,但是没有启动,就要调用Intent去问问看系统要不要启动蓝牙。

    f (!mBluetoothAdapter.isEnabled()) {
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
    }
    

    然后你的机子会跳到蓝牙的程序,去启动蓝牙。启动之后返回当前Activity,调用Activity的onActivityResult()回调方法,蓝牙程序会返回给你两个整数常量:RESULT_OK和RESULT_CANCELED,看英文想必就大概知道什么意思了吧。
    这两个常量都是Activity里面设定的常量,直接用就可以了。

    protected void onActivityResult(int requestCode, int resultCode, Intent data) {  
            switch (requestCode) {  
                case RESULT_OK:  
                    //用户开启了蓝牙
                    break;  
                case RESULT_CANCELED:  
                    //用户拒绝开启蓝牙
                    break;  
            }  
        }
    

    5.寻找设备

    Using the BluetoothAdapter, you can find remote Bluetooth devices either through device discovery or by querying the list of paired (bonded) devices.

    这是AndroidDeveloper上的原话,就是说要用BluetoothAdapter找其他的蓝牙设备,要通过设备discovery,通过查询配对的设备。
    在讲具体实现之前,讲一下蓝牙配对(Paired)和蓝牙连接(Connected)的区别:
    配对:两个机子只是意识到了彼此的存在,互相有一个共有的密码(用于验证),具备了加密传输的能力。好比一个男单身狗和一个女单身狗相遇搭讪····
    连接:两个机子连在了一起,通过RFCOMM(蓝牙的通讯协议)传输数据。好比刚才的男单身狗和女单身狗在一起了····
    接下来将如何查询设备。

    1. 查询Paired的设备
    //通过mBluetoothAdapter.getBondedDevices()返回一个BluetoothDevice的Set
    Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
    // 如果有设备
    if (pairedDevices.size() > 0) {
        // 循环遍历
        for (BluetoothDevice device : pairedDevices) {
            // 通过Adapter把这些加到ListView中
            mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
        }
    }
    
    1. Discovery设备,通过广播接收者实现
    //创建一个广播接收者用于接收信息
    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            //收到获取的广播的Action,好比你听广播的时候的频率
            String action = intent.getAction();
            //我要的广播的“频率”符合要求,Action符合要求
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                //从Intent中获取BluetoothDevice对象
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                //信息加到ListView中去
                mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
            }
        }
    };
    //注册广播
    IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
    registerReceiver(mReceiver, filter); //不要忘了在onDestroy中销毁广播
    

    另外要让机子可以被扫描,还有一步是通过Intent开启蓝牙的扫描。通过startActivityForResult()方法

    Intent discoverableIntent = new
    Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
    //扫描蓝牙设备的间隔时间,默认120秒,最大3600秒
    discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
    startActivity(discoverableIntent);
    

    然后会显示如下的界面,就是说我们的软件要让你的手机可以被其他设备所扫描300s,你愿意吗?(我愿意~ :-))

    6.连接设备

    这里有一个很重要的类叫BluetoothSocket,其实他和TCP Socket的原理是一样的,一个服务端,一个客户端,客户端触发连接,服务端处理请求。只不过这里的Socket基于蓝牙的RFCOMM协议罢了,这个协议的内容我们不作深入探讨。我们之研究他们如何实现。
    不过这里有一种实现的技术,就是让两个设备都成为服务端和客户端,这样两个设备都可以触发和接收请求啦~
    注意:两个机子配对了,那么Android会自动产生一个对话框(如下图),然后Pair就连在一起了。
    回想一下你用蓝牙的时候的怎么用的,比如电脑说要连手机,然后电脑手机上都会产生一个密码,然后让你确认密码是否一致,你说是的话就他们就连在一起了。
    先说说服务端的实现,大概就是:服务端要监听来自客户端的连接请求(要用到一个BluetoothServerSocket对象),被允许后可以产生一个BluetoothSocket对象。获得BluetoothSocket对象后就可以将BluetoothServerSocket对象给“丢弃”了。
    实现过程如下:

    1. 通过listenUsingRfcommWithServiceRecord(String, UUID)方法获取BluetoothServerSocket 对象。String可以理解为你服务端名字的代号。UUID的相关内容参考以下英文文献,我没有深入研究过,因为在蓝牙协议中要用到这个信息所以有了这个内容:

    About UUID
    A Universally Unique Identifier (UUID) is a standardized 128-bit format for a string ID used to uniquely identify information. The point of a UUID is that it's big enough that you can select any random and it won't clash. In this case, it's used to uniquely identify your application's Bluetooth service. To get a UUID to use with your application, you can use one of the many random UUID generators on the web, then initialize a UUID withfromString(String).

    1. 调用accept()实现监听
    2. 通过close()关闭BluetoothServerSocket
    private class AcceptThread extends Thread {
        private final BluetoothServerSocket mmServerSocket;
     
        public AcceptThread() {
        
            // 建立BluetoothServerSocket的tmp,因为mmServerSocket是final类型的只能赋值一次,
            //所以要这个tmp作中转站
            BluetoothServerSocket tmp = null;
            
            try {
                // MY_UUID 是这款应用的UUID
                tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
            } catch (IOException e) { }
            mmServerSocket = tmp;
        }
     
        public void run() {
            BluetoothSocket socket = null;
            //保持监听知道发生异常
            while (true) {
                try {
                    socket = mmServerSocket.accept();
                } catch (IOException e) {
                    break;
                }
                //连接被允许了
                if (socket != null) {
                    //调用管理这个socket的函数(自己写的)
                    manageConnectedSocket(socket);
                    //关闭ServerSocket
                    mmServerSocket.close();
                    break;
                }
            }
        }
     
        /** 线程关闭mServerSocket也会关闭 */
        public void cancel() {
            try {
                mmServerSocket.close();
            } catch (IOException e) { }
        }
    }
    

    下面讲讲客户端的实现:

    1. 为了从serverSocket上获取BluetoothSocket,你必须要先获取BluetoothDevice。
    2. 通过BluetoothDevice的createRfcommSocketToServiceRecord(UUID)方法获取BluetoothSocket的对象
    3. 通过connect()方法触发连接,conect()方法要在主线程之外实现!!!
    private class ConnectThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final BluetoothDevice mmDevice;
     
        public ConnectThread(BluetoothDevice device) {
            //和Server端一样的原理
            BluetoothSocket tmp = null;
            //获取蓝牙设备
            mmDevice = device;
    
            try {
                // MY_UUID is the app's UUID string, also used by the server code
                tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
            } catch (IOException e) { }
            mmSocket = tmp;
        }
     
        public void run() {
            // 停止搜索设备,会降低连接的速度
            mBluetoothAdapter.cancelDiscovery();
     
            try {
                // 通过Socket建立连接
                //直到他成功或者抛出异常
                mmSocket.connect();
            } catch (IOException connectException) {
                // 不能连接,关闭Socket
                try {
                    mmSocket.close();
                } catch (IOException closeException) { }
                return;
            }
     
            // Do work to manage the connection (in a separate thread)
            manageConnectedSocket(mmSocket);
        }
     
        /** Will cancel an in-progress connection, and close the socket */
        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) { }
        }
    }
    

    7.管理连接

    终于到最后一步了,这里我们通过BluetoothSocket获得InputStream和OutputStream,通过read和write方法就可以做数据传输的事情了!注意:读取这些数据要异步处理(因为read和write方法会阻塞主线程)!

    private class ConnectedThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final InputStream mmInStream;
        private final OutputStream mmOutStream;
        public ConnectedThread(BluetoothSocket socket) {
            mmSocket = socket;
            //这里的tmp和之前的原理一样
            InputStream tmpIn = null;
            OutputStream tmpOut = null;
     
            //获取输出流和输入流
            try {
                tmpIn = socket.getInputStream();
                tmpOut = socket.getOutputStream();
            } catch (IOException e) { }
     
            mmInStream = tmpIn;
            mmOutStream = tmpOut;
        }
     
        public void run() {
            byte[] buffer = new byte[1024];  //存储流数据的载体
            int bytes; //read的bytes数
     
            //保持监听输入流直到出现异常
            while (true) {
                try {
                    //从输入流中读取信息
                    bytes = mmInStream.read(buffer);
                    //利用Handler向主线程发送这些信息
                    mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
                            .sendToTarget();
                } catch (IOException e) {
                    break;
                }
            }
        }
     
        /*从主线程中调用这个方法实现写出*/
        public void write(byte[] bytes) {
            try {
                mmOutStream.write(bytes);
            } catch (IOException e) { }
        }
     
        /*调用这个方法关闭连接*/
        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) { }
        }
    }
    

    (END)

    相关文章

      网友评论

          本文标题:初步探索Android的蓝牙实现

          本文链接:https://www.haomeiwen.com/subject/irxusxtx.html