周末又是偷懒 打了两把DOTA2,想写的系列还没有动笔。这两天狠下功夫把蓝牙研究了个明白,因为同学有需求,他的小车上要用到。搞懂了自然就记下来,网上有用的太少了,做个小整理,免得再出问题。
首先呢,这篇只对BR/EDR类型的蓝牙进行讨论,即普通蓝牙。对于4.0,即BLE以后再说。大致结构如下:
那么开始吧!!
第一步:加入权限,并且检查设备是否支持蓝牙
清单中需加入的两个权限
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关于ListViewAdapter的我就不说了,之后更新的文章会详细讲解。这里,运用device的getName方法,getAddress方法可以分别得到查找到的设备的昵称和MAC地址。并且,我加入了一个判定。判断当前设备的配对状态,未配对的话,按下配对按钮就可以配对{ 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; } }
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