初步探索Android的蓝牙实现

蓝牙是十世纪的一位国王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)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容

  • Guide to BluetoothSecurity原文 本出版物可免费从以下网址获得:https://doi.o...
    公子小水阅读 7,824评论 0 6
  • 蓝牙 注:本文翻译自https://developer.android.com/guide/topics/conn...
    RxCode阅读 8,576评论 11 99
  • 公司的项目最近需要用到蓝牙开发的相关内容,因此特地查阅了Google官方文档的内容并进行二次整理,希望能对需要学习...
    Chuckiefan阅读 32,374评论 44 123
  • 前言 最近在做Android蓝牙这部分内容,所以查阅了很多相关资料,在此总结一下。 基本概念 Bluetooth是...
    猫疏阅读 14,395评论 7 113
  • 1.简介 通过蓝牙API,可以实现以下内容: 扫描其他蓝牙设备 查询配对蓝牙设备的本地蓝牙适配器 创建RFCOMM...
    justCode_阅读 4,720评论 0 3