1. 效果图

2. 开发环境

  • Gprinter佳博打印机,型号GP-2120TU
  • Android Studio 3.6.1,gradle 3.5.3

3. 涉及知识点

  • 蓝牙相关
  • 多线程开发
  • 线程池
  • 构建者模式
  • 单例模式
  • 运行时权限
  • BroadcastReceiver
  • startActivityForResult

4. 集成配置

1.添加jar包,在app目录下新建libs文件夹,拷入jar文件并Add As Library

2.在main目录下新建jniLibs目录,并拷入so文件

3.配置我们的manifest文件,添加必要的权限

    <!-- 打印相关权限 --><uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /><uses-permission android:name="android.permission.BLUETOOTH" /><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /><uses-featureandroid:name="android.hardware.usb.host"android:required="true" />

ok,基本的配置完成,开始进入正题。

5. 蓝牙连接

  • 因为蓝牙涉及到隐私权限,所以先检查、请求权限
    private void checkPermission() {for (String permission : permissions) {if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(this, permission)) {per.add(permission);}}}private void requestPermission() {if (per.size() > 0) {String[] p = new String[per.size()];ActivityCompat.requestPermissions(this, per.toArray(p), REQUEST_CODE);}}
  • 点击连接按钮,通过startActivityForResult启动一个蓝牙列表页面,实际上显示为一个dialog
    public void btnConnect(View view) {startActivityForResult(new Intent(MainActivity.this, BluetoothListActivity.class), BLUETOOTH_REQUEST_CODE);}
  • 这个页面的作用就是判断蓝牙是否可用、是否开启,显示已配对和未配对的蓝牙设备列表
    /*** 初始化蓝牙*/private void initBluetooth() {// 获取蓝牙适配器mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();// 检查蓝牙是否可用if (mBluetoothAdapter == null) {Toast.makeText(this, "当前设备不支持蓝牙", Toast.LENGTH_SHORT).show();} else {// 检查蓝牙是否打开if (!mBluetoothAdapter.isEnabled()) {Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);startActivityForResult(enableIntent, REQUEST_ENABLE_BT);} else {getDeviceList();}}}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if (requestCode == REQUEST_ENABLE_BT) {if (resultCode == Activity.RESULT_OK) {// bluetooth is openedgetDeviceList();} else {// bluetooth is not openToast.makeText(this, "蓝牙没有开启", Toast.LENGTH_SHORT).show();}}}/*** 蓝牙设备列表*/protected void getDeviceList() {// 初始化一个数组适配器,用来显示已匹对和未匹对的设备mDevicesArrayAdapter = new ArrayAdapter<>(this, R.layout.bluetooth_device_name_item);lvPairedDevice.setAdapter(mDevicesArrayAdapter);lvPairedDevice.setOnItemClickListener(mDeviceClickListener);// 已匹对数据Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();// 添加一个item显示信息mDevicesArrayAdapter.add("已配对:");if (pairedDevices.size() > 0) {//遍历填充数据for (BluetoothDevice device : pairedDevices) {mDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());}} else {mDevicesArrayAdapter.add("没有已配对设备");}}/*** 接收扫描设备的广播* changes the title when discovery is finished*/private final BroadcastReceiver mFindBlueToothReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {String action = intent.getAction();// 每当发现一个蓝牙设备时if (BluetoothDevice.ACTION_FOUND.equals(action)) {// Get the BluetoothDevice object from the Intent//获取设备BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);// If it's already paired, skip it, because it's been listed// 未匹对的情况下添加显示if (device.getBondState() != BluetoothDevice.BOND_BONDED) {mDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());}// 扫描结束} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {setProgressBarIndeterminateVisibility(false);setTitle("选择蓝牙设备");//此处-2是减去我们手动添加的两个区分显示的itemLog.i("tag", "finish discovery" + (mDevicesArrayAdapter.getCount() - 2));if (mDevicesArrayAdapter.getCount() == 0) {mDevicesArrayAdapter.add("没有找到蓝牙设备");}}}};/*** 扫描设备*/private void discoveryDevice() {setProgressBarIndeterminateVisibility(true);setTitle("扫描中");// 添加一个item区分显示信息mDevicesArrayAdapter.add("未配对:");// If we're already discovering, stop itif (mBluetoothAdapter.isDiscovering()) {mBluetoothAdapter.cancelDiscovery();}// 开始扫描,每扫描到一个设备,都会发送一个广播mBluetoothAdapter.startDiscovery();}
  • 点击一个列表item的时候 即表示连接此设备,通过setResult返回该item对应的设备mac地址
    private OnItemClickListener mDeviceClickListener = new OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> av, View v, int arg2, long arg3) {// Cancel discovery because it's costly and we're about to connect// Get the device MAC address, which is the last 17 chars in the ViewString info = ((TextView) v).getText().toString();String noDevices = "没有已配对设备";String noNewDevice = "没有找到蓝牙设备";Log.i("TAG", info);// info 不是我们手动添加的信息 即表示为真实蓝牙设备信息if (!info.equals(noDevices) && !info.equals(noNewDevice) && !info.equals("未配对") && !info.equals("已配对")) {mBluetoothAdapter.cancelDiscovery();//mac 地址String address = info.substring(info.length() - 17);// 设置信息并返回// Set result and finish this ActivityIntent intent = new Intent();intent.putExtra(EXTRA_DEVICE_ADDRESS, address);setResult(Activity.RESULT_OK, intent);finish();}}};
  • 在MainActivity的onActivityResult中获取mac地址,并通过设备连接管理类DeviceConnFactoryManager进行连接
    @Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if (resultCode == RESULT_OK) {//蓝牙连接if (requestCode == BLUETOOTH_REQUEST_CODE) {closePort();//获取蓝牙mac地址String macAddress = data.getStringExtra(BluetoothListActivity.EXTRA_DEVICE_ADDRESS);//初始化DeviceConnFactoryManager 并设置信息new DeviceConnFactoryManager.Build()//设置标识符.setId(id)//设置连接方式.setConnMethod(DeviceConnFactoryManager.CONN_METHOD.BLUETOOTH)//设置连接的蓝牙mac地址.setMacAddress(macAddress).build();//配置完信息,就可以打开端口连接了Log.i("TAG", "onActivityResult: 连接蓝牙" + id);threadPool = ThreadPool.getInstantiation();threadPool.addTask(new Runnable() {@Overridepublic void run() {DeviceConnFactoryManager.getDeviceConnFactoryManagers()[id].openPort();}});}}}
  • 在连接管理类中通过jar包封装的方法进行连接,并通过广播Broadcast把连接状态发送出去
    public void openPort() {deviceConnFactoryManagers[id].isOpenPort = false;sendStateBroadcast(CONN_STATE_CONNECTING);switch (deviceConnFactoryManagers[id].connMethod) {case BLUETOOTH:System.out.println("id -> " + id);mPort = new BluetoothPort(macAddress);isOpenPort = deviceConnFactoryManagers[id].mPort.openPort();break;default:break;}//端口打开成功后,检查连接打印机所使用的打印机指令ESC、TSCif (isOpenPort) {queryCommand();} else {if (this.mPort != null) {this.mPort = null;}sendStateBroadcast(CONN_STATE_FAILED);}}
  • 在MainActivity中接收广播,并根据状态对界面进行显示处理
    private BroadcastReceiver receiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {String action = intent.getAction();if (DeviceConnFactoryManager.ACTION_CONN_STATE.equals(action)) {int state = intent.getIntExtra(DeviceConnFactoryManager.STATE, -1);int deviceId = intent.getIntExtra(DeviceConnFactoryManager.DEVICE_ID, -1);switch (state) {case DeviceConnFactoryManager.CONN_STATE_DISCONNECT:if (id == deviceId) mTvState.setText("未连接");break;case DeviceConnFactoryManager.CONN_STATE_CONNECTING:mTvState.setText("连接中");break;case DeviceConnFactoryManager.CONN_STATE_CONNECTED:mTvState.setText("已连接");Toast.makeText(MainActivity.this, "已连接", Toast.LENGTH_SHORT).show();break;case CONN_STATE_FAILED:mTvState.setText("未连接");Toast.makeText(MainActivity.this, "连接失败!重试或重启打印机试试", Toast.LENGTH_SHORT).show();break;}/* Usb连接断开、蓝牙连接断开广播 */} else if (ACTION_USB_DEVICE_DETACHED.equals(action)) {mHandler.obtainMessage(CONN_STATE_DISCONN).sendToTarget();}}};

6. 蓝牙打印

  • 通过线程池添加打印任务
    public void printLabel() {Log.i("TAG", "准备打印");threadPool = ThreadPool.getInstantiation();threadPool.addTask(new Runnable() {@Overridepublic void run() {...}});}
  • 打印之前也要先做蓝牙状态的判断,只有做到足够的严谨,才能看起来万无一失。
    public void printLabel() {Log.i("TAG", "准备打印");threadPool = ThreadPool.getInstantiation();threadPool.addTask(new Runnable() {@Overridepublic void run() {//先判断打印机是否连接if (DeviceConnFactoryManager.getDeviceConnFactoryManagers()[id] == null ||!DeviceConnFactoryManager.getDeviceConnFactoryManagers()[id].getConnState()) {mHandler.obtainMessage(CONN_PRINTER).sendToTarget();return;}...}});}

这里呢,因为是在子线程,所以通过handler在主线程更新ui或者给个提示

    @SuppressLint("HandlerLeak")private Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case CONN_STATE_DISCONN:if (DeviceConnFactoryManager.getDeviceConnFactoryManagers()[id] != null || !DeviceConnFactoryManager.getDeviceConnFactoryManagers()[id].getConnState()) {DeviceConnFactoryManager.getDeviceConnFactoryManagers()[id].closePort(id);Toast.makeText(MainActivity.this, "成功断开连接", Toast.LENGTH_SHORT).show();}break;case PRINTER_COMMAND_ERROR:Toast.makeText(MainActivity.this, "请选择正确的打印机指令", Toast.LENGTH_SHORT).show();break;case CONN_PRINTER:Toast.makeText(MainActivity.this, "请先连接打印机", Toast.LENGTH_SHORT).show();break;}}};
  • 一切正常,我们开始走打印流程
    public void printLabel() {Log.i("TAG", "准备打印");threadPool = ThreadPool.getInstantiation();threadPool.addTask(new Runnable() {@Overridepublic void run() {//先判断打印机是否连接if (DeviceConnFactoryManager.getDeviceConnFactoryManagers()[id] == null ||!DeviceConnFactoryManager.getDeviceConnFactoryManagers()[id].getConnState()) {mHandler.obtainMessage(CONN_PRINTER).sendToTarget();return;}if (DeviceConnFactoryManager.getDeviceConnFactoryManagers()[id].getCurrentPrinterCommand() == PrinterCommand.TSC) {Log.i("TAG", "开始打印");sendLabel();} else {mHandler.obtainMessage(PRINTER_COMMAND_ERROR).sendToTarget();}}});}
  • 设置打印数据,发送打印数据
    private void sendLabel() {LabelCommand tsc = new LabelCommand();tsc.addSize(40, 30); // 设置标签尺寸,按照实际尺寸设置tsc.addGap(1); // 设置标签间隙,按照实际尺寸设置,如果为无间隙纸则设置为0tsc.addDirection(LabelCommand.DIRECTION.FORWARD, LabelCommand.MIRROR.NORMAL);// 设置打印方向tsc.addQueryPrinterStatus(LabelCommand.RESPONSE_MODE.ON);//开启带Response的打印,用于连续打印tsc.addReference(0, 0);// 设置原点坐标tsc.addTear(EscCommand.ENABLE.ON); // 撕纸模式开启tsc.addCls();// 清除打印缓冲区// 绘制简体中文tsc.addText(30, 30, LabelCommand.FONTTYPE.SIMPLIFIED_CHINESE, LabelCommand.ROTATION.ROTATION_0, LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"这是标题");tsc.addText(200, 30, LabelCommand.FONTTYPE.SIMPLIFIED_CHINESE, LabelCommand.ROTATION.ROTATION_0, LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"序号:" + "1");tsc.addText(30, 90, LabelCommand.FONTTYPE.SIMPLIFIED_CHINESE, LabelCommand.ROTATION.ROTATION_0, LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"价格:" + "99.00");tsc.addText(30, 140, LabelCommand.FONTTYPE.SIMPLIFIED_CHINESE, LabelCommand.ROTATION.ROTATION_0, LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"数量:" + "99");tsc.addText(30, 190, LabelCommand.FONTTYPE.SIMPLIFIED_CHINESE, LabelCommand.ROTATION.ROTATION_0, LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"日期:" + "2020-02-02");// 绘制图片
//        Bitmap b = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
//        tsc.addBitmap(20, 50, LabelCommand.BITMAP_MODE.OVERWRITE, b.getWidth(), b);//二维码tsc.addQRCode(200, 90, LabelCommand.EEC.LEVEL_L, 4, LabelCommand.ROTATION.ROTATION_0, "www.baidu.com");tsc.addPrint(1, 1); // 打印标签tsc.addSound(2, 100); // 打印标签后 蜂鸣器响/* 发送数据 */Vector<Byte> data = tsc.getCommand();if (DeviceConnFactoryManager.getDeviceConnFactoryManagers()[id] == null) {Log.i("TAG", "sendLabel: 打印机为空");return;}DeviceConnFactoryManager.getDeviceConnFactoryManagers()[id].sendDataImmediately(data);}

7. Demo

GitHub 地址:https://github.com/yechaoa/PrinterDemo

【Android -- 蓝牙】蓝牙连接打印机相关推荐

  1. android 蓝牙地址连接打印机,android 连接蓝牙打印机 BluetoothAdapter

    android 连接蓝牙打印机 BluetoothAdapter 源码下载地址:https://github.com/yylxy/BluetoothText.git public class Prin ...

  2. Android 设备接入小票打印机 爱宝小票打印机

    实现android 设备连接小票打印机打印小票的功能时,发现了一个好用的库:https://github.com/AlexMofer/ProjectX/tree/master/printer andr ...

  3. android 如何实现连接蓝牙打印机来实现打印功能

    ============问题描述============ 目前,android 如何实现连接蓝牙打印机来实现打印功能,请大侠来指点, 是否要遵循什么协议标准,还是有设备提供商会提供打印的sdk, 我们 ...

  4. Android 蓝牙连接打印机打印网络图片

    实现蓝牙连接打印机打印网络图片 经过自己一下午加一个小时的时间整理出来,希望能帮助到各位码兄弟! 主要分为以下几步: 将网络图片URL转为bitmap :其中需要进行网络请求,不可在主线程中进行,需另 ...

  5. Android——蓝牙连接打印机

    博客分类: 我的第一个工作Android项目,刚刚完成使用手机连接打印机然后打印小票的功能,单位买了一个类似车载的打印机,非常小巧,打印机的卖家附送了开发使用的手机连接打印机的代码,非常方便. 代码已 ...

  6. android专题-蓝牙扫描、连接、读写

    android专题-蓝牙扫描.连接.读写 概念 外围设备 可以被其他蓝牙设备连接的外部蓝牙设备,不断广播自身的蓝牙名及其数据,如小米手环.共享单车.蓝牙体重秤 中央设备 可以搜索并连接周边的外围设备, ...

  7. Android蓝牙打印服务,Android 模拟蓝牙打印机

    1: 思路 百度百科的介绍 所谓蓝牙打印机,就是指在主机端用一单片机来仿真打印机进行工作,截取从主机并口传出的数据及控制信号,并通过蓝牙无线连接传送到打印机端.在打印机侧的单片机则根据所收到的蓝牙数据 ...

  8. android操作蓝牙打印机(上)

    前言 工作中或多或少都会遇到困扰自己很久的问题,我也毫无例外,曾经在项目中对蓝牙打印这一块也困惑和迷茫过,最近在做项目重构的时候,翻看了之前写的代码,还是决定通过两篇文章详细阐述蓝牙连接打印机完成整个 ...

  9. uniapp APP实现通过蓝牙连接打印机打印

    蓝牙连接德佟打印机打印 1.导入插件: 在插件市场中搜索LPAPI,进入之后,点击右侧的"购买for云打包",选择目标项目,按照提示操作即可: 2. 配置插件: 用HBuilder ...

最新文章

  1. Android怎么不显示手机模型,以编程方式获取Android手机模型
  2. 关于ASP.NET 中站点地图sitemap 的使用【转xugang】
  3. hms能适配鸿蒙吗,国产手机即将抱团?魅族率先使用HMS服务,或多家国产适配鸿蒙!...
  4. matlab save将变量值保存为mat
  5. 2021辽宁大洼高中高考成绩查询,2021大洼高中最后一跑——励志高考,逆袭人生...
  6. SpringCloud 入门教程(四): 分布式环境下自动发现配置服务
  7. jQuery 往table添加新内容有以下四个方法:
  8. suse linux ftp家目录,Suse linux下控制ftp用户访问目录
  9. 用计算机探索奥秘规律例题,计算器指法练习题.doc
  10. 感慨公交车766路调整
  11. 原来做浏览器这么简单
  12. Android Studio开发实战(新手入门)一
  13. MySQL ODBC驱动简介
  14. 如何下载贵州省卫星地图高清版大图
  15. msm8937之串口dts配置
  16. ORA-12162: TNS:net service name is incorrectly specified 错误解决
  17. Latex排版大括号让其左对齐
  18. java开发app教程,就是这么简单
  19. 深圳-珠海-澳门-香港四日游攻略
  20. 将.pyc反编译成.py

热门文章

  1. Hertz椭球接触计算公式
  2. 6.6 创建选项卡窗体(上)
  3. CFA中国之队国际青年足球锦标赛
  4. Ubuntu下利用Conda创建虚拟环境并安装Pytorch各版本教程(妈妈再也不用担心我在家还不学习了系列三)
  5. 《妈妈再也不用担心我的学习系列》之RabbitMQ动态修改队列名
  6. 使用.Net获取OLEDB数据库的架构.
  7. Linux C++ 通信 - 信号的概念、认识、处理动作
  8. 全方位揭秘!大数据从0到1的完美落地之Mysql操作DQL
  9. 【Redis】缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存击穿、缓存降级
  10. ASP.NET Web程序设计习题与练习答案-祁长兴主编版