Android蓝牙相关—蓝牙打印

来源:互联网 发布:淘宝卖包包的好店 编辑:程序博客网 时间:2024/05/27 16:40

一、概述

最近公司刚好遇到个蓝牙打印的功能,以前实习时看到过类似功能,刚好这次自己实现,顺便记录一下。

二、基本环境

权限:

<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/><uses-permission android:name="android.permission.BLUETOOTH"/>

初始化蓝牙适配器:

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();if (mBluetoothAdapter == null) {    // Device does not support Bluetooth}

如果为null代表设备不支持蓝牙功能,需要作出相应处理,比如弹出个对话框;如果设备支持蓝牙,就检查蓝牙是否打开:

if (!mBluetoothAdapter.isEnabled()) {    Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);    startActivityForResult(intent, REQUEST_ENABLE_BT);}

该intent为强制自动打开蓝牙,看机型会有不同的情况,通常机型会弹出对话框询问是否打开蓝牙;也可以处理成跳转到系统蓝牙界面:

Intent intent = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);startActivityForResult(intent, REQUEST_SETTING);

打开蓝牙后,最好实现onActivityResult方法,收到回应后设置一些初始化工作:

    @Override    protected void onActivityResult(int requestCode, int resultCode, Intent data) {        super.onActivityResult(requestCode, resultCode, data);        switch (requestCode) {            case REQUEST_ENABLE_BT:                initBluetooth();                break;            case REQUEST_SETTING:                initBluetooth();                break;            default:                break;        }    }

三、扫描配对连接

1.扫描:发现设备
打开蓝牙之后,接下来就是扫描附近的蓝牙设备:

mBluetoothAdapter.startDiscovery();

这是个异步过程,通常需要十多秒的时间,扫描的开始、发现、结束均有广播,所以,我们需要注册广播,并在onStop或者onDestory时解除注册:

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {        @Override        public void onReceive(Context context, Intent intent) {            String action = intent.getAction();            if (BluetoothDevice.ACTION_FOUND.equals(action)) {            // 发现设备            // 得到BluetoothDevice对象的意图            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);            BluetoothClass bluetoothClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS);            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {                // 扫描结束            } else if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)){          // 扫描开始                  }        }    };

广播中的intent包含一个BluetoothDevice对象,通过

intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)

可以获取到,同时还有一个extra字段

BluetoothDevice.EXTRA_CLASS

, 可以得到一个 BluetoothClass 对象,主要用来保存设备的一些额外的描述信息,比如可以知道这是否是一个音频设备。

注意:

startDiscovery() 只能扫描到那些状态被设为 可发现 的设备。安卓设备默认是不可发现的,要改变设备为可发现的状态,需要如下操作:

Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);//设置可被发现的时间,300sintent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);startActivity(intent);

startDiscovery()是一个十分耗费资源的操作,所以需要及时的调用cancelDiscovery()来释放资源。比如在进行设备连接之前,一定要先调用cancelDiscovery()

2.配对连接
2.1 配对:
当与一个设备第一次进行连接操作的时需先配对,屏幕会弹出提示框询问是否允许配对,只有配对成功之后,才能建立连接。

系统会保存所有的曾经成功配对过的设备信息。所以在执行startDiscovery()之前,可以先尝试查找已配对设备,因为这是一个本地信息读取的过程,所以比startDiscovery()要快得多,也避免占用过多资源。如果设备在蓝牙信号的覆盖范围内,就可以直接发起连接了。

查找已配对的设备代码如下:

Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();if (pairedDevices.size() > 0) {    for (BluetoothDevice device : pairedDevices) {        mArrayAdapter.add(device.getName() + "\n" + device.getAddress());    }}

2.2 连接:
蓝牙连接也是Client-Server模式,通过一个socket来进行数据传输,作为一个android设备,会存在三种情况:

  1. 只作为 Client 端发起连接
  2. 只作为 Server 端等待别人发起建立连接的请求
  3. 同时作为 Client 和 Server

    但这篇重点是蓝牙打印,需要连接打印机,所以只能作为client端,毕竟打印机不可能主动跟其他设备发起连接;另外两种情况应该是ble通信会经常遇到的,后续有机会也会学习记录。

连接步骤:
1.获取一个BluetoothDevice对象,可通过扫描并监听广播获取,也可以通过查询已配对设备获得,还可以通过mac地址获取:

BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(bd.getAddress());

2.获得BluetoothSocket对象:

BluetoothSocket mmSocket = device.createRfcommSocketToServiceRecord(UUID);

3.通过BluetoothSocket.connect()建立连接(这是个同步过程,连接失败需要处理异常)
4.异常处理以及连接关闭

代码:

private class ConnectThread extends Thread {    private final BluetoothSocket mmSocket;    private final BluetoothDevice mmDevice;    public ConnectThread(BluetoothDevice device) {        BluetoothSocket tmp = null;        mmDevice = device;        try {            // 通过 BluetoothDevice 获得 BluetoothSocket 对象            tmp = device.createRfcommSocketToServiceRecord(MY_UUID);        } catch (IOException e) { }        mmSocket = tmp;    }    @Override    public void run() {        // 建立连接前记得取消设备发现        mBluetoothAdapter.cancelDiscovery();        try {            // 耗时操作,所以必须在主线程之外进行            mmSocket.connect();        } catch (IOException connectException) {            //处理连接建立失败的异常            try {                mmSocket.close();            } catch (IOException closeException) { }            return;        }        doSomething(mmSocket);    }    //关闭一个正在进行的连接    public void cancel() {        try {            mmSocket.close();        } catch (IOException e) { }    }}

注:

device.createRfcommSocketToServiceRecord(MY_UUID) 这里需要传入一个 UUID,这个UUID 需要格外注意一下。简单的理解,它是一串约定格式的字符串,用来唯一的标识一种蓝牙服务。Client 发起连接时传入的 UUID 必须要和 Server 端设置的一样!否则就会报错!一些常见的蓝牙服务协议已经有约定的 UUID。比如我们连接热敏打印机是基于 SPP 串口通信协议,其对应的 UUID 是 “00001101-0000-1000-8000-00805F9B34FB”。其他常见的蓝牙服务的UUID大家可以自行搜索。如果只是用于自己的应用之间的通信的话,那么理论上可以随便定义一个 UUID,只要 server 和 client 两边使用的 UUID 一致即可。

四、蓝牙数据传输

连接成功之后,我们可以利用InputStream 和OutputStream进行数据的收发。

代码如下:

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;        //通过 socket 得到 InputStream 和 OutputStream        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()        //不断的从 InputStream 取数据        while (true) {            try {                bytes = mmInStream.read(buffer);                mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)                        .sendToTarget();            } catch (IOException e) {                break;            }        }    }    //向 Server 写入数据    public void write(byte[] bytes) {        try {            mmOutStream.write(bytes);        } catch (IOException e) { }    }    public void cancel() {        try {            mmSocket.close();        } catch (IOException e) { }    }}

五、蓝牙打印

其实蓝牙打印就是从BluetoothSocket 得到了一个OutputStream,然后不停的往里面write数据。

手机通过蓝牙向打印机发送的都是纯字节流,打印机如何知道该打印什么呢?这里就要 ESC/POS 打印控制命令,详情可以百度,通常用到的命令如下:

public static final byte[][] byteCommands = { { 0x1b, 0x40 },// 复位打印机 0            { 0x1b, 0x4d, 0x00 },// 标准ASCII字体1            { 0x1b, 0x4d, 0x01 },// 压缩ASCII字体2            { 0x1d, 0x21, 0x00 },// 字体不放大3            { 0x1d, 0x21, 0x02 },// 宽高加倍4            { 0x1d, 0x21, 0x11 },// 宽高加倍5            { 0x1b, 0x45, 0x00 },// 取消加粗模式6            { 0x1b, 0x45, 0x01 },// 选择加粗模式7            { 0x1b, 0x7b, 0x00 },// 取消倒置打印8            { 0x1b, 0x7b, 0x01 },// 选择倒置打印9            { 0x1d, 0x42, 0x00 },// 取消黑白反显10            { 0x1d, 0x42, 0x01 },// 选择黑白反显11            { 0x1b, 0x56, 0x00 },// 取消顺时针旋转90°12            { 0x1b, 0x56, 0x01 },// 选择顺时针旋转90°13            { 0x1b, 0x61, 0x30 },// 左对齐14            { 0x1b, 0x61, 0x01 },// 居中对齐15            { 0x1b, 0x61, 0x32 },// 右对齐16            { 0x1C, 0x21, 0x0C },// 设置倍宽倍高17            { 0x1B, 0x61, 0x00 },// 取消居中18            { 0x1C, 0x21, 0x00 },// 取消倍宽19            { 0x0a },// 换行20            { 0x1d, 0x56, 0x42, 0x01 },// 切纸21            { 0x1C, 0x21, 0x08 },// 稍微小一点的倍宽22            { 0x0D, 0x1B, 0x40 },// 23            { 0x1A },// 24            // { 0x1b, 0x69 },// 切纸    };

注:

每次打印开始之前,都要对打印机进行初始化,发送指令如下:

mService.write(byteCommands[0]);

注:

通常打印机最大宽度为256个像素点,可以根据这个值来设置格式,不推荐使用空格等来达到打印效果,另,需要打印字符串的话需要进行转码,如下:

mService.write(("测试蓝牙打印").getBytes("GB2312"));// 或者封装下OutputStreamOutputStreamWriter writer = new OutputStreamWriter(outputStream, "GB2312");

六、总结

关于蓝牙连接部分的线程管理代码是网上很多博客采用的,之前项目中领导应该也是这样借鉴的;通过线程来管理不同的连接状态情况,建议实现蓝牙打印功能时:蓝牙连接线程管理封装成一个工具类,同时保存当前连接状态以便随时打印;具体打印内容格式则封装成另一个类,方便扩展。

原创粉丝点击