之前因为准备考研,所以以前学的很多东西都慢慢忘了,博客也没有时间写。我发现,还是需要将以前学过的知识总结起来,形成一个体系,才不容易忘。因此趁着考完研准备复试的间隙,重新开始写博客,顺带总结一下之前学过的知识,也算是一个整理加总结吧。


这个项目的缘由

这几天女票有个安卓的课设,而她不打算走技术路线,因此这个重任就交到我这个以前学过安卓开发的人的身上。

总体项目概述

这个课设其实很简单:一个用单片机编程的小车,在一个8字形轨道上跑,然后通过蓝牙传回左轮和右轮的速度。手机APP通过蓝牙接收到这个左轮和右轮的速度后,绘制小车运行的轨迹。

向androidtv蓝牙传输_ci


向androidtv蓝牙传输_搜索_02


小车硬件那块原来已经弄好了,我只要负责APP的开发就好了。所以项目主要就分成了蓝牙模块和绘图模块蓝牙模块的话就用Android内置的蓝牙库,而绘图模块我使用SurfaceView来实现。我将这两个模块分成两篇博客来讲,今天就先讲蓝牙模块


蓝牙连接主要流程

  1. 通过蓝牙适配器打开蓝牙。
  2. 搜索(扫描)周围的蓝牙设备并展示出来。
  3. 用户选定所要连接的设备。启动子线程向设备请求连接,使用BluetoothSocket建立连接。
  4. 启动子线程,在线程中通过步骤4中已经与设备建立连接的BluetoothSocket与设备进行通信(数据传输)。
  5. 数据传输结束后,断开连接。通过蓝牙适配器关闭蓝牙。


Created with Raphaël 2.2.0 开始 通过蓝牙适配器打开蓝牙 搜索周围蓝牙设备 向目标设备请求连接 连接成功后使用BluetoothSocket传输数据 断开连接,关闭蓝牙 结束


1.通过蓝牙适配器打开蓝牙
(1)获取权限

首先,要操作蓝牙,先要在AndroidManifest.xml里加入权限

<uses-permissionandroid:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permissionandroid:name="android.permission.BLUETOOTH" />
(2)获取蓝牙适配器
BluetoothAdapter mBtAdapter = BluetoothAdapter.getDefaultAdapter();

通过BluetoothAdapter我们可以对本地的蓝牙设备进行操作,具体方法可以看官方API。

(3)打开蓝牙
if (mBtAdapter == null)      {
	// 设备不支持蓝牙
}
if(!mBtAdapter.isEnabled()){
	// 通过Intent来打开蓝牙,弹出对话框提示用户是后打开
	Intent enabler = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
	startActivityForResult(enabler, REQUEST_ENABLE);
	// 不做提示,强行打开,此方法需要BLUETOOTH_ADMIN权限
	// mBtAdapter.enable();
}

以上执行完后,蓝牙就成功打开了。

2.搜索蓝牙设备

蓝牙打开后,需要搜索周围的蓝牙设备,并将蓝牙设备分为未配对设备和已配对设备,分别在各自的ListView中展示出来。

(1)获取已配对的设备,并展示出来
// 获取已经配对设备的列表
Set<BluetoothDevice> pairedDevices = mBtAdapter.getBondedDevices();
if (pairedDevices.size() > 0) {
	oldDeviceText.setVisibility(View.VISIBLE);
	for (BluetoothDevice device : pairedDevices) {
		oldDevicesAdapter .add(device.getName() + "\n" + device.getAddress());
	}
} 
else {
	oldDevicesAdapter .add("没有已经配对的设备");
}

通过mBtAdapter.getBondedDevices()可以获取本地蓝牙已配对的设备。

(2)开始扫描搜索
mBtAdapter.startDiscovery();
(3)自定义BroadcastReceiver

当搜索到设备后,系统会发送广播BluetoothDevice.ACTION_FOUND,因此我们需要注册相应的BroadcastReceiver来处理搜索到设备的广播。此外,当结束扫描时,系统会发送广播BluetoothAdapter.ACTION_DISCOVERY_FINISHED,我们也需要进行相应的处理。

public class BluetoothReceiver extends BroadcastReceiver {
	@Override
	public void onReceive(Context context, Intent intent) {
		// TODO Auto-generated method stub
		String action = intent.getAction();
		// 搜索到了设备,接收系统发送的广播
		if (BluetoothDevice.ACTION_FOUND.equals(action)) {
			BluetoothDevice device = intent
				.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
			// 如果设备还没有配对,添加到未配对ListView的Adapter中
			if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
				newDevicesAdapter.add(device.getName() + "\n"
					+ device.getAddress());
			} 
		} 
		// 接收到结束扫描的广播,进行以下操作
		else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
			setTitle("选择要连接的设备");
			if (newDevicesAdapter.getCount() == 0) {
				newDevicesAdapter.add("没有查找到设备!");
			}	
		}
	}
}
// 注册接收查找到设备action接收器
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(receiver, filter);
// 注册查找结束action接收器
filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(receiver, filter);

其中BluetoothDevice为蓝牙设备类,这个类保存了对应蓝牙设备的名字、MAC地址以及是否已经与其配对等设备信息。
device.getBondState()可以确定本地蓝牙是否与其配对过
device.getName() 获取设备名
device.getAddress()获取设备的MAC地址
其他的一些方法可以看官方API。

因为扫描蓝牙设备很耗电,所以扫描完成后,需要取消注册BroadcastReceiver和结束扫描。

mBtAdapter.cancelDiscovery();
unregisterReceiver(receiver);

将已配对设备和未配对设备展示出来后,下一步就是建立连接。

3.用户选定所要连接的设备,建立连接

首先Android sdk(2.0以上版本)支持的蓝牙连接是通过BluetoothSocket建立连接,建立连接时需要指定一个UUID
关于UUID:

蓝牙RFCOMM数据(SPP 串口)通信
在蓝牙协议中,UUID被用来标识蓝牙设备所提供的服务,并非是标识蓝牙设备本身哦,一个蓝牙设备可以提供多种服务,比如A2DP(蓝牙音频传输)、HEADFREE(免提)、PBAP(电话本)、SPP(串口通信)等等,每种服务都对应一个UUID,其中在蓝牙协议栈里,这些默认提供的profile是都有对应的UUID的,也就是默认的UUID,比如SPP,00001101-0000-1000-8000-00805F9B34FB就是一个非常 well-known的UUID,基本上所有的蓝牙板不修改的话都是这个值,所以,如果是与一个蓝牙开发板进行串口通信,而蓝牙侧又不是自己可以控制的,就可以试试这个值。
当然,我们进行串口通信的开发,一般都会自己同时开发两侧,因为串口传递的数据就是数据流,没有格式之说,具体发送的数据的意义需要自己来定义,就是说自己定义规则,这就要求一端发送的数据,另一端可以理解。两者的通信基于socket进行实现,所以必须有一端做服务端,另一端做客户端。
引用自:

总的来说,就是蓝牙客户端和蓝牙服务端通过类似于SocketBluetoothSocket来建立连接,而建立连接时两者的UUID必须相同。这个项目中使用的UUID就是引用中所说的默认UUID00001101-0000-1000-8000-00805F9B34FB

(1)获取BluetoothSocket,并使用它向目标设备请求蓝牙连接

因为请求连接是耗时操作,因此必须放在线程中执行。

// device为目标设备对应的BluetoothDevice对象,device通过前面的搜索获得
// 根据UUID新建BluetoothSocket
BluetoothSocket mSocket = device.createRfcommSocketToServiceRecord(uuid);
// 请求连接
mSocket.connect();
4.与设备进行数据传输

请求连接成功后,我们就可以通过BluetoothSocket与设备进行数据传输。传输方式就是普通的数据流方式。
数据传输也是耗时操作,因此必须开启一个子线程用于管理连接和进行数据传输。

(1)获取输入输出流
InputStream inputStream = mSocket.getInputStream();
OutputStream outputStream = mSocket.getOutputStream();
(2)读入输入流,进行数据处理
@Override
public void run() {// 一直等待远端发送数据过来
	byte[] buffer = new byte[1024];
	int bytes;
	while (true) {
		try {
			// 从输入流中读取字节流到buffer数组里面,并统计读取的字节数
			bytes = inputStream.read(buffer);
			//将buffer数组中下标为0到bytes的数据转换成为字符串数据
			String str=new String(buffer,0,bytes);

			String[] tstr = str.trim().split(",");

			if(tstr.length>=2) {
				if (tstr[0].equals("") || tstr[1].equals("") ||
				 tstr[0].equals("-") || tstr[1].equals("-"))
					continue;
				// 左右轮的速度
				int leftVelocity = Integer.parseInt(tstr[0].trim());
				int rightVelocity = Integer.parseInt(tstr[1].trim());
				// 原地静止
				if (leftVelocity < 0 && rightVelocity < 0) {
					isRunningFlag--;
				}
				else {
					if (isRunningFlag < 0) {
						isRunningFlag++;
					}
				}
				if (isRunningFlag >= 0) {
					// 静止
					if (leftVelocity == 0 && rightVelocity == 0) 
						mSynQueue.put(BluetoothMainActivity.NOT_MOVE);
					// 直走
					else if (Math.abs(leftVelocity - rightVelocity) < 20) 
						mSynQueue.put(BluetoothMainActivity.GO_STRAIGHT);
					// 转右
					else if (leftVelocity > rightVelocity)
						mSynQueue.put(BluetoothMainActivity.TURN_RIGHT);
					// 转左
					else if (leftVelocity < rightVelocity)
						mSynQueue.put(BluetoothMainActivity.TURN_LEFT);
				}
			}
		} catch (Exception e) {
			// 连接失去
			connectionLost();
			e.printStackTrace();
			break;
		} 
	}
}

主要就是通过inputStream.read()来读取字节流,然后将字节流转换为String,接着将数据切割为两个int型数据:左轮速度、右轮速度。因为小车的传感器问题,如果我借助小车传回来的具体数值来计算小车的前进距离和方向的话,误差会非常大。因此我只是通过左右轮速度的大小来判断小车是否前进、前进的方向是左转、右转还是直走。至于前进距离、小车速度等具体的处理留到下一篇绘制轨迹的博客中说。
因为蓝牙通信线程要将接收到的数据传送给绘制轨迹线程,这里涉及到线程间的通信以及线程间的同步问题,所以我使用了Java并发包中的同步队列SynchronousQueue来解决这个问题,同步队列相关的内容大家可以看一看其他大佬的博客。

5.断开连接,关闭蓝牙

数据传输完毕后,断开连接,最后关闭蓝牙。

mSocket = null;
if (mBtAdapter != null) {
	mBtAdapter.disable();
}

以上就是Android蓝牙编程的简单使用,总的来说就是:

打开蓝牙,搜索周边蓝牙设备,选择要连接的设备,使用BluetoothSocket进行连接,然后就像网络编程中的socket一样使用BluetoothSocket进行数据传输。数据传输完毕后,断开连接,并关闭蓝牙。

当然还有很多蓝牙的高级用法,因为这个项目中没用上,所以我就不提了,大家有兴趣的话,可以上网再找找其他博客看看。
下一篇博客是介绍项目中关于使用SurfaceView绘制轨迹的部分,欢迎大家继续阅读,谢谢~
下一篇博客:Android绘制小车移动轨迹——蓝牙通信与SurfaceView实践(2)
谢谢大家观看我的博客!若文中有出错的地方,还请大家指出,谢谢~