原文地址:html
Android 蓝牙开发(一)蓝牙通讯 CSDNjava
Android 蓝牙开发(一)蓝牙通讯 简书android
开发环境:安全
jdk1.8 服务器
Eclipse Luna Service Release 1 (4.4.1)异步
运行环境:socket
华为荣耀6(Android4.4)、华为p9(Android7.0)ide
实现功能:函数
Android 蓝牙开发 (开关蓝牙、搜索设备、蓝牙配对、链接、通讯、断开链接等)。ui
代码包里面,有两个部分,一个是源码,一个是V7支持包。
随着可穿戴设备的流行,研究蓝牙是必不可少的一门技术了。
总结了下蓝牙开发使用的一些东西分享一下。
蓝牙权限
首先须要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() ==
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);
}
}
|
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()等待客户端的链接(阻塞),直到链接成功或失败。
客户端主要用来建立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主动链接服务端。链接过程当中会自动进行配对,须要双方赞成才能够链接成功。
客户端与服务端链接成功后都会调用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
;
}
}
}
|
一、运行,右键项目:Run as -》Android Application (备注:Eclipse须要配置Android开发环境)
二、运行效果以下:
客户端
服务端
出处:http://www.demodashi.com/demo/10676.html