private
void
search() { BluetoothAdapter adapter =
BluetoothAdapter.getDefaultAdapter();
if (!
adapter.isEnabled()) { adapter.enable(); } Intent enable =
new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); enable.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 3600);
//
3600为蓝牙设备可见时间
startActivity(enable); Intent searchIntent =
new Intent(
this, ComminuteActivity.
class
); startActivity(searchIntent); }
首先,需要获得一个BluetoothAdapter,可以通过getDefaultAdapter()获得系统默认的蓝牙适配器,当然我们也可以自己指定,但这个真心没有必要,至少我是不需要的。然后我们检查手机的蓝牙是否打开,如果没有,通过enable()方法打开。接着我们再设置手机蓝牙设备的可见,可见时间可以自定义。
完成这些必要的设置后,我们就可以正式开始与蓝牙模块进行通信了:
public
class ComminuteActivity
extends
Activity {
private
BluetoothReceiver receiver;
private
BluetoothAdapter bluetoothAdapter;
private List
devices;
private List
deviceList;
private
Bluetooth client;
private
final String lockName = "BOLUTEK"
;
private String message = "000001"
;
private
ListView listView; @Override
public
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState); setContentView(R.layout.search_layout); listView = (ListView)
this
.findViewById(R.id.list); deviceList =
new ArrayList
(); devices =
new ArrayList
(); bluetoothAdapter =
BluetoothAdapter.getDefaultAdapter(); bluetoothAdapter.startDiscovery(); IntentFilter filter =
new
IntentFilter(BluetoothDevice.ACTION_FOUND); receiver =
new
BluetoothReceiver(); registerReceiver(receiver, filter); listView.setOnItemClickListener(
new
AdapterView.OnItemClickListener() { @Override
public
void onItemClick(AdapterView> parent, View view,
int position,
long
id) { setContentView(R.layout.connect_layout); BluetoothDevice device =
deviceList.get(position); client =
new
Bluetooth(device, handler);
try
{ client.connect(message); }
catch
(Exception e) { Log.e("TAG"
, e.toString()); } } }); } @Override
protected
void
onDestroy() { unregisterReceiver(receiver);
super
.onDestroy(); }
private
final Handler handler =
new
Handler() { @Override
public
void
handleMessage(Message msg) {
switch
(msg.what) {
case
Bluetooth.CONNECT_FAILED: Toast.makeText(ComminuteActivity.
this, "连接失败"
, Toast.LENGTH_LONG).show();
try
{ client.connect(message); }
catch
(Exception e) { Log.e("TAG"
, e.toString()); }
break
;
case
Bluetooth.CONNECT_SUCCESS: Toast.makeText(ComminuteActivity.
this, "连接成功"
, Toast.LENGTH_LONG).show();
break
;
case
Bluetooth.READ_FAILED: Toast.makeText(ComminuteActivity.
this, "读取失败"
, Toast.LENGTH_LONG).show();
break
;
case
Bluetooth.WRITE_FAILED: Toast.makeText(ComminuteActivity.
this, "写入失败"
, Toast.LENGTH_LONG).show();
break
;
case
Bluetooth.DATA: Toast.makeText(ComminuteActivity.
this, msg.arg1 + ""
, Toast.LENGTH_LONG).show();
break
; } } };
private
class BluetoothReceiver
extends
BroadcastReceiver { @Override
public
void
onReceive(Context context, Intent intent) { String action =
intent.getAction();
if
(BluetoothDevice.ACTION_FOUND.equals(action)) { BluetoothDevice device =
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if
(isLock(device)) { devices.add(device.getName()); } deviceList.add(device); } showDevices(); } }
private
boolean
isLock(BluetoothDevice device) {
boolean isLockName =
(device.getName()).equals(lockName);
boolean isSingleDevice = devices.indexOf(device.getName()) == -1
;
return isLockName &&
isSingleDevice; }
private
void
showDevices() { ArrayAdapter adapter =
new ArrayAdapter(
this
, android.R.layout.simple_list_item_1, devices); listView.setAdapter(adapter); }}
要想与任何蓝牙模块进行通信,首先得搜到该设备:
private
class BluetoothReceiver
extends
BroadcastReceiver { @Override
public
void
onReceive(Context context, Intent intent) { String action =
intent.getAction();
if
(BluetoothDevice.ACTION_FOUND.equals(action)) { BluetoothDevice device =
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if
(isLock(device)) { devices.add(device.getName()); } deviceList.add(device); } showDevices(); } }
在这之前,我们得先调用一个方法:
bluetoothAdapter.startDiscovery();
startDiscovery()方法是一个异步方法,它会对其他蓝牙设备进行搜索,持续时间为12秒。搜索过程其实是在System Service中进行,我们可以通过cancelDiscovery()方法来停止这个搜索。在系统搜索蓝牙设备的过程中,系统可能会发送以下三个广播:ACTION_DISCOVERY_START(开始搜索),ACTION_DISCOVERY_FINISHED(搜索结束)和ACTION_FOUND(找到设备)。ACTION_FOUND这个才是我们想要的,这个Intent中包含两个extra fields:EXTRA_DEVICE和EXTRA_CLASS,包含的分别是BluetoothDevice和BluetoothClass,BluetoothDevice中的EXTRA_DEVICE就是我们搜索到的设备对象。 确认搜索到设备后,我们可以从得到的BluetoothDevice对象中获得设备的名称和地址。
在android中使用广播需要我们注册,这里也不例外:
IntentFilter filter =
new
IntentFilter(BluetoothDevice.ACTION_FOUND); receiver =
new
BluetoothReceiver(); registerReceiver(receiver, filter);
广播注册后需要我们撤销,这个可以放在这里进行:
@Override
protected
void
onDestroy() { unregisterReceiver(receiver);
super
.onDestroy(); }
这样在Activity结束的时候就会自动撤销该广播,而不需要我们手动执行。
我这里使用一个ListView来显示搜索到的蓝牙设备,但因为需要只限定一个蓝牙设备,所以这里进行了检查,检查该设备是否是我们的目标设备,如果是,就添加。当然,为了防止重复添加,有必要增加这么一句:
boolean isSingleDevice = devices.indexOf(device.getName()) == -1;
搜索到该设备后,我们就要对该设备进行连接。
public
void connect(
final
String message) { Thread thread =
new Thread(
new
Runnable() {
public
void
run() { BluetoothSocket tmp =
null
; Method method;
try
{ method = device.getClass().getMethod("createRfcommSocket",
new Class[]{
int.
class
}); tmp = (BluetoothSocket) method.invoke(device, 1
); }
catch
(Exception e) { setState(CONNECT_FAILED); Log.e("TAG"
, e.toString()); } socket =
tmp;
try
{ socket.connect(); isConnect =
true
; }
catch
(Exception e) { setState(CONNECT_FAILED); Log.e("TAG"
, e.toString()); }
连接设备之前需要UUID,所谓的UUID,就是用来进行配对的,全称是Universally Unique Identifier,是一个128位的字符串ID,用于进行唯一标识。网上的例子,包括谷歌的例子,它们的UUID都是说能用但是我用不了的,都会报出这样的错误:
Service discovery failed
原因可能是作为唯一标识的UUID没有发挥作用,所以,我就利用反射的原理,让设备自己提供UUID。
这个错误在我们把手机既当做客户端有当做服务端的时候,同样也有可能出现,因为作为服务器的时候,我们需要的也是同一个UUID:
mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
作为客户端是这样的:
device.createRfcommSocketToServiceRecord(MY_UUID);
当两个UUID想同时建立Rfcomm的通道时,我们的选择都是在两个线程中分别实现,但是忽略了一件最重要的事情:同一个时间只能充当一个角色!所以,解决这个问题的方法就是在我们相连接的设备上也安装同样的应用程序,谁先发起连接谁就是客户端,但我这里是蓝牙模块啊!!怎么能安装我的应用程序呢!!解决办法就在下面的通信中。
连接设备之前还有一件事必须确保:
bluetoothAdapter.cancelDiscovery();
这是为了停掉搜索设备,否则连接可能会变得非常慢并且容易失败。
有关于Socket的编程都需要我们设置一些状态值来标识通信的状态,以方便我们调错,而且连接应该放在一个线程中进行,要让该线程与我们程序的主线程进行通信,我们需要使用Handle,关于Handle的使用,可以参考我的另一篇博客http://www.cnblogs.com/wenjiang/p/3180324.html,这里不多讲。
在使用Socket中,我注意到一个方法:isConnect(),它返回的是布尔值,但是根本就不需要使用到这个方法,Socket的连接如果没有报错,说明是已经连接上了。
在谷歌提供的例子中,我们可以看到谷歌的程序员的程序水平很高,一些好的编码习惯我们可以学习一下,像是在try..catch中才定义的变量,我们应该在try...catch之前声明一个临时变量,然后再在try...catch后赋值给我们真正要使用的变量。这种做法的好处就是:如果我们直接就是使用真正的变量,当出现异常的时候,该变量的使用就会出现问题,而且很难进行排查,如果是临时变量,我么可以通过检查变量的值来确定是否是赋值时出错。
谷歌的例子中最大的感想就是满满的异常检查,但也是因为这个,导致它的可读性不高。java的异常处理机制有时候对于代码的阅读真的不是一件舒服的事情,能避免就尽量避免。
如果连接没有问题,我们就可以和蓝牙模块进行通信:
if
(isConnect) {
try
{ OutputStream outStream =
socket.getOutputStream(); outStream.write(getHexBytes(message)); }
catch
(IOException e) { setState(WRITE_FAILED); Log.e("TAG"
, e.toString()); }
try
{ InputStream inputStream =
socket.getInputStream();
int
data;
while (
true
) {
try
{ data =
inputStream.read(); Message msg =
handler.obtainMessage(); msg.what =
DATA; msg.arg1 =
data; handler.sendMessage(msg); }
catch
(IOException e) { setState(READ_FAILED); Log.e("TAG"
, e.toString());
break
; } } }
catch
(IOException e) { setState(WRITE_FAILED); Log.e("TAG"
, e.toString()); } }
if (socket !=
null
) {
try
{ socket.close(); }
catch
(IOException e) { Log.e("TAG"
, e.toString()); } } } }
这里包括写入和读取,用法和基本的Socket是一样的,但是写入的时候,需要将字符串转化为16进制:
private
byte
[] getHexBytes(String message) {
int len = message.length() / 2
;
char[] chars =
message.toCharArray(); String[] hexStr =
new
String[len];
byte[] bytes =
new
byte
[len];
for (
int i = 0, j = 0; j < len; i += 2, j++
) { hexStr[j] = "" + chars[i] + chars[i + 1
]; bytes[j] = (
byte) Integer.parseInt(hexStr[j], 16
); }
return
bytes; }
当然,这里只是将手机当做客户端,但是接收蓝牙模块发送过来的信息是没有必要特意创建服务端的,我们只要一个不断监听并读取对方消息的循环就行。
很简单的程序就能实现像是蓝牙串口助手的功能,由于是项目的代码,不能贴完整的代码,但是基本上都在上面了,大家可以参考一下。要想使用蓝牙,相应的权限也是必不可少的: