Android蓝牙遥控器(通过手机蓝牙与蓝牙模块通信)
一.概述
这篇文章是我学习Android开发官网以及网上一些其他文章总结而来,主要就是为了好好研究一下蓝牙开发,看完这篇文章以后,我们就知道了怎样使用蓝牙API完成建立蓝牙连接的必要四步:1.打开蓝牙;2.查找附近已配对或可用的设备;3.连接设备;4.设备间数据交换。在此,做出如下总结:
二.基础
1.API
BluetoothAdapter
代表本地蓝牙适配器(蓝牙无线)。BluetoothAdapter是所有蓝牙交互的入口。使用这个类,你能够发现其他的蓝牙设备,查询已配对设备的列表,使用已知的MAC地址来实例化一个BluetoothDevice对象,并且创建一个BluetoothServerSocket对象来监听与其他设备的通信。
BluetoothDevice
代表一个远程的蓝牙设备。使用这个类通过BluetoothSocket或查询诸如名称、地址、类和配对状态等设备信息来请求跟远程设备的连接。
BluetoothSocket
代表蓝牙socket的接口(类似TCP的Socket)。这是允许一个应用程序跟另一个蓝牙设备通过输入流和输出流进行数据交换的连接点。
BluetoothServerSocket
代表一个打开的监听传入请求的服务接口(类似于TCP的ServerSocket)。为了连接两个Android设备,一个设备必须用这个类打开一个服务接口。当远程蓝牙设备请求跟本设备建立连接请求时,BluetoothServerSocket会在连接被接收时返回一个被连接的BluetoothSocket对象。
BluetoothClass
描述了蓝牙设备的一般性特征和功能。这个类是一个只读的属性集,这些属性定义了设备的主要和次要设备类和服务。但是,这个类并不保证描述了设备所支持的所有的蓝牙配置和服务,但是这种对设备类型的提示是有益的
2.权限
为了在你的应用程序中使用蓝牙功能,至少要声明两个蓝牙权限(BLUETOOTH和BLUETOOTH_ADMIN)中的一个。
为了执行任何蓝牙通信(如请求连接、接收连接和传输数据),你必须申请BLUETOOTH权限。
为了启动设备发现或维护蓝牙设置,你必须申请BLUETOOTH_ADMIN权限。大多数需要这个权限的应用程序,仅仅是为能够发现本地的蓝牙设备。这个权限所授予的其他能力应该不被使用,除非是电源管理的应用程序,它会在依据用户的请求来修改蓝牙设置。注意:如果你使用了BLUETOOTH_ADMIN权限,那么必须要有BLUETOOTH权限。
在AndroidManifest.xml文件中声明如下权限:
/**
*在AndroidManifest中配置权限
*/
3.设置蓝牙
在应用程序能够利用蓝牙通道通信之前,需要确认设备是否支持蓝牙通信,如果支持,要确保它是可用的。如果支持蓝牙,但是被禁用的,那么你要在不离开你的应用程序的情况下,请求用户启用蓝牙功能,这种设置要使用BluetoothAdapter对象
/**
*判断设备是否支持蓝牙,如果支持打开蓝牙
*(1)获得BluetoothAdapter对象
*(2)启用蓝牙功能
*/
private void initBluetooth() {
/*(1)获得BluetoothAdapter对象
BluetoothAdapter对象是所有蓝牙活动都需要的,要获得这个对象,就要调
用静态的getDefaultAdapter()方法。这个方法会返回一个代表设备自己的蓝牙适配器的BluetoothAdapter对象。整
个系统有一个蓝牙适配器,你的应用程序能够使用这个对象来进行交互。如果getDefaultAdapter()方法返回null,那
么该设备不支持蓝牙,你的处理也要在此结束。
*/
//获取本机BluetoothAdapter对象
BluetoothManager bluetoothManager = (BluetoothManager)
getSystemService(Context.BLUETOOTH_SERVICE);
bluetoothAdapter = bluetoothManager.getAdapter();
// 另一种获取获取本机BluetoothAdapter的方法
// bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// 检查设备上是否支持蓝牙设备
if (bluetoothAdapter == null) {
Log.d(TAG, "Device does not support Bluetooth");
Toast.makeText(LoginActivity.this, "Device does not support Bluetooth", Toast.LENGTH_SHORT).show();
// 不支持蓝牙设备,木法,巧妇难为无米之炊,退出!
finish();
return;
}
/*(2)启用蓝牙功能
接下来,你需要确保蓝牙是可用的。调用isEnabled()方法来检查当前蓝牙是否可用。如果这个方法返回false,那么蓝牙是被禁用的。
要申请启用蓝牙功能,就要调用带有ACTION_REQUEST_ENABLE操作意图的startActivityForResult()方法。它会给系统设置发一个启用蓝牙功能的请求(不终止你的应用程序)。
*/
//使用inEnabled方法确认设备蓝牙是否开启
if (!bluetoothAdapter.isEnabled()) {
//打开蓝牙,会提示用户 ; 接下去,在onActivityResult回调判断
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent, requestCodeXXX);
}
}
//启用蓝牙功能时,会显示一个请求用户启用蓝牙功能的对话框
/*
//如果用户点击允许,那么系统会开始启用蓝牙功能,完成启动过程(有可能失败),焦点会返回给你的应用程序。
//传递给startActivityForResult()方法的requestCodeXXX是一个你的应用程序中定义的整数(它必须大于0),系统会把它作为参数返回到你的onActivityResult()回调实现中。
//如果蓝牙功能启用成功,你的Activity会在onActivityResult()回调中接收到RESULT_OK结果,如果蓝牙没有被启动(或者用户响应了拒绝),那么该结果编码是RESULT_CANCELED。
/**
*回调判断
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == requestCodeXXX) { //打开蓝牙
if (resultCode == RESULT_OK) {
//打开蓝牙后的一系列操作
enableOperation();
} else {
Toast.makeText(LoginActivity.this, "You must turn on Bluetooth", Toast.LENGTH_SHORT).show();
finish();
}
}
}
private static final int DISCOVER_REQUEST = 2; //启用可发现机制
//开始搜索广播
String dStarted = BluetoothAdapter.ACTION_DISCOVERY_STARTED;
//搜索结束广播 String dFinished = BluetoothAdapter.ACTION_DISCOVERY_FINISHED;
private boolean startedEndReceiver = false;
private boolean resultReceiver = false;
private List
deviceList = new ArrayList();//发现远程蓝牙设备
private List adpterLeDevices = new ArrayList(); //去重筛选后的周报蓝牙
/**
* 打开蓝牙后的一系列操作
*/
private void enableOperation() {
//启用本设备可发现机制
/*可发现模式:有些时候,我们当前设备是不可被发现的,也就是说即使我们打开了蓝牙其他设备也是无法搜索到的。所以我 们要学学如何启用设备的可发现性。如果要让本地设备可以被其他设备发现,那么就要调用 ACTION_REQUEST_DISCOVERABLE操作意图的startActivityForResult(Intent, int)方法。这个方法会向系统设置发出一个启用 可发现模式的请求(不终止应用程序)。默认情况下,设备的可发现模式会持续120秒。通过给Intent对象添加 EXTRA_DISCOVERABLE_DURATION附加字段,可以定义不同持续时间。应用程序能够设置的最大持续时间是3600秒,0意味着 设备始终是可发现的。任何小于0或大于3600秒的值都会自动的被设为120秒。*/
startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE), DISCOVER_REQUEST);
//获取本机BluetoothAdapter的友好名称以及硬件地址
findLocalMessages();
// 注册“发现远程蓝牙设备”的广播
startDiscovery();
}
/**
* 获取BluetoothAdapter的友好名称以及硬件地址
*/
private void findLocalMessages() {
//用getName和getAddress方法分别访问BluetoothAdapter的友好名称(有用户设置的、用于标识特定设备的任意字符
串)以及硬件地址;
String address = bluetoothAdapter.getAddress(); //获取本地蓝牙地址
String name = bluetoothAdapter.getName(); //获取本地蓝牙名称
int state = bluetoothAdapter.getState(); //获取本地蓝牙适配器当前状态
Log.d(TAG, " 本地蓝牙地址address: " + address + " ; 本地蓝牙名称name: " + name + "; 本地蓝牙适配器当前状
态state: " + state);
}
4.查找设备
/*使用BluetoothAdapter对象,能够通过设备发现或查询已配对的设备列表来找到远程的蓝牙设备。
设备发现是一个扫描过程,该过程搜索本地区域内可用的蓝牙设备,然后请求一些彼此相关的一些信息(这个过程被叫做“发现”、“查询”或“扫描”)。但是,本地区域内的蓝牙设备只有在它们也启用了可发现功能时,才会响应发现请求。如果一个设备是可发现的,那么它会通过共享某些信息(如设备名称、类别和唯一的MAC地址)来响应发现请求。使用这些信息,执/行发现处理的设备能够有选择的初始化跟被发现设备的连接。 */
/*
*发现设备 :
简单的调用startDiscovery()方法就可以开始发现设备。
*该过程是异步的,并且该方法会立即返回一个布尔值来指明发现处理是*否被成功的启动。
*通常发现过程会查询扫描大约12秒,接下来获取扫描发现的每个设备的蓝牙名称。
*/
/**
* 注册“发现远程蓝牙设备”的广播
*/
private void startDiscovery() {
//注册“开始搜索广播”和“搜索结束广播”
registerReceiver(discoverState, new IntentFilter(dStarted));
registerReceiver(discoverState, new IntentFilter(dFinished));
// 注册广播接收器:接收蓝牙发现讯息
registerReceiver(discoveryResult, new IntentFilter(BluetoothDevice.ACTION_FOUND));
//isEnabled()蓝牙是否开启,isDiscovering()判断当前是否正在查找设备
if (bluetoothAdapter.isEnabled() && !bluetoothAdapter.isDiscovering()) {
deviceList.clear();
//adapter.startDiscovery()是一个异步方法,调用后在后台对周围的蓝牙设备进行搜索,然后将搜索结果通过广播的形
//式发送,所以这里要注册广播
bluetoothAdapter.startDiscovery();
}
}
/**
* 发现远程蓝牙设备BroadcastReceiver
*/
BroadcastReceiver discoveryResult = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
resultReceiver = true;
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
//String remoteDeviceName = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
BluetoothDevice remoteDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
deviceList.add(remoteDevice);
Log.d(TAG, "远程设备名字remoteDevice.getName(): " + remoteDevice.getName());
Log.d(TAG, "远程设备地址remoteDevice.getAddress(): " + remoteDevice.getAddress());
}
}
};
/**
* “开始搜索广播”和“搜索结束广播”
*/
BroadcastReceiver discoverState = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
startedEndReceiver = true;
//开始搜索
if (dStarted.equals(intent.getAction())) {
//启动发现过程
tv_lanya.setText("蓝牙搜索中...");
}
//搜索结束
if (dFinished.equals(intent.getAction())) {
//发现过程完成
tv_lanya.setText("蓝牙搜索已完成");
addDevice(deviceList);
// 蓝牙搜索是非常消耗系统资源开销的过程,一旦发现了目标感兴趣的设备,可以考虑关闭扫描。
bluetoothAdapter.cancelDiscovery();
//解除监听( 如果不解除监听,或一直调用addDevice(deviceList) )
destroyBrocast();
}
}
};
/**
* 解除监听
*/
private void destroyBrocast() {
if (startedEndReceiver) {
unregisterReceiver(discoverState);
startedEndReceiver = false;
}
if (resultReceiver) {
unregisterReceiver(discoveryResult);
resultReceiver = false;
}
}
public void addDevice(List leDevices) {
adpterLeDevices.clear();
if (leDevices != null && leDevices.size() > 0) {
for (int i = 0; i < leDevices.size(); i++) {
BluetoothDevice device = leDevices.get(i);
if (!adpterLeDevices.contains(device)) {
adpterLeDevices.add(device);
}
}
}
//打印搜索到的蓝牙信息Log
if (adpterLeDevices != null && adpterLeDevices.size() > 0) {
adapter = new LeDeviceListAdapter(SearchBluetoothActivity.this, adpterLeDevices);
listview.setAdapter(adapter);
adapter.notifyDataSetChanged();
for (int i = 0; i < adpterLeDevices.size(); i++) {
Log.d(TAG, "第 " + i + " 设备" + adpterLeDevices.get(i));
}
}
}
/**
* 获得和当前Android已经配对的蓝牙设备。
*/
private void findpairedDevices() {
// 获得和当前Android已经配对的蓝牙设备。
Set pairedDevices = bluetoothAdapter.getBondedDevices();
if (pairedDevices != null && pairedDevices.size() > 0) {
// 遍历
for (BluetoothDevice device : pairedDevices) {
// 把已经取得配对的蓝牙设备名字和地址打印出来。
Log.d("已经配对的蓝牙设备", device.getName() + " : " + device.getAddress());
}
}
}
三 、连接蓝牙
/**
* 准备连接蓝牙
*/
private void goConnectBluetooth() {
//蓝牙连接是费时操作,所以要单起一个线程
new Thread() {
public void run() {
//获取BluetoothAdapter
BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
//连接默认的水木行蓝牙串口设备
BluetoothDevice device = bluetoothAdapter.getRemoteDevice("20:17:10:24:44:16");
//蓝牙连接并通信
connect(device);
}
;
}.start();
}
/*作为连接的客户端
为了初始化一个与远程设备(持有打开的服务套接字的设备)的连接,首先必须获取个代表远程设备的BluetoothDevice对象。然后使用BluetoothDevice对象来获取一个BluetoothSocket对象,并初始化该连接。 以下是一个基本的连接过程:
1.通过调用BluetoothDevice的createRfcommSocketToServiceRecord(UUID)方法,获得一个BluetoothSocket对象。这个方法会初始化一个连接到BluetoothDevice对象的BluetoothSocket对象。传递给这个方法的UUID参数必须与服务端设备打开BluetoothServerSocket对象时所使用的UUID相匹配。在你的应用程序中简单的使用硬编码进行比对,如果匹配,服务端和客户端代码就可以应用这个BluetoothSocket对象了。
*/
/**
* 蓝牙连接并通信
*
* @param device
*/
protected void connect(BluetoothDevice device) {
// BluetoothSocket socket = null;
try {
/*通过调用connect()方法来初始化连接。在这个调用中,为了找到匹配的UUID,系统会在远程的设备上执行一个SDP
查询。如果查询成功,并且远程设备接收了该连接请求,那么它会在连接期间共享使用RFCOMM通道,并且
connect()方法会返回。这个方法是一个阻塞调用。如果因为某些原因,连接失败或连接超时(大约在12秒之后),就
会抛出一个异常。
因为connect()方法是阻塞调用,这个连接过程始终应该在独立与主Activity线程之外的线程中被执行。
注意:在调用connect()方法时,应该始终确保设备没有正在执行设备发现操作。如果是在发现操作的过程中,那么连
接尝试会明显的变慢,并且更像是要失败的样子。 */
//在建立连接之前要调用cancelDiscovery()方法。在连接之前应该始终调用这个方法,并且不用实际的检查蓝牙发现
处理是否正在运行也是安全的
//(如果想要检查,调用isDiscovering()方法)。
// Create a Socket connection: need the server's UUID number ofregistered
socket = device.createRfcommSocketToServiceRecord(
UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
socket.connect();
//蓝牙是否成功连接
boolean isConnect = socket.isConnected();
//获取蓝牙通信的输入输出流
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
while (true) { //保证socket一直连接
readDataFromServer();
}
} catch (IOException e) {
Log.e("EF-BTBee", ">>", e);
ToastUtil.showToast(BluetoothClientActivity.this, "Socket connection failed");
finish();
return;
} finally {
if (socket != null) {
try {
socket.close();
//finish();
return;
} catch (IOException e) {
}
}
}
}
/**
*读取服务端发来的信息
*/
private void readDataFromServer() {
final byte[] bytes = new byte[1024];
try {
int cnt = inputStream.read(bytes);
String s = new String(bytes, 0, cnt);
if (!StringUtils.isEmpty(s)) {
//把读取到的信息用handler处理,改变界面显示
Message msg = handler.obtainMessage();
msg.what = 0;
msg.obj = s;
handler.sendMessage(msg);
}
} catch (Exception e) {
ToastUtil.showToast(BluetoothClientActivity.this, "socket error");
e.printStackTrace();
}
}
/**
*发送指令到服务端
*/
private void sentMessage(String message) {
if (!StringUtils.isEmpty(message)) {
Log.d(TAG, "发送信息To服务器: " + message);
try {
byte[] bytes = message.getBytes();
outputStream.write(bytes);
outputStream.flush();
} catch (IOException e) {
ToastUtil.showToast(BluetoothClientActivity.this, "socket error");
e.printStackTrace();
}
}
}
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
//获取服务端发来的信息
String result = (String) msg.obj;
//然后自行处理
break;
}
}
};