您的位置:首页 > 移动开发 > Android开发

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

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

首先呢,这篇只对BR/EDR类型的蓝牙进行讨论,即普通蓝牙。对于4.0,即BLE以后再说。大致结构如下:




那么开始吧!!

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

清单中需加入的两个权限

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
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);
<pre name="code" class="java"><span style="white-space:pre">				</span>bluetoothAdapter.startDiscovery();



<span style="white-space:pre">				</span>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<BluetoothDevice> 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来存放设备名和设备地址

</pre>先上代码,再细细说。<pre name="code" class="java">public class BlueToothDeviceAdapter extends ArrayAdapter<BluetoothDevice> {
private Context mContext;
private int resourceId;
private BluetoothDevice device;
private ViewHolder holder;

public BlueToothDeviceAdapter(Context context, int resource,
List<BluetoothDevice> 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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: