Android 蓝牙打印

1、Android 12 蓝牙权限(解决闪退问题)

项目配置是compileSdkVersion31 targetSdkVersion30 手机升级了Android 12后运行Crash。照理说targetSdkVersion没有用31,怎么会报Android 12的蓝牙权限错误,百思不得其解,先来看看Android12的蓝牙权限。

1.Manifest权限

<manifest>   

<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

<!--请求旧设备上的蓝牙权限,可设置android:maxSdkVersion为30。这个兼容性步骤帮助系统只授予你的应用程序所需的蓝牙权限,当安装在运行Android 12或更高的设备上。>   

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

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

<!-- 有蓝牙扫描功能的时候需要。 -->

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

<!--如果你的应用程序不使用蓝牙扫描结果得出物理位置信息,可以加上usesPermissionFlags声明你的scan结果不会用于推导出物理位置。--> 

<uses-permission android:name="android.permission.BLUETOOTH_SCAN"                     android:usesPermissionFlags="neverForLocation" />

<!--仅当你的应用程序使设备可被蓝牙设备发现时需要。 -->   

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

<!-- 只有当你的应用程序与已经配对的蓝牙设备通信时才需要。 -->   

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

<!-- 只有当你的应用程序使用蓝牙扫描结果来获取物理位置时才需要。--> 

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

<!--在Android 12 如果你确定你的应用程序从来没有从蓝牙扫描结果获取物理位置,则不需要这个权限。-->  

<!--在Android 12以下 这个权限是必须要的-->  

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

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

<!--如果你的应用程序支持一个服务,并且可以运行在Android 10 (API级别29)或Android 11,你还必须声明ACCESS_BACKGROUND_LOCATION权限来发现蓝牙设备。-->

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

</manifest>

2.动态请求相关蓝牙权限

BLUETOOTH_ADVERTISE、BLUETOOTH_CONNECT和BLUETOOTH_SCAN权限是运行时权限。因此必须在应用程序中明确请求用户批准,才能查找蓝牙设备,使设备可被其他设备发现,或与已经配对的蓝牙设备通信。当你的应用程序请求这些权限中的至少一个,系统提示用户允许你的应用程序访问附近的设备,如图1所示。

//所以请加上这段代码

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {

    if (!checkSinglePermission(Manifest.permission.BLUETOOTH_SCAN) ||!checkSinglePermission(Manifest.permission.BLUETOOTH_CONNECT)) {

        String[] list =new String[]{Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT};

        requestPermissions(list, Constants.PERMISSION_REQUEST_SCAN);

    }

}

3.为什么闪退

先完成1,2两步, 然后再去做各种蓝牙有关的操作,不然在Android 12 手机上会直接闪退!

2、Android sdk版本过高解决方法
    <!--蓝牙权限-->
    <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30"/>
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30"/>
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
    <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>


3、PrintUtils
package com.asura.android.tmspda.util.print

import android.Manifest
import android.app.Activity
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.os.Build
import android.text.TextUtils
import android.util.Log
import androidx.core.app.ActivityCompat

import com.github.dfqin.grantor.PermissionListener
import com.github.dfqin.grantor.PermissionsUtil
import com.kana.crazytv.app.util.LiveDataKeyUtils
import com.lyy.bluetoothdemo.tools.BluetoothBean
import com.lyy.bluetoothdemo.tools.CommonDialogUtil
import com.lyy.bluetoothdemo.tools.LiveDataBus
import com.lyy.bluetoothdemo.tools.ToastUtils


/**
 * 打印工具
 */
object PrintUtils {
    var bluetoothDataList = arrayListOf<BluetoothBean>()
    var bluetoothDataAddress = arrayListOf<String>()
    var bluetoothAdapter: BluetoothAdapter? = null
    //来源,是设置页面,不需要弹窗了
    var currentSource = 0

    /**
     * 获取蓝牙权限
     * source: 0是不处理;1是设置页面
     */
    fun openBluetoothPermissions(context: Activity,source:Int=0) {
        currentSource = source
        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
        //获取权限
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            PermissionsUtil.requestPermission(
                context,
                object : PermissionListener {
                    override fun permissionGranted(permission: Array<out String>) {
                        //蓝牙列表
                        bluetoothList(context)
                    }

                    override fun permissionDenied(permission: Array<out String>) {
                        ToastUtils.showToast(context, "用户拒绝了蓝牙权限")
                        PermissionsUtil.gotoSetting(context)
                    }
                },
                Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT,
            )

        } else {
            //蓝牙列表
            bluetoothList(context)
        }
    }

    /**
     * 蓝牙列表
     */
    fun bluetoothList(context: Activity) {
        if (bluetoothAdapter != null) {
            if (bluetoothAdapter!!.isEnabled) {
                // 蓝牙已开启
              //  LogUtils.debugInfo("蓝牙已开启")
                //获取蓝牙列表
                openBluetooth(context)
            } else {
                // 蓝牙未开启
                ToastUtils.showToast(context, "请打开蓝牙")
                val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
                if (ActivityCompat.checkSelfPermission(
                        context,
                        Manifest.permission.BLUETOOTH_CONNECT
                    ) == PackageManager.PERMISSION_GRANTED
                ) {

                } else {
                    context.startActivityForResult(enableBtIntent, 200)
                }
            }
        } else {
            // 设备不支持蓝牙
            ToastUtils.showToast(context, "设备不支持蓝牙")
        }
    }

    /**
     * 获取蓝牙列表
     */
     fun openBluetooth(context: Context) {
        if (ActivityCompat.checkSelfPermission(
                context,
                Manifest.permission.BLUETOOTH_CONNECT
            ) == PackageManager.PERMISSION_GRANTED
        ) {
            val pairedDevices: Set<BluetoothDevice>? = bluetoothAdapter?.bondedDevices
            Log.d("lyy","-----蓝牙已链接--------${pairedDevices?.size}")
            if (pairedDevices != null && pairedDevices.isNotEmpty()) {
                bluetoothDataList.clear()
                bluetoothDataAddress.clear()
                if (currentSource==0){
                    CommonDialogUtil.commonBluetoothList(context)
                }
                for (device in pairedDevices) {
                    // 处理已配对设备(Android 13)
                    val deviceName = device.name
                    val deviceAddress = device.address
                    Log.d("lyy","Device Name: $deviceName, Device Address: $deviceAddress")
                    if (!TextUtils.isEmpty(deviceName)) {
                        var isExist :Boolean  = bluetoothDataAddress.any { it == deviceAddress }
                        if (!isExist) {
                            bluetoothDataAddress.add(deviceAddress)
                            var bluetoothBean = BluetoothBean(deviceName, deviceAddress, false)
                            bluetoothDataList.add(bluetoothBean)
                            LiveDataBus.get()
                                .with(LiveDataKeyUtils.BLUETOOTH_REFRESH, String::class.java)
                                .postValue("")
                        }
                    }
                }
            }
        } else {
            val startDiscovery = bluetoothAdapter?.startDiscovery()
            Log.d("lyy","-----蓝牙未链接--------${startDiscovery}")
            registerBluetoothReceiver(context)
        }

    }

    private fun registerBluetoothReceiver(context: Context) {
        //filter注册广播接收器
        var filter = IntentFilter();
        //蓝牙当前状态
        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
        //开始扫描蓝牙设备广播
        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
        //找到蓝牙设备广播
        filter.addAction(BluetoothDevice.ACTION_FOUND);
        //扫描蓝牙设备结束广播
        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
        //蓝牙设备配对状态改变广播
        filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
        //设备扫描模式改变广播
        filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
        context.registerReceiver(broadcastReceiver, filter);
    }

    private val broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            val action = intent.action
            Log.d("lyy","-----action--------${action}")
            //开始查找设备
            if (BluetoothAdapter.ACTION_DISCOVERY_STARTED == action) {
                //找到蓝牙设备
                bluetoothDataList.clear()
                bluetoothDataAddress.clear()
                Log.d("lyy","开始查找...")
                if (currentSource==0){
                    CommonDialogUtil.commonBluetoothList(context)
                }
            } else if (BluetoothDevice.ACTION_FOUND == action) {
                //搜到蓝牙设备
                val device =
                    intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
                //把搜索到的设备添加到已找到列表中,显示它的信息
                if (ActivityCompat.checkSelfPermission(
                        context,
                        Manifest.permission.BLUETOOTH_CONNECT
                    ) != PackageManager.PERMISSION_GRANTED
                ) {
                }
                var deviceName = device?.name ?: ""
                var address = device?.address ?: ""
                Log.d("lyy","设备名:${deviceName} 地址:${address}")
                if (!TextUtils.isEmpty(deviceName)) {
                  var isExist :Boolean  = bluetoothDataAddress.any { it == address }
                  if (!isExist) {
                      bluetoothDataAddress.add(address)
                      var bluetoothBean = BluetoothBean(deviceName, address, false)
                      bluetoothDataList.add(bluetoothBean)
                      LiveDataBus.get().with(LiveDataKeyUtils.BLUETOOTH_REFRESH, String::class.java)
                          .postValue("")
                  }
                }
            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED == action) {
                //搜索完毕
                Log.d("lyy","查找结束...")
            }
        }
    }


}


4、PrintDataUtils

package com.lyy.bluetoothdemo.tools

import android.app.Activity
import android.graphics.Bitmap
import android.text.TextUtils
import com.asura.android.tmspda.util.print.PrintUtils
import com.google.zxing.WriterException
import com.kana.crazytv.app.util.MmkvConstantUtils
import com.lyy.bluetoothdemo.databinding.LayoutPrintR8LabelBinding
import com.lyy.bluetoothdemo.tools.*
import zp_print_bigdataSDK.zpPrinter

object PrintDataUtils {
    /**
     * 打印模版
     */
    fun printTemplate1(context: Activity) {
        try {
            var address = MmkvUtils.decodeString(MmkvConstantUtils.LAST_BLUETOOTH_ADDRESS)
           // var address = "C0:40:56:24:98:96"
//            var address = "123456"
            if (TextUtils.isEmpty(address)){
                PrintUtils.openBluetoothPermissions(context)
            }else{
                val zpSDK = zpPrinter(context)
                if (!zpSDK.connect(address)) {
                    ToastUtils.showToast(context,"蓝牙连接失败,请检查设备或重启")
                    PrintUtils.openBluetoothPermissions(context)
                } else {

                    val binding: LayoutPrintR8LabelBinding = LayoutPrintR8LabelBinding.inflate(context.layoutInflater)
                    if (!TextUtils.isEmpty("123456789")) {
                        try {
                            binding.icQrCode.setImageBitmap(CodeCreateUtil.CreateTwoDCode("123456789")) //receiptBean.getBoxCode()
                        } catch (e: WriterException) {
                            ToastUtils.showToast(context, "${e.message}")
                        }
                    }
                    binding.tvTag1.setText("RG") //RG
                    binding.tvTag2.setText("佛山仓") //订单发货
                    binding.tvTag3.setText("哈哈哈") //仓库
                    if (true) {
                        //是企拍
                        binding.tvTag4.setText("企拍")
                        //是否显示地址
                        binding.tvAddress.setText("提货地址:上海市-闵行区-剑川路-268号") //proxyAddress
                    } else {
                        //不是企拍
                        binding.tvTag4.setText("")
                        binding.tvAddress.setText("")
                    }
                    binding.tvBoxCode.setText("箱码:12345667")
                    binding.tvRelatedNumber.setText("单号:1234567788") //orderNo
                    binding.tvSupplierCode.setText("编号:1234567788") //getStoreCode
                    binding.tvShop2.setText("dhhdhd") //
                    binding.tvReceiptUser.setText("成都五金店") //firstCell
                    binding.tvOrderTime.setText("2024/01/17 12:01:21") //secondCell,不要加下单时间

                    //tv_receipt_address
                    binding.tvReceiptAddress.setText("无锡仓") //targetStation
                    binding.tvFlag.setText("2" + "-" + "6")
                    binding.tvReceiptFlag.setText("H890") //集货位collectAreaCode

                    //是BDT,文字要变小
                    binding.tvReceiptAddress.setTextSize(ImageUtil.px2dp(context,40f).toFloat())
                    binding.tvReceiptFlag.setTextSize(ImageUtil.px2dp(context,40f).toFloat())

                    //布局不旋转(竖向时使用)
                    //Bitmap bitmap = ViewToImageUtil.viewToBitMapFor100x80mm(binding.getRoot());
                    //布局旋转90度(横向时使用)
                    val bitmap: Bitmap =
                        ViewToImageUtil.rotateBitmap(ViewToImageUtil.viewToBitMapFor870x500(binding.getRoot()))
                    ToastUtils.showToast(context,"蓝牙连接成功")
                    zpSDK.Draw_Page_Bitmap_(bitmap, 0)
                    //是否走纸
                    var paperFeed = MmkvUtils.decodeBoolean(MmkvConstantUtils.PAPER_FEED)
                    if (paperFeed) {
                        zpSDK.Write(byteArrayOf(0x1d, 0x0c))
                    }
                    zpSDK.printerStatus()
                    zpSDK.disconnect()
                }
            }

        } catch (e: Exception) {
            e.printStackTrace()
            ToastUtils.showToast(context,"${e.message}")
            PrintUtils.openBluetoothPermissions(context)
        }
    }
}

5、高版本手机蓝牙打印的问题
  • 1、用户需要手动打开蓝牙开关;
  • 2、用户还要点击设置->应用设置->应用管理->找到项目(BluetoothDemo)->点击权限管理->开启蓝牙。

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题
图片

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值