Android设备、外接蓝牙设备之间的连接与数据传输

Android开发学习秘籍笔记(十九)

吼,花了2天的时间做出了一个类似于蓝牙串口助手功能的小程序,其实也是实习公司的要求。有一个蓝牙无线蓝牙扫描枪,要求终端可以通过蓝牙连接到该设备,并且蓝牙无线扫描枪扫描二维码或者条形码的时候可以将二维码或者条形码的数据输出到TextView中。

效果:

听了上面的介绍是不是觉得很好做。说明下蓝牙扫描器的功能,有两个常用的模式--普通模式和SPP模式。普通模式的话就是蓝牙连接后,扫描枪就相当于外接的键盘,可以扫码然后将数据输出到EditText(必须活的焦点)。SPP模式则是用于模拟串口通信的,在我看来就是相当于开发者模式。

方案一:

在界面代码上下手脚,是一种投机取巧的方法。不是要显示在TextView上面嘛,不是扫码前要有获得焦点的EditText嘛。那我就在界面代码里面设置这两个控件,但是EditText设置其高度是1dp,除了开发人员自己知道这里有个EditText之外,使用者是不知道这里还有个EditText的,然后你就可以直接将EditText中的内容获取下来,再setText到TextView中去就可以了。

主要代码就只需要在EditText的setOnKeyListener里设置就好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
et.setOnKeyListener(new EditText.OnKeyListener()  
{

@Override

public boolean onKey(View v, int keyCode, KeyEvent event)

{


tv.setText(et.getText());

return false;

}

});

操作很容易,思路也很清晰,简单粗暴!但是问题也很多,万一你一个界面有好多个EditText,比如登陆界面,除了你自己知道但别人看不到的EditText之外还得有两个EditText,一旦其中某一个获得光标,你的扫码枪就失去了意义,还有可能吓使用者一跳,所以这种方案不适合用在需要推送的APP上。

方案二:

蓝牙传输通信,具体就使用到了Android I/O流的传送方式,这种方式就很符合要求,就在你要传输数据的时候把你的数据截下来,然后我在相应的操作。我看了很多博客,大部分博客基本相似。这里放两篇经典的吧。
https://segmentfault.com/a/1190000004899799

http://www.cnblogs.com/wenjiang/p/3200138.html

具体的解释我会在代码里说,光说理论本人菜的抠脚。首先说说布局,超级简单,一个TextView,一个EditText(测试用的,用来做对比的),一个Button(可以根据自己的需求修改)。布局的代码就不贴了!实在不会就下实例看吧。

获取Android扫描到的蓝牙设备

DevicListAcitivity里面需要做的事情就是将Android手机能扫描到的蓝牙设备显示在ListView中,如果你能只需要连接一种设备的话并且知道设备的Mac 地址的话,你可以省略这一步操作,这里当学习使用

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;


public class DeviceListActivity extends Activity {
// 调试用
private static final String TAG = "DeviceListActivity";
private static final boolean D = true;
// 返回时数据标签
public static String EXTRA_DEVICE_ADDRESS = "设备地址";
// 成员域
private BluetoothAdapter mBtAdapter;
private ArrayAdapter<String> mPairedDevicesArrayAdapter;
private ArrayAdapter<String> mNewDevicesArrayAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 创建并显示窗口
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); //设置窗口显示模式为窗口方式
setContentView(R.layout.device_list);
// 设定默认返回值为取消
setResult(Activity.RESULT_CANCELED);
// 设定扫描按键响应
Button scanButton = (Button) findViewById(R.id.button_scan);
scanButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
doDiscovery();
v.setVisibility(View.GONE);
}
});
// 初使化设备存储数组
mPairedDevicesArrayAdapter = new ArrayAdapter<>(this, R.layout.device_name);
mNewDevicesArrayAdapter = new ArrayAdapter<>(this, R.layout.device_name);
// 设置已配队设备列表
ListView pairedListView = (ListView) findViewById(R.id.paired_devices);
pairedListView.setAdapter(mPairedDevicesArrayAdapter);
pairedListView.setOnItemClickListener(mDeviceClickListener);
// 设置新查找设备列表
ListView newDevicesListView = (ListView) findViewById(R.id.new_devices);
newDevicesListView.setAdapter(mNewDevicesArrayAdapter);
newDevicesListView.setOnItemClickListener(mDeviceClickListener);
// 注册接收查找到设备action接收器
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
this.registerReceiver(mReceiver, filter);
// 注册查找结束action接收器
filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
this.registerReceiver(mReceiver, filter);
// 得到本地蓝牙句柄
mBtAdapter = BluetoothAdapter.getDefaultAdapter();
// 得到已配对蓝牙设备列表
//Set<BluetoothDevice> pairedDevices = mBtAdapter.getBondedDevices();
// 添加已配对设备到列表并显示
// if (pairedDevices.size() > 0) {
// findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE);
// for (BluetoothDevice device : pairedDevices) {
// mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
// }
// } else {
// String noDevices = "No devices have been paired";
// mPairedDevicesArrayAdapter.add(noDevices);
// }
}
@Override
protected void onDestroy() {
super.onDestroy();
// 关闭服务查找
if (mBtAdapter != null) {
mBtAdapter.cancelDiscovery();
}
// 注销action接收器
this.unregisterReceiver(mReceiver);
}
public void OnCancel(View v){
finish();
}
/**
* 开始服务和设备查找
*/

private void doDiscovery() {
if (D) Log.d(TAG, "doDiscovery()");
// 在窗口显示查找中信息
setProgressBarIndeterminateVisibility(true);
setTitle("查找设备中...");
// 显示其它设备(未配对设备)列表
findViewById(R.id.title_new_devices).setVisibility(View.VISIBLE);
// 关闭再进行的服务查找
if (mBtAdapter.isDiscovering()) {
mBtAdapter.cancelDiscovery();
}
//并重新开始
mBtAdapter.startDiscovery();
}
// 选择设备响应函数
private OnItemClickListener mDeviceClickListener = new OnItemClickListener() {
public void onItemClick(AdapterView<?> av, View v, int arg2, long arg3) {
// 准备连接设备,关闭服务查找
mBtAdapter.cancelDiscovery();
// 得到mac地址
String info = ((TextView) v).getText().toString();
String address = info.substring(info.length() - 17);
// 设置返回数据
Intent intent = new Intent();
intent.putExtra(EXTRA_DEVICE_ADDRESS, address);
// 设置返回值并结束程序
setResult(Activity.RESULT_OK, intent);
finish();
}
};
// 查找到设备和搜索完成action监听器
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// 查找到设备action
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// 得到蓝牙设备
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// 如果是已配对的则略过,已得到显示,其余的在添加到列表中进行显示
if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}else{ //添加到已配对设备列表
mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
// 搜索完成action
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
setProgressBarIndeterminateVisibility(false);
setTitle("选择要连接的设备");
if (mNewDevicesArrayAdapter.getCount() == 0) {
String noDevices = "没有找到新设备";
mNewDevicesArrayAdapter.add(noDevices);
}
// if(mPairedDevicesArrayAdapter.getCount() > 0)
// findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE);
}
}
};
}

代码贴在上面了,具体的代码解释也写在里面了,其实我开始是不打算做这步操作的,因为需求时只需要连接一个蓝牙扫描枪。但后面觉得还是得扫描的,首先你不一定有设备的Mac地址,再来万一后面要添加需求的话就整个爆炸了。所以有时候不能把程序写的太死。至于如何查找蓝牙设备以及蓝牙设备的状态,上面的代码写的很详细了,再结合我之前推荐的第一篇文章就很好懂了。

蓝牙设备的连接以及数据I/O流的传输

其实在做这步操作的时候,我有点担心实现不了,因为对于Socket来说,你至少得写Socket和ServerSocket这两个类,具体的可以看看我之前对Socket的博客http://blog.csdn.net/cuihaoren01/article/details/45458265,但这里蓝牙扫描枪那边你是不可能编程的的,所以不存在ServerSocket的,好像网上大部分的蓝牙传输都是和Ardunio进行传输的,我也不晓得它可不可以进行编程所以就很担心这样写能不能实现。后面我在GitHub上找到一个蓝牙串口助手demo来试试,发现它可以进行数据传送,但由于它写的代码过于复杂也不是一个框架可以直接利用https://github.com/hzjerry/BluetoothSppPro,所以就没有深究。

没办法了,实践是检验真理的唯一标准,那就只能开始自己测试了,接下来第二篇文章就发挥了作用,可能我不需要实现ServerSocket服务端的编程,在使用蓝牙串口助手测试的时候,你配对上加连接上就可以直接用了,说明我只需要和它(蓝牙枪)能连接上就好了。连接的做法步骤如下:

  1. 首先你得获取到你需要连接的设备。(这里就需要你得设备的Mac地址)
  2. 你需要建立与服务端通信的Socket,看我之前的博客客户端都是通过IP和端口来获得的通信的socket,服务端是通过accept()的方式获取的,而这里这种方式被毙了(感觉也不能说毙了,估计是蓝牙枪那边有一个ServerSocket,去accept(),而客户端有其他的方法拿到通信的Socket。)
  3. socket.connect()
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
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(address);

try{
mBluetoothSocket = mBluetoothDevice.
createRfcommSocketToServiceRecord
(UUID.fromString(MY_UUID));
}catch (IOException e){
Toast.makeText(MainActivity.this, "配对失败,原因是:" + e.getMessage(), Toast.LENGTH_SHORT)
.show();
Log.e("过程", "失败的原因是:" + e.getMessage());
}
//与设备进行连接
try{
mBluetoothSocket.connect();
Toast.makeText(MainActivity.this, "连接"+ mBluetoothDevice.getName()
+ "成功", Toast.LENGTH_SHORT).show();
Log.e("TAGGGAGGAGGHG","连接过程");
} catch (IOException e) {
try{
Toast.makeText(MainActivity.this, "连接失败,原因是:" + e.getMessage(), Toast.LENGTH_SHORT).show();
mBluetoothSocket.close();
mBluetoothSocket = null;
Log.e("连接失败", "连接失败的原因是:" + e.getMessage());
} catch (IOException e1) {
e1.printStackTrace();
}
return;
}

//与设备进行数据传输
try{
is = mBluetoothSocket.getInputStream();
}catch (IOException e){
Toast.makeText(MainActivity.this, "接收数据失败", Toast.LENGTH_SHORT).show();
return;
}

if(ready_receive == false){
ReadThread.start();
ready_receive = true;
}else {
isReceiving = true;
}
}
});

这一部分就是连接蓝牙设备的操作,本来这些操作我是放到一个线程中去执行的,但是很不幸的是会报错,然后我就放到UI线程中去了,结果还过了,按道理耗时操作放到UI线程中不是会爆炸的嘛…暂时放一放这个问题。然后还有一个线程ReadThread

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
Thread ReadThread = new Thread(){
public void run(){
int num = 0;
byte[] buffer = new byte[1024];
byte[] buffer_new = new byte[1024];

int n = 0;
isReceiving= true;
while(true){
try {
while (is.available() == 0){
while (isReceiving == false){}
}
while(true){
num = is.read(buffer);
n = 0;

String s0 = new String(buffer, 0, num);
// fmsg += s0;
for (int i = 0; i < num; i++ ){
if((buffer[i] == 0x0d) && (buffer[i + 1] == 0x0a)){
buffer_new[n] = 0x0a;
i++;
}else{
buffer_new[n] = buffer[i];
}
n++;
}
String s = new String(buffer_new, 0, n);
receive_msg += s;
if (is.available() == 0) break;
}
handler.sendMessage(handler.obtainMessage());
}catch (IOException e){

}
}
}
};

这样就完成了蓝牙串口助手的部分功能,具体的实例见源码下载。

源码下载:

已知要连接设备的address,只需要修改MainActivity中address字符串的值就可以了。
http://download.csdn.net/detail/cuihaoren01/9496677
扫描蓝牙设备,选择需要连接的设备,进行传输。
http://download.csdn.net/detail/cuihaoren01/9496711