首页 > 代码库 > Android 蓝牙串口服务客户端开发 尝试

Android 蓝牙串口服务客户端开发 尝试

如题,经过三四天的开发尝试已经初步成型,下面是简陋的界面图:


上图是做的蓝牙串口服务的收发界面,主要用于平时的调试之用,由于开发的初衷是为了实现蓝牙对单片机的控制,因此加入了<进入控制/>的按钮选项,下图是控制界(xian)面(tiao)


没办法,没有太多的美学细胞,拖了两个重写的seekbar就作为控制摇杆了。。。

初次写Android,初次触及Java,水平自然就是无言以对,哈哈,不过为了防止万一以后有需求的升级,博客还是要写的。

1.环境搭建

Android开发者官网上提供了两套集成开发环境,Eclipse+ADT和Android studio,没有多想就下了个Eclipse+ADT,安装后运行时还是出了点问题,提示Failed to create the java vitual machine,好吧这个问题网上一搜一大把,大抵说是内存问题,只需把eclipse.ini配置文件相关内存参数调小,基本就没什么问题了。

2.创建第一个APP

好了下面基本是靠着官网的training材料开始的第一个app尝试,着重说一下创建android工程的几个选项的意义,加深下记忆:

Project Name:工程的目录名和开发环境中的名字

Package Name:应用程序的命名空间,但是要保证不能同于安卓系统中其他package的名字,默认是com.example.appname

Minimum Required SDK:指定应用程序能支持的最低安卓版本(API LEVEL),最好是将版本设为最低(使用默认即可),这样可以让你的app提供核心特征,而如果你的app里必需要用到在更高版本安卓上才有的特征并且此特征并不影响你的核心特征,那么可以只在高版本的安卓平台中才使能该特征

Target SDK:表明你的app可能的最高安卓版本的测试平台(api level),这是出于android版本更新的考虑,为了能够让用户程序匹配最新的api接口

Compile With:这是你的应用程序将要使用的编译平台,缺省情况下是选择最新版本,当然你可以选择低版本,但是新版本可以让你的应用程序使用新的特征并且优化的程序可以产生更好的用户体验

Theme:主题,UI的风格相关的,如actionbar

好了,这些选择了,下面缺省就可以,blank activity默认是一个hello world程序。

下面再着重说下android project的文件组织结构(我能用到的):

AndroidManifest.xml

 工程根目录下比较重要的文件,描述应用程序的基本特征和组件,大致如下 :

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.bluetooth"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="21" />
    
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" 
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
    </application>

</manifest>
/src:

src文件下自然就是源文件了,是以package来组织的,com.example.appname,也就是我们的应用程序的package下,有几个activity应该就有几个对应的源文件(不知道准确不?)

/res

这个文件夹就是资源文件夹,包含有drawable-hdpi,layout,menu,values等文件夹,drawable-hdpi 包含绘图性质的对象,如bitmap,hdpi是指针对高密度的显示屏,当然其实还有其他不同密度屏幕对应的文件夹,layout也就是界面布局,也是用xml文件组织, menu?。。。上图



value 多用来定义应用程序中的常量资源,如string,color等。

3.运行程序

我是用手机直接运行的,很简单打开手机的usb调试,然后点击运行,确定设备,程序就会部署到手机并运行,并且通过logcat可以实时输出调试信息,追踪程序运行。

4.蓝牙

好了,了解基本开发步骤后,可以开始尝试蓝牙的开发,依然紧跟开发者官网,找到api guides->connnectivity->bluetooth。传说中蓝牙4.0支持BLE技术,不过下文只是传统的蓝牙开发。在正式的开发之前,官网先向我们抛出了如下几个概念:

BluetoothAdapter:

所有蓝牙交互的入口点,通过它,你可以发现其他蓝牙设备,访问绑定的蓝牙设备,并且可以通过MAC地址实例化一个BluetooothDevice以及创建bluetoothserversocket来监听待接入的设备。

BluetoothDevice:

表示一个远程的蓝牙设备,用它你可以通过BluetoothSocket 来申请与远程蓝牙设备的连接,当然你也可以通过它来访问设备名称,地址,绑定状态等

BluetoothSocket:

表示蓝牙套接字的接口,通过InputStream和OutputStream可实现与远程蓝牙设备的数据交换

BluetoothServerSocket:

表示一个开放的服务端套接字用来监听外部的申请,为了实现两个蓝牙设备的通信,必须有一端开放服务套接字来让远程设备请求连接,当远程设备的请求被允许时,那么就会返回一个连接的BluetoothSocket

BluetoothClass:

描述了一个蓝牙设备的通用特性和能力,它是一组只读的特性用来定义设备的主要和次要设备类以及它的服务

下面还有诸如BluetoothProfile,BluetoothHeadset等这样的一些概念,但是由于本应用中不涉及,故不一一介绍。好了在正式蓝牙开发步骤之前,有一点是不容忽视的,那就是蓝牙的操作权限

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
上面两条代码是必须要加入到AndroidManifest.xml文件中的,第一条是使用蓝牙特性的权限如:申请连接,接受连接和传输数据,而第二条则主要是用来发现周围的蓝牙设备权限。好了,这样我们便可以开始肆无忌惮的蓝牙操作了。

4.1建立本地蓝牙

首先是通过getDefaultAdapter()来获得BluetoothAdapter,BluetoothAdapter 代表本地蓝牙的接收器,并且整个系统中有且只能有一个Adapter ,如果你的设备不支持蓝牙设备,则函数会返回 NULL

如果设备支持蓝牙,那么接下来该是使能蓝牙设备,调用isEnabled() 函数来判断蓝牙是否被使能(也就是打开),如果返回false那么要通过startActivityForResult() 来申请蓝牙的打开:

if (!mBluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, 1);
}
Intent是安卓的一种运行时绑定机制,在程序运行过程中连接两个不同的组件,这里是向系统申请开启蓝牙意愿,安卓根据相应意愿开启一个Activity,如下图所示:


4.2寻找蓝牙设备

通过BluetoothAdapter就可以发现远程设备或者访问已配对的设备,这里解释一下发现远程设备和访问已配对设备的区别,所谓发现远程设备是指对附近可被发现的蓝牙设备进行搜索并获取相应信息(如名字,MAC等);而访问已配对的设备则是指表示某些蓝牙设备的信息(名字,MAC)已经被存入本地,那么如果这些蓝牙设备在范围并且打开,那么只要使用MAC地址直接与设备连接,而无需发现查找这一步骤。

在因为真正发现设备步骤之前,访问已经配对的设备是有必要的,因为可能你需要的设备已经在配对列表中,Android系统中只要调用 getBondedDevices()函数就可以获得一系列绑定的BluetoothDevice,而发现蓝牙设备的操作也是很简单的,调用startDiscovery(),这个函数不过是一个搜索过程的启动按钮,并且会立刻返回是否正常启动搜索信息,那么接下来将是大约12S左右的蓝牙搜索过程,这里需要注意的是我们需要在程序中注册Broadcast Receiver组件,因为系统在每搜索到一个蓝牙设备后会通过ACTION_FOUND Intent来通知,并且携带EXTRA_DEVICE 和 EXTRA_CLASS信息,这里Broadcast Receiver注册与实现形如下:

// Create a BroadcastReceiver for ACTION_FOUND
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        // When discovery finds a device
        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
            // Get the BluetoothDevice object from the Intent
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            // Add the name and address to an array adapter to show in a ListView
            mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
        }
    }
};
// Register the BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy


4.3连接蓝牙设备

因为本人应用场合是主动去连接蓝牙设备,所以这里主要是实现以客户端连接步骤,在4.2节我们已经讲到了如何找到设备并获得 BluetoothDevice,那么现在就是利用 BluetoothDevice来连接远程设备(open server socket),这里通过BluetoothDevice的 createRfcommSocketToServiceRecord(UUID)来实现获得 BluetoothSocket,那么下面便是连接了,不过别忘了在连接之前要将发现设备过程关闭,因为该过程浪费资源很多会造成连接缓慢甚至是失败,调用cancelDiscovery();连接蓝牙设备则是调用BluetoothSocket的connect() 实现,如果UUID及对方设备都已接受应该就能连接成功,不过该连接函数是个阻塞函数,所以为了避免用户窗口的阻塞,一般将其放入单独的一个线程,示例代码如下 :

tip:UUID(Universally Unique Identifier),这里主要用该参数来申请连接特殊的蓝牙服务,如我申请的蓝牙串口那么就是

device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));

private class ConnectThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final BluetoothDevice mmDevice;
 
    public ConnectThread(BluetoothDevice device) {
        // Use a temporary object that is later assigned to mmSocket,
        // because mmSocket is final
        BluetoothSocket tmp = null;
        mmDevice = device;
 
        // Get a BluetoothSocket to connect with the given BluetoothDevice
        try {
            // MY_UUID is the app's UUID string, also used by the server code
            tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
        } catch (IOException e) { }
        mmSocket = tmp;
    }
 
    public void run() {
        // Cancel discovery because it will slow down the connection
        mBluetoothAdapter.cancelDiscovery();
 
        try {
            // Connect the device through the socket. This will block
            // until it succeeds or throws an exception
            mmSocket.connect();
        } catch (IOException connectException) {
            // Unable to connect; close the socket and get out
            try {
                mmSocket.close();
            } catch (IOException closeException) { }
            return;
        }
 
        // Do work to manage the connection (in a separate thread)
        manageConnectedSocket(mmSocket);
    }
 
    /** Will cancel an in-progress connection, and close the socket */
    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) { }
    }
}

4.4管理蓝牙连接

你一定注意到了上代码中的

manageConnectedSocket(mmSocket);

这样一句,这其实才是真正的重点,同样作为一个单独的线程,主要负责管理蓝牙数据的收发,这里主要通过蓝牙套接字获得数据的输入输出流,然后根据相应流接口对蓝牙进行读写操作:

private class ConnectedThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final InputStream mmInStream;
    private final OutputStream mmOutStream;
 
    public ConnectedThread(BluetoothSocket socket) {
        mmSocket = socket;
        InputStream tmpIn = null;
        OutputStream tmpOut = null;
 
        // Get the input and output streams, using temp objects because
        // member streams are final
        try {
            tmpIn = socket.getInputStream();
            tmpOut = socket.getOutputStream();
        } catch (IOException e) { }
 
        mmInStream = tmpIn;
        mmOutStream = tmpOut;
    }
 
    public void run() {
        byte[] buffer = new byte[1024];  // buffer store for the stream
        int bytes; // bytes returned from read()
 
        // Keep listening to the InputStream until an exception occurs
        while (true) {
            try {
                // Read from the InputStream
                bytes = mmInStream.read(buffer);
                // Send the obtained bytes to the UI activity
                mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
                        .sendToTarget();
            } catch (IOException e) {
                break;
            }
        }
    }
 
    /* Call this from the main activity to send data to the remote device */
    public void write(byte[] bytes) {
        try {
            mmOutStream.write(bytes);
        } catch (IOException e) { }
    }
 
    /* Call this from the main activity to shutdown the connection */
    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) { }
    }
}
好了整个蓝牙打开及管理的基本步骤,界面设计及交互就没什么好说的了,不过要提一下在开发过程中遇到了几个问题或建议吧:

1.Android是不能在线程中更新UI的,必须通过Handler来实现信息的传递更新

2.setRequestedOrientation函数可以改变界面的显示方向,但是要注意的是该函数会引起Activity的onCreate调用,也就是UI或组件会被重新创建,为了避免可以重写

onConfigurationChanged而后根据this.getResources().getConfiguration().orientation 来执行相应操作

3.可以适当重写控件工具包的实现来达到想要的UI效果

4.CTRL+SHIFT+O神KEY



参考:http://developer.android.com/guide/topics/connectivity/bluetooth.html#ManagingAConnection


Android 蓝牙串口服务客户端开发 尝试