源码网商城,靠谱的源码在线交易网站 我的订单 购物车 帮助

源码网商城

Android 蓝牙连接 ESC/POS 热敏打印机打印实例(蓝牙连接篇)

  • 时间:2022-06-24 13:01 编辑: 来源: 阅读:
  • 扫一扫,手机访问
摘要:Android 蓝牙连接 ESC/POS 热敏打印机打印实例(蓝牙连接篇)
公司的一个手机端的 CRM 项目最近要增加小票打印的功能,就是我们点外卖的时候经常会见到的那种小票。这里主要涉及到两大块的知识: [list=1] [*]蓝牙连接及数据传输[/*] [*]ESC/POS 打印指令[/*] [/list] 蓝牙连接不用说了,太常见了,这篇主要介绍这部分的内容。但ESC/POS 打印指令是个什么鬼?简单说,我们常见的热敏小票打印机都支持这样一种指令,只要按照指令的格式向打印机发送指令,哪怕是不同型号品牌的打印机也会执行相同的动作。比如打印一行文本,换行,加粗等都有对应的指令,这部分内容放在下一篇介绍。 本篇主要基于[url=http://developer.android.com/intl/zh-cn/guide/topics/connectivity/bluetooth.html]官方文档[/url],相比官方文档,省去了大段的说明,更加便于快速上手。 [b]1. 蓝牙权限[/b] 想要使用蓝牙功能,首先要在 AndroidManifest 配置文件中声明蓝牙权限:
<manifest> 
 <uses-permission android:name="android.permission.BLUETOOTH" />
 <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
 ...
</manifest>
[code]BLUETOOTH [/code]权限只允许建立蓝牙连接以及传输数据,但是如果要进行蓝牙设备发现等操作的话,还需要申请 [code]BLUETOOTH_ADMIN [/code]权限。 [b]2. 初始配置[/b] 这里主要用到一个类[url=http://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html]BluetoothAdapter[/url]。用法很简单,直接看代码:
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);
}
如果蓝牙未打开,那么执行[code] startActivityForResult() [/code]后,会弹出一个对话框询问是否要打开蓝牙,点击`是`之后就会自动打开蓝牙。成功打开蓝牙后就会回调到[code] onActivityResult()。[/code] 除了主动的打开蓝牙,还可以监听 [url=http://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#ACTION_STATE_CHANGED]BluetoothAdapter.ACTION_STATE_CHANGED[/url] 广播,包含[code]EXTRA_STATE[/code]和[code]EXTRA_PREVIOUS_STATE[/code]两个 extra 字段,可能的取值包括 [code]STATE_TURNING_ON[/code], STATE_ON, [code]STATE_TURNING_OFF[/code], and[code] STATE_OFF[/code]。含义很清楚了,不解释。 [b]3. 发现设备[/b] 初始化完成之后,蓝牙打开了,接下来就是扫描附近的设备,只需要一句话:
mBluetoothAdapter.startDiscovery();
不过这样只是开始执行设备发现,这肯定是一个异步的过程,我们需要注册一个广播,监听发现设备的广播,直接上代码:
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
 public void onReceive(Context context, Intent intent) {
  String action = intent.getAction();

  // 当有设备被发现的时候会收到 action == BluetoothDevice.ACTION_FOUND 的广播
  if (BluetoothDevice.ACTION_FOUND.equals(action)) {

   //广播的 intent 里包含了一个 BluetoothDevice 对象
   BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

   //假设我们用一个 ListView 展示发现的设备,那么每收到一个广播,就添加一个设备到 adapter 里
   mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
  }
 }
};
// 注册广播监听
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy

注释已经写的很清楚了,除了 [code]BluetoothDevice.EXTRA_DEVICE [/code]之外,还有一个 extra 字段 [code]BluetoothDevice.EXTRA_CLASS[/code], 可以得到一个 [url=http://developer.android.com/reference/android/bluetooth/BluetoothClass.html]BluetoothClass [/url]对象,主要用来保存设备的一些额外的描述信息,比如可以知道这是否是一个音频设备。 关于设备发现,有两点需要注意: [code]startDiscovery() [/code]只能扫描到那些状态被设为 可发现 的设备。安卓设备默认是不可发现的,要改变设备为可发现的状态,需要如下操作:
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
//设置可被发现的时间,00s
intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(intent);
执行之后会弹出对话窗询问是否允许设备被设为可发现的状态,点击`是`之后设备即被设为可发现的状态。 [code]startDiscovery()[/code]是一个十分耗费资源的操作,所以需要及时的调用[code]cancelDiscovery()[/code]来释放资源。比如在进行设备连接之前,一定要先调用[code]cancelDiscovery()[/code] [b]4. 设备配对与连接[/b] [b]4.1 配对[/b] 当与一个设备第一次进行连接操作的时候,屏幕会弹出提示框询问是否允许配对,只有配对成功之后,才能建立连接。 系统会保存所有的曾经成功配对过的设备信息。所以在执行[code]startDiscovery()[/code]之前,可以先尝试查找已配对设备,因为这是一个本地信息读取的过程,所以比[code]startDiscovery()[/code]要快得多,也避免占用过多资源。如果设备在蓝牙信号的覆盖范围内,就可以直接发起连接了。 查找配对设备的代码如下:
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
if (pairedDevices.size() > 0) {
 for (BluetoothDevice device : pairedDevices) {
  mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
 }
}
代码很简单,不解释了,就是调用[code]BluetoothAdapter.getBondedDevices()[/code]得到一个[code] Set<BluetoothDevice> [/code]并遍历取得已配对的设备信息。 [b]4.2 连接[/b] 蓝牙设备的连接和网络连接的模型十分相似,都是Client-Server 模式,都通过一个 socket 来进行数据传输。那么作为一个 Android 设备,就存在三种情况: [list=1] [*]只作为 Client 端发起连接[/*] [*]只作为 Server 端等待别人发起建立连接的请求[/*] [*]同时作为 Client 和 Server[/*] [/list] 因为是为了下一篇介绍连接热敏打印机打印做铺垫,所以这里先讲 Android 设备作为 Client 建立连接的情况。因为打印机是不可能主动跟 Android 设备建立连接的,所以打印机必然是作为 Server 被连接。 [b]4.2.1 作为 Client 连接[/b] [list=1] [*]首先需要获取一个 [url=http://developer.android.com/reference/android/bluetooth/BluetoothDevice.html]BluetoothDevice [/url]对象。获取的方法前面其实已经介绍过了,可以通过调用 startDiscovery()并监听广播获得,也可以通过查询已配对设备获得。[/*] [*]通过 BluetoothDevice.createRfcommSocketToServiceRecord(UUID) 得到[url=http://developer.android.com/reference/android/bluetooth/BluetoothSocket.html]BluetoothSocket [/url]对象[/*] [*]通过BluetoothSocket.connect()建立连接[/*] [*]异常处理以及连接关闭 [/*] [/list] 废话不多说,上代码:
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) { }
 }
}

[code]device.createRfcommSocketToServiceRecord(MY_UUID) [/code]这里需要传入一个 [code]UUID[/code],这个[code]UUID [/code]需要格外注意一下。简单的理解,它是一串约定格式的字符串,用来唯一的标识一种蓝牙服务。 Client 发起连接时传入的 UUID 必须要和 Server 端设置的一样!否则就会报错! 如果是连接热敏打印机这种情况,不知道 Server 端设置的 UUID 是什么怎么办? 不用担心,因为一些常见的蓝牙服务协议已经有约定的 UUID。比如我们连接热敏打印机是基于 SPP 串口通信协议,其对应的 UUID 是 "00001101-0000-1000-8000-00805F9B34FB",所以实际的调用是这样:
[url=http://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html]BluetoothServerSocket [/url]对象。这里传入的第一个参数用来设置服务的名称,当其他设备扫描的时候就会显示这个名称。UUID 前面已经介绍过了。 [*]调用[code]BluetoothServerSocket.accept()[/code]开始监听连接请求。这是一个阻塞操作,所以当然也要放在主线程之外进行。当该操作成功执行,即有连接建立的时候,会返回一个BluetoothSocket 对象。[/*] [*]调用 [code]BluetoothServerSocket.close() [/code]会关闭监听连接的服务,但是当前已经建立的链接并不会受影响。[/*] [/list] 还是看代码吧:
private class AcceptThread extends Thread {

 private final BluetoothServerSocket mmServerSocket;

 public AcceptThread() {

  BluetoothServerSocket tmp = null;
  try {
   // client 必须使用一样的 UUID !!!
   tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
  } catch (IOException e) { }
  mmServerSocket = tmp;
 }

 @Override
 public void run() {
  BluetoothSocket socket = null;
  //阻塞操作
  while (true) {
   try {
    socket = mmServerSocket.accept();
   } catch (IOException e) {
    break;
   }
   //直到有有连接建立,才跳出死循环
   if (socket != null) {
    //要在新开的线程执行,因为连接建立后,当前线程可能会关闭
    doSomething(socket);
    mmServerSocket.close();
    break;
   }
  }
 }

 public void cancel() {
  try {
   mmServerSocket.close();
  } catch (IOException e) { }
 }
}

[b]5. 数据传输[/b] 终于经过了前面的4步,万事俱备只欠东风。而最后这一部分其实是最简单的,因为就只是简单的利用 [code]InputStream [/code]和[code]OutputStream[/code]进行数据的收发。 示例代码:
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) { }
 }
}

下一篇介绍通过手机操作热敏打印机打印的时候,还会用到这部分内容,所以这里就先不多讲了。 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程素材网。
  • 全部评论(0)
联系客服
客服电话:
400-000-3129
微信版

扫一扫进微信版
返回顶部