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设备,会存在三种情况:
- 只作为 Client 端发起连接
- 只作为 Server 端等待别人发起建立连接的请求
同时作为 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");
六、总结
关于蓝牙连接部分的线程管理代码是网上很多博客采用的,之前项目中领导应该也是这样借鉴的;通过线程来管理不同的连接状态情况,建议实现蓝牙打印功能时:蓝牙连接线程管理封装成一个工具类,同时保存当前连接状态以便随时打印;具体打印内容格式则封装成另一个类,方便扩展。
- Android蓝牙相关—蓝牙打印
- Android蓝牙打印源码
- android蓝牙打印
- android 蓝牙打印
- android 蓝牙打印
- Android打印机--蓝牙打印
- Android蓝牙连接打印
- android蓝牙相关
- Android蓝牙相关开发
- android蓝牙相关
- android蓝牙打印的若干问题
- Android蓝牙打印格式排版
- MUI蓝牙打印(Android)
- Android控制蓝牙票据打印
- android蓝牙打印小票机Demo
- 蓝牙打印
- android 蓝牙打印和网络打印
- Android 蓝牙相关的东西
- Jedis对redis的操作详解
- UML常用图的几种关系的总结
- 深谈多并发计数器CountDownLatch(一)
- 数学笔记——导数5(指数函数和对数函数的导数)
- 探究多个装饰器执行顺序
- Android蓝牙相关—蓝牙打印
- Glide 判断图片是否存在 以及获得宽高
- java入门篇
- Dev c++调试时停止工作解决方法
- 抓firefox上https请求
- Lua_协同程序_011
- angularjs+生成随机数猜数字大小
- C++连接Mysql数据库 & 使用预处理读写BLOB
- 自己实现的qgraphicsitem