这是以前做的一个手机蓝牙遥控器,原本是用来控制一个微型四旋翼的。四旋翼做了第二版后改NRF2401控制了,所以这个程序最终还是没用,下面介绍一下这个程序的关键代码。
连接的对象是一个蓝牙4.0模块,连接上了之后通过串口对飞机进行控制。说一下蓝牙模块的距离,可能因为是用的是蓝牙4.0的缘故,我在走廊里面测试是33米的距离,还是挺远的,足够了。这个程序连接的上蓝牙模块的是有概率失败的,我使用的魅族手机,失败的概率非常高,但是换成的华为手机后,失败概率就小多了,所以这个成功率还是跟手机厂商的优化有关的,但是连接上了还是非常稳定的。
下面介绍程序功能,该程序该程序包括两个Activity,主Activity如下图所示:
主Activity是一个遥控界面(按钮是随便拉进去的,各种没对齐-_-),点击蓝牙连接按钮,可以跳到第二个Activity,如下图:
这个Activity包括一个ListView,点击开启蓝牙Button查找设备,并把所查找到的设备名称放在ListView里面,点击ListView中要连接的设备,就会跳回主Activity,同时进行蓝牙连接。在屏幕的中间显示连接状态,连接成功后即可传输数据。
总的来说,要使用Android的蓝牙,主要分下面几步:
因为开启蓝牙Button在从Activity里面,所以开启蓝牙的操作应该是在从Activity中的,因此需要从主Activity切换到从Activity,这里很简单就省略了。下面是开启蓝牙的代码:
Intent enable = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
//3600为蓝牙设备可见时间
enable.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 3600);
startActivity(enable);
通过Intent开启蓝牙,写到按钮事件中就可以点击按钮开启蓝牙。
开启蓝牙蓝牙之后,就可以调用适配器来查找蓝牙设备了,其代码如下:
private BluetoothAdapter adapter;
private BluetoothReceiver receiver;
adapter = BluetoothAdapter.getDefaultAdapter();//得到默认的蓝牙适配器
if (adapter.getState() != BluetoothAdapter.STATE_ON) {// 如果蓝牙还没开启
Toast.makeText(MyBlueTooth.this, "请开启蓝牙", Toast.LENGTH_SHORT).show();
}else {
adapter.startDiscovery();//开始搜索
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
receiver = new BluetoothReceiver();
registerReceiver(receiver, filter);//注册广播
}
首先通过adtapter
查看蓝牙是否开启,如开启则开始搜索,其中adapter.startDiscovery()
是一个异步方法,调用后在后台对周围的蓝牙设备进行搜索,然后将搜索结果通过广播的形式发送,所以这里要注册广播,BluetoothReceiver
是自定义的一个广播接收器,继承自BroadcastReceiver
类,这里定义成了一内部类,代码如下:
private ListView listView;
private List deviceNames;
private List deviceList;
listView = (ListView)findViewById(R.id.listView);
deviceNames = new ArrayList();
deviceList = new ArrayList();
//内部类
private class BluetoothReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {//发现设备
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (deviceNames.indexOf(device.getName()) == -1) {//列表中没有名字
deviceNames.add(device.getName());//添加
}
deviceList.add(device);
}
showDevices();
}
}
private void showDevices() {
ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1,deviceNames);
listView.setAdapter(adapter);
}
这里涉及到了一些ListView的用法,就不展开了。
前面几步进展地还比较顺利,但到了蓝牙连接这里就出现了很多问题。第一是BluetoothSocket
的两种创建方式,一种是通过特定的UUID来创建,我试了很多次连接都成问题,然后查到了第二种方法,通过调用BlueDevice
的隐藏方法createRfcommSocket()
来创建socket,应用到了Java的反射机制,这种方法只能说能够连上,失败的概率还是很大的(之后测试了其他手机,连接成功率就上去了,可能跟不同的手机厂商的优化有关,问题也可能出在代码本身上)。第二是连接耗时比较长,所以不能放到主线程中,需要新开一个线程进行连接,用Handler
接收连接状态,更新UI界面。第三是前面所有的操作都是在从Activity中完成的,但是蓝牙连接的操作需要放到主Activity中,所以要实现主Activity与从Activity 的数据传输。先说Activity间的数据传输问题,我用了Android的Application类来传输,代码如下:
BluetoothDevice device = deviceList.get(position);//获取蓝牙设备
adapter.cancelDiscovery();//取消搜索
bridge.setDate(device);//传递数据
MainActivity.back = true;
//返回操作界面
Intent backIntent = new Intent(getApplicationContext(), MainActivity.class);
startActivity(backIntent);
需要说明的是,在连接之前需要取消搜索,就是调用cancelDiscovery()
方法,否则连接会变得异常缓慢。其中的 bridge
继承自Application类,用于传递数据,可以看到,这里是把一个BluetoothDevice
的实例化对象传递了过去。然后在主Activity中接收该对象:
public static boolean back = false;
private BluetoothConnect client;
//onCreate()
if(back){//返回后开始读数据
device = bridge.getDate();
client = new BluetoothConnect(device,handler);
client.connect();//开始连接
}
可以看到,这里蓝牙设备的获取是在从Activity中,而创建连接是在主Activity中。在主Activity中接收到了device
,通过device
就可以创建socket进行连接了,这个过程我封装到一个BluetoothConnect
类中,方便以后调用,下面是其中的connect()
方法:
private BluetoothSocket socket;
public void connect(){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Method method;
state = CONNECT_SUCCESS;
try {
//利用反射机制创建socket
method = device.getClass().getMethod("createRfcommSocket", new Class[]{int.class});
socket = (BluetoothSocket) method.invoke(device, 1);
socket.connect();
isConnect = ture;
} catch (IOException e) {
state = CONNECT_FAILED;
Log.e("TAG", e.toString());
} catch (InvocationTargetException e) {
state = CONNECT_FAILED;
Log.e("TAG", e.toString());
} catch (NoSuchMethodException e) {
state = CONNECT_FAILED;
Log.e("TAG", e.toString());
} catch (IllegalAccessException e) {
state = CONNECT_FAILED;
Log.e("TAG", e.toString());
}
//发送连接状态
Message msg = handler.obtainMessage();
msg.what = state;
handler.sendMessage(msg);
}
});
thread.start();
}
因为连接过程必须放到子线程中,因此这是一个异步方法,该方法中通过调用createRfcommSocket()
方法创建socket,然后向主Activity中的Handler发送消息,需要用到消息机制,主线程接收消息并更新UI,即在屏幕的中间显示连接状态。接下来代码是主Activity中的Handler,作用是更新UI:
private final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case BluetoothConnect.CONNECT_FAILED:
Toast.makeText(MainActivity.this, "连接失败", Toast.LENGTH_SHORT).show();
break;
case BluetoothConnect.CONNECT_SUCCESS:
Toast.makeText(MainActivity.this, "连接成功", Toast.LENGTH_SHORT).show();
client.receive();//接收数据
break;
case BluetoothConnect.READ_FAILED:
Toast.makeText(MainActivity.this, "读取失败", Toast.LENGTH_SHORT).show();
break;
case BluetoothConnect.WRITE_FAILED:
Toast.makeText(MainActivity.this, "写入失败", Toast.LENGTH_SHORT).show();
break;
case BluetoothConnect.DATA:
Toast.makeText(MainActivity.this, ""+msg.arg1, Toast.LENGTH_SHORT).show();
break;
}
}
};
可以在此Handler中用TextView等来显示连接状态,这里简单起见就用只用了Toast。可以看到这里有一个receive()
方法,这个方法也是封装到BluetoothConnect
中的一个异步方法,该方法如下:
public void receive(){
//连接成功则接收数据
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
if(isConnect){
try {
InputStream inputStream = socket.getInputStream();
int data;
while (true){
try {
data = inputStream.read();
Message msg = handler.obtainMessage();
msg.what = BluetoothConnect.DATA;
msg.arg1 = data;
handler.sendMessage(msg);
}catch (IOException e){
Log.e("TAG", e.toString());
break;
}
}
} catch (IOException e) {
Log.e("TAG", e.toString());
}
}
}
});
thread.start();
主要是通过不断读取输入流的内容来接收数据,可以看到,这里又利用了消息机制,将接收到的数据通过消息打包发回了主线程,在主线程的Handle中接收消息即可更新UI,显示所接收的内容。
最后不要忘了加上相关的蓝牙权限。
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
本文参考来源:http://www.cnblogs.com/wenjiang/p/3200138.html#!comments