Android 蓝牙 BR/EDR 的关于串口通信的学习

周末又是偷懒 打了两把DOTA2,想写的系列还没有动笔。这两天狠下功夫把蓝牙研究了个明白,因为同学有需求,他的小车上要用到。搞懂了自然就记下来,网上有用的太少了,做个小整理,免得再出问题。

首先呢,这篇只对BR/EDR类型的蓝牙进行讨论,即普通蓝牙。对于4.0,即BLE以后再说。大致结构如下:Android 蓝牙 BR/EDR 的关于串口通信的学习_第1张图片                             Android 蓝牙 BR/EDR 的关于串口通信的学习_第2张图片

那么开始吧!!

第一步:加入权限,并且检查设备是否支持蓝牙

清单中需加入的两个权限

  
    
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
		if (bluetoothAdapter == null) {
			Toast.makeText(MainActivity.this, "该设备不支持蓝牙", Toast.LENGTH_SHORT)
					.show();
			finish();
		}
调用BluetoothAdapter类(用来管理bluetooth的)getDefaultAdapter可以得到本机蓝牙。加个判定,如果本机不支持蓝牙设备的话


第二步:打开蓝牙和关闭蓝牙

这个嘛,用两个按钮来显示就好。打开蓝牙的话,我用的这种方法,会提示你要不要打开蓝牙(我觉得这样好些)

if (!bluetoothAdapter.isEnabled()) {
				Intent openBluetoothIntent = new Intent(
						BluetoothAdapter.ACTION_REQUEST_ENABLE);
				openBluetoothIntent.putExtra(
						BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 120);
				startActivityForResult(openBluetoothIntent,
						REQUEST_OPEN_BLUETOOTH);
稍稍分析一下,isEnable方法用来判断蓝牙是否打开。Action当然是请求打开了。另外的Extra_discoverable_duration表示可被发现的时间长。记得定义一个int型的request_code

private static final int REQUEST_OPEN_BLUETOOTH = 1;
关闭的就简单多了,一句话搞定

bluetoothAdapter.disable();


第三步:查找设备并进行配对

这里要用到listView来表示存放查找到的设备,涉及到listView的知识这里不做解释。可以查阅相关资料。

首先先注册广播,注册好之后就可以查找了

IntentFilter filter = new IntentFilter();
				filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
				filter.addAction(BluetoothDevice.ACTION_FOUND);
				registerReceiver(broadcastReceiver, filter);
				bluetoothAdapter.startDiscovery();
 
  
				openSearchDialog();
这边openSearchDialog方法是我用来查找的时候,显示一个等待的提示框

private void openSearchDialog() {
		dialog = new AlertDialog.Builder(MainActivity.this).create();
		dialog.show();
		dialog.setContentView(R.layout.search_dialog);
		dialog.setCancelable(false);
	}
下面的就是自定义的BroadcastReceiver类
public class BlueToothBroadcastReceiver extends BroadcastReceiver {

	@Override
	public void onReceive(Context context, Intent intent) {
		String action = intent.getAction();
		switch (action) {
		case BluetoothDevice.ACTION_FOUND:
			BluetoothDevice device = intent
					.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
			((MainActivity) context).getDeviceItems().add(device);
			((MainActivity) context).haveFoundDevice();
			break;
		case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
			context.unregisterReceiver(this);
			((MainActivity) context).closeSearchDialog();
			break;
		default:
			break;
		}
	}

}
两个行为,找到设备时以及搜索终了时都会受到广播。

发现了自然就将device添加到list里去(我在MainActivity里使用了get方法来获得这个list对象)

private List deviceItems;
另外的haveFoundDevice方法是用来判定我的另一个线程进行的,涉及到异步进程的知识。这边也不作讨论。直接上代码块。

public void haveFoundDevice() {
		mThread = new NewThread();
		mThread.start();
	}
启动线程

class NewThread extends Thread implements Runnable {
		@Override
		public void run() {
			super.run();
			try {
				adapter = new BlueToothDeviceAdapter(MainActivity.this,
						R.layout.listview_bluetooth_devices, deviceItems);
				Message message = new Message();
				message.what = UPDATA_LISTVIEW_UI;
				mHandler.sendMessage(message);
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
	}
内部类

mHandler = new Handler() {
			@Override
			public void handleMessage(Message msg) {
				super.handleMessage(msg);
				switch (msg.what) {
				case UPDATA_LISTVIEW_UI:
					listView.setAdapter(adapter);
					break;
				case START_CONNECT_DEVICE:
					btnCarUp.setVisibility(View.VISIBLE);
					break;
				default:
					break;
				}
			}
		};
Handler的处理

接下来,就是一个ListViewAdapter来存放设备名和设备地址

先上代码,再细细说。
public class BlueToothDeviceAdapter extends ArrayAdapter {
	private Context mContext;
	private int resourceId;
	private BluetoothDevice device;
	private ViewHolder holder;

	public BlueToothDeviceAdapter(Context context, int resource,
			List devices) {
		super(context, resource, devices);
		this.mContext = context;
		this.resourceId = resource;
	}

	@SuppressLint("NewApi")
	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		device = getItem(position);
		View view;

		if (convertView == null) {
			view = LayoutInflater.from(mContext).inflate(resourceId, null);
			holder = new ViewHolder();
			holder.deviceName = (TextView) view.findViewById(R.id.device_name);
			holder.deviceAddress = (TextView) view
					.findViewById(R.id.device_address);
			holder.btnConnect = (Button) view.findViewById(R.id.btn_listView);
			holder.isPair = (TextView) view.findViewById(R.id.textView_isPair);
			view.setTag(holder);
		} else {
			view = convertView;
			holder = (ViewHolder) view.getTag();
		}
		if (Integer.toHexString(device.getBluetoothClass().getDeviceClass())
				.length() < 4) {
			Log.i("device",
					"0"
							+ Integer.toHexString(device.getBluetoothClass()
									.getDeviceClass()));
		} else {
			Log.i("device", Integer.toHexString(device.getBluetoothClass()
					.getDeviceClass()));
		}
		holder.deviceName.setText(device.getName());
		holder.deviceAddress.setText(device.getAddress());
		switch (device.getBondState()) {
		case BluetoothDevice.BOND_BONDED:
			holder.isPair.setText("已配对");
			holder.btnConnect.setText("连接");
			break;
		case BluetoothDevice.BOND_NONE:
			holder.isPair.setText("未配对");
			break;
		case BluetoothDevice.BOND_BONDING:
			holder.isPair.setText("配对中");
			break;
		default:
			break;
		}

		holder.btnConnect.setTag(position);
		holder.btnConnect.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				BluetoothDevice device = getItem((int) v.getTag());
				switch (device.getBondState()) {
				case BluetoothDevice.BOND_NONE:
					try {
						Method createBondMethod = BluetoothDevice.class
								.getMethod("createBond");
						createBondMethod.invoke(device);
					} catch (Exception e) {
						Toast.makeText(mContext, "配对失败", Toast.LENGTH_SHORT)
								.show();
					}
					break;
				case BluetoothDevice.BOND_BONDED:
					((MainActivity) mContext).startConnectThread((int)v.getTag());
					break;
				default:
					break;
				}
			}
		});
		return view;
	}

	class ViewHolder {
		TextView deviceName;
		TextView deviceAddress;
		Button btnConnect;
		TextView isPair;
	}
}
关于ListViewAdapter的我就不说了,之后更新的文章会详细讲解。这里,运用device的getName方法,getAddress方法可以分别得到查找到的设备的昵称和MAC地址。并且,我加入了一个判定。判断当前设备的配对状态,未配对的话,按下配对按钮就可以配对
case BluetoothDevice.BOND_NONE:
					try {
						Method createBondMethod = BluetoothDevice.class
								.getMethod("createBond");
						createBondMethod.invoke(device);
					} catch (Exception e) {
						Toast.makeText(mContext, "配对失败", Toast.LENGTH_SHORT)
								.show();
					}
					break;
这边用到了反射机制来进行配对

接着,我在主类里注册了一个新的广播,用来监听BondState,这样可以根据配对的状态,动态改变按钮上的文字。未配对就显示配对,配对过的就显示连接。嘿嘿嘿,这边和上边的广播一样的。不多解释,上代码

IntentFilter filter2 = new IntentFilter();
				filter2.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
				registerReceiver(listenBroadcastReceiver, filter2);

public class ListenStateBroadcastReceiver extends BroadcastReceiver {

	@Override
	public void onReceive(Context context, Intent intent) {
		String action = intent.getAction();
		if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
			((MainActivity) context).getAdapter().notifyDataSetChanged();
		}
	}

}



第四步:连接设备

这里用到了一个UUID码,用来辨识设备提供的UUID服务的。当然了,我们这边只针对蓝牙串口这一种情况讨论,别的UUID码可以上网找哦~

String SPP_UUID = "00001101-0000-1000-8000-00805F9B34FB";这个呢,就是串口蓝牙服务的UUID

public void run() {
			super.run();
			final String SPP_UUID = "00001101-0000-1000-8000-00805F9B34FB";
			UUID uuid = UUID.fromString(SPP_UUID);
			try {
				BluetoothSocket socket = deviceItems.get(position)
						.createRfcommSocketToServiceRecord(uuid);
				socket.connect();
				mmOutputStream = socket.getOutputStream();
				Message message = new Message();
				message.what = START_CONNECT_DEVICE;
				mHandler.sendMessage(message);
			} catch (Exception e) {

			}
		}
这边说明一下,socket是一个像插座一样的东西,蓝牙之间连接传数据就要靠它。通过指定的UUID来创建Rfcomm协议,这样得到了一个socket实例。接着,就可以连接了呢!!用connect方法连接。还有一个注意点就是,因为这是会阻塞的,所以也要用子线程来写!

提示:因为这里我使用的是蓝牙串口,所有只用到了客户端。如果是两个手机蓝牙之间之类的话,还要有服务端哦~~


第五步:发送数据

这边一开始不是太理解,下面就是我自己的理解。非术语,可能还是有点形象的。首先,你要有个输出流。这个输出流给传给小车,就像履带一样,然后你往输出流里写东西,就像往履带上放东西。这样就可以把东西传过去了(原谅我以前没接触过流之类的,一开始搞得真的晕晕的)

mmOutputStream = socket.getOutputStream();
这样我们可以得到一个输出流履带~~~

private void writeByteMessage(String msg) {
		byte[] buffer = new byte[1];
		try {
			if (mmOutputStream == null) {
				Toast.makeText(MainActivity.this, "输出流为空", Toast.LENGTH_SHORT)
						.show();
				return;
			}
			buffer = msg.getBytes();
			mmOutputStream.write(buffer);
		} catch (Exception e) {
			// TODO: handle exception
		} finally {
			try {

			} catch (Exception e2) {
				// TODO: handle exception
			}
		}
	}
这个方法就是往履带上放东西的~~~记得调用。比如,我这边是发送"W"让小车向前,我可以这么写

case R.id.btn_car_up:
			writeByteMessage("W");
			break;
到这里就结束了,这样就简单地运用了串口蓝牙通信。之后会补上更有趣的UI,做成更好玩的东西。


源代码链接(百度云网盘):http://pan.baidu.com/s/1dD6dPvV



















你可能感兴趣的:(Android 蓝牙 BR/EDR 的关于串口通信的学习)