博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android - 传统蓝牙通信聊天
阅读量:4925 次
发布时间:2019-06-11

本文共 7978 字,大约阅读时间需要 26 分钟。

Android -传统蓝牙通信聊天

技术:java+Android4.4+jdk1.8
运行环境:Android4.4、Android7.0

概述

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

详细

原文地址

CSDN

简书

一、准备工作

 

开发环境:

    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//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() == 
null
return
;  
                
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

转载于:https://www.cnblogs.com/mq0036/p/8483076.html

你可能感兴趣的文章
link
查看>>
[翻译]Writing Custom Report Components 编写自定义报表组件
查看>>
20172311 《程序设计与数据结构》第四周学习总结
查看>>
JS中的let变量
查看>>
[ZZ]Ubuntu<-->Windows 远程桌面连接(debian等同)
查看>>
UVa 3602 - DNA Consensus String 水题 难度: 0
查看>>
6-5-树的双亲表示法-树和二叉树-第6章-《数据结构》课本源码-严蔚敏吴伟民版...
查看>>
删除Excel中的打印预览留下的打印线
查看>>
Python根据上下限生成不重复随机数
查看>>
Linux常用命令集
查看>>
验证码 60秒后重新发送
查看>>
JAVA上百实例源码网站
查看>>
AngularJs之ng-repeat的用法
查看>>
转载文件,英语学习
查看>>
Linux系统编程:进程控制
查看>>
leetcode Word Search
查看>>
微服务架构设计
查看>>
有关iOS热更新
查看>>
隐藏17年的Office远程代码执行漏洞(CVE-2017-11882)
查看>>
IE8下的typeof(console.log)为"object"的BUG
查看>>