Android蓝牙遥控器

这是以前做的一个手机蓝牙遥控器,原本是用来控制一个微型四旋翼的。四旋翼做了第二版后改NRF2401控制了,所以这个程序最终还是没用,下面介绍一下这个程序的关键代码。

连接的对象是一个蓝牙4.0模块,连接上了之后通过串口对飞机进行控制。说一下蓝牙模块的距离,可能因为是用的是蓝牙4.0的缘故,我在走廊里面测试是33米的距离,还是挺远的,足够了。这个程序连接的上蓝牙模块的是有概率失败的,我使用的魅族手机,失败的概率非常高,但是换成的华为手机后,失败概率就小多了,所以这个成功率还是跟手机厂商的优化有关的,但是连接上了还是非常稳定的。

下面介绍程序功能,该程序该程序包括两个Activity,主Activity如下图所示:

主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<String> deviceNames;                                                                                             
private List deviceList; 

listView = (ListView)findViewById(R.id.listView);
deviceNames = new ArrayList<String>();                                                
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<String> adapter = new ArrayAdapter<String>(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

猜你喜欢

转载自blog.csdn.net/egean/article/details/53033515