轻松搞定Android蓝牙打印机,双屏异显及副屏分辨率适配解决办法

前Android时代,是应用层面大量C端App百花齐放时代 ,现已经被夸端React Native,Flutter,UniApp 等逐渐蚕食
后Android时代,一定是智能硬件、智能设备时代,大数据可视化时代

前言

在前面文章已经介绍了Android串口,USB,打印机,扫码枪,支付盒子,键盘,鼠标,U盘等开发使用一网打尽的用法,今天我们来简单介绍普通蓝牙打印机,和Android 系统上双屏异显的使用。 蓝牙及蓝牙音乐,蓝牙实时传输音视频将在后面文章中进行介绍

一、普通蓝牙打印机

  1. 蓝牙打印机使用步骤:权限-->搜索-->连接-->发送打印指令
  2. 需要权限:
ini 复制代码
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

android 12以后需要更多的权限:

ini 复制代码
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

3.使用蓝牙是需要用到蓝牙适配器:BluetoothAdapter

arduino 复制代码
//蓝牙适配器
private BluetoothAdapter mBluetoothAdapter;
  1. 打开蓝牙搜索前需要注册蓝牙监听广播:
scss 复制代码
            if (mBluetoothAdapter == null) {
                mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
            }
            if (!mBluetoothAdapter.isEnabled()) {
                mBluetoothAdapter.enable();
            }
            if (mBlueToothBroadcastReceiver == null) {
                mBlueToothBroadcastReceiver = new BlueToothBroadcastReceiver();
                //广播注册
                IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
                filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
                filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
                filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
                filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
                filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
                filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
                filter.addAction(SEARCH_TLUE_TOOTH_ACTION);//自定义一个开始搜搜广播action
                filter.addAction(PRINTE_ACTION);
                filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);

                filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
                filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
                filter.addAction(USBPrinter.ACTION_USB_PERMISSION);
                registerReceiver(mBlueToothBroadcastReceiver, filter);
                Intent intent = new Intent();
                intent.setAction(SEARCH_TLUE_TOOTH_ACTION);
                sendBroadcast(intent);//去开始搜索蓝牙
           }     
  1. 蓝牙广播监听BlueToothBroadcastReceiver的实现: 可以监听到:
    自己蓝牙开关变化状态
    蓝牙搜索完成
    某蓝牙设备已经连接
    某蓝牙设备已经断开
    已经匹配的设备
csharp 复制代码
public class BlueToothBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                    BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                    if (device == null) {
                        return;
                    }
                    //已经匹配的设备
                    if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
                        addBluetoothDevice(device);
                    } else { //没有匹配的设备

                    }
                } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { 
                     //搜索完成进行处理
                } else if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)) { 
                     //某蓝牙设备已经连接
                } else if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) { 
                     //某蓝牙设备已经断开
                } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { 
                     //自己设备蓝牙状态
                    int blueState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0);
                    switch (blueState) {
                        case BluetoothAdapter.STATE_OFF:
                            //蓝牙已关闭,请重新打开
                            break;
                        case BluetoothAdapter.STATE_ON:
                            //蓝牙已开启
                            if (mBluetoothAdapter != null) {
                                mBluetoothAdapter.startDiscovery();//开始搜索蓝牙
                            }
                            break;
                    }
                } else if (SEARCH_TLUE_TOOTH_ACTION.equals(action)) {
                     //执行搜索藍牙
                    if (mBluetoothAdapter != null) {
                        mBluetoothAdapter.startDiscovery();
                    }
               }
        }
    }
  1. 当已经匹配到某蓝牙设备时候:

需要判断找出该蓝牙设备是否是蓝牙打印机:

多个蓝牙打印机可以通过设备里面的: pID,vID进行区分

ini 复制代码
private void addBluetoothDevice(BluetoothDevice device) {
    if (device != null) {
        if (device.getBluetoothClass().getDeviceClass() == PRINT_TYPE) {
            //PRINT_TYPE 普通蓝牙打印机 该值1664 是固定的,
            //int pid = device.getProductId();//多个蓝牙打印机 需要该值进行判断
            //int vid = device.getVendorId();//多个蓝牙打印机 需要该值进行判断
            mPrintBean = null;
            mPrintBean = new PrintBean(device);
        }
    }
}
  1. 蓝牙设备封装的实体类:包含:蓝牙名称,蓝牙地址,蓝牙设备类型,蓝牙是否匹配
arduino 复制代码
/**
 * 类说明:蓝牙设备的实体类
 * wgllss  2017/1/27  19:57
 */
public class PrintBean {
    public static final int PRINT_TYPE = 1664;
    //蓝牙-名称
    public String name;
    //蓝牙-地址
    public String address;
    //蓝牙-设备类型
    public int type;
    //蓝牙-是否已经匹配
    public boolean isConnect;

    BluetoothDevice device;

    /**
     * @param devicee 蓝牙设备对象
     */
    public PrintBean(BluetoothDevice devicee) {
        this.name = TextUtils.isEmpty(devicee.getName()) ? "未知" : devicee.getName();
        this.address = devicee.getAddress();
        this.isConnect = devicee.getBondState() == BluetoothDevice.BOND_BONDED;
        this.type = devicee.getBluetoothClass().getDeviceClass();
    }
}
  1. 现在蓝牙打印设备已经拿到了,执行蓝牙打印需要拿到 蓝牙socket对象,就可以执行蓝牙打印发送蓝牙打内容了
arduino 复制代码
//蓝牙socket对象
private BluetoothSocket mmSocket;
php 复制代码
class ConnectRunnable implements Runnable {

    //打印内容
    private String content;
   
    public ConnectRunnable(BluetoothDevice device, String content) {
        this.content = content;
        try {
            if (mmSocket == null || !mmSocket.isConnected()) {
                mmSocket = device.createRfcommSocketToServiceRecord(uuid);
            }
        } catch (Exception e) {
           //异常处理
        }
    }

    @Override
    public void run() {
        try {
            if (content != null && content.length() > 2) {
                try {
                    Thread.sleep(300);
                } catch (Exception e) {

                }
                if (mBluetoothAdapter != null) { //取消的发现,因为它将减缓连接
                    mBluetoothAdapter.cancelDiscovery();
                }
                //连接socket
                if (mmSocket != null && !mmSocket.isConnected()) {
                    mmSocket.connect();
                    print(content, mmSocket.getOutputStream());
                    mmSocket.close();
                    try {
                        Thread.sleep(2000);
                    } catch (Exception e) {

                    }
                }
            }
        } catch (ConnectException connectException) {
           //异常处理
        } catch (IOException iOException) {
           //异常处理
        } catch (Exception e) {
           //异常处理
        } finally {
            try {
                if (mmSocket != null) {
                    mmSocket.close();
                }
                mmSocket = null;
            } catch (Exception e) {
               //异常处理
            }
        }
    }
}
  1. 普通蓝牙打印指令:都是ESC打印指令:包含文字,二维码,切刀,语音播报等相关如下:

设置打印格式 及相关指令

ini 复制代码
/**
* 设置打印格式
*
* @param command 格式指令
*/
public static void selectCommand(OutputStream outputStream,byte[] command) {
   try {
       outputStream.write(command);
       outputStream.flush();
   } catch (Exception e) {
       e.printStackTrace();
   }
}

/**
* 复位打印机
*/
public static final byte[] RESET = {0x1b, 0x40};

/**
* 左对齐
*/
public static final byte[] ALIGN_LEFT = {0x1b, 0x61, 0x00};

/**
* 中间对齐
*/
public static final byte[] ALIGN_CENTER = {0x1b, 0x61, 0x01};

/**
* 右对齐
*/
public static final byte[] ALIGN_RIGHT = {0x1b, 0x61, 0x02};

/**
* 选择加粗模式
*/
public static final byte[] BOLD = {0x1b, 0x45, 0x01};

/**
* 取消加粗模式
*/
public static final byte[] BOLD_CANCEL = {0x1b, 0x45, 0x00};

/**
* 宽高加倍
*/
public static final byte[] DOUBLE_HEIGHT_WIDTH = {0x1d, 0x21, 0x11};

/**
* 宽加倍
*/
public static final byte[] DOUBLE_WIDTH = {0x1d, 0x21, 0x10};

/**
* 高加倍
*/
public static final byte[] DOUBLE_HEIGHT = {0x1d, 0x21, 0x01};

/**
* 字体不放大
*/
public static final byte[] NORMAL = {0x1d, 0x21, 0x00};

/**
* 设置默认行间距
*/
public static final byte[] LINE_SPACING_DEFAULT = {0x1b, 0x32};

打印完切纸操作

ini 复制代码
/**
 * 切紙
 */
public static void cut(OutputStream outputStream) {
    try {
        Thread.sleep(600);
        byte[] box = new byte[6];
        box[0] = 0x1B;
        box[1] = 0x64;
        box[2] = 0x01;
        box[3] = 0x1d;
        box[4] = 0x56;
        box[5] = 0x31;
        outputStream.write(box);
        outputStream.flush();
        outputStream.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

打印机收到打印时候语言播报指令(飞蛾打印机的)

scss 复制代码
 public static void printAudio(OutputStream outputStream,int n){
        try {
            OutputStreamWriter writer = new OutputStreamWriter(outputStream, "gbk");
            writer.write(0x1f);// init
            writer.write(0x12);// adjust height of barcode
            writer.write( 0x0f ); // pl
            writer.write(0); // 你有新的订单请及时处理
//            writer.write(1); // 你有新的美团外卖订单请查询
//            writer.write(2); // 您有新的了么订单,请及时查询
//            writer.write(3); // 您有百度外卖订单
//            writer.write(4); // 有用户申请提交订单了
//            writer.write(5); // 有用户申请退单了
//            writer.write(6); // 缺纸请重新装纸
            writer.flush();
        } catch (Exception e) {
             e.printStackTrace();
        }
    }

打印文字内容:

arduino 复制代码
/**
 * 打印文字
 *
 * @param text 要打印的文字
 */
public static void printText(OutputStream outputStream,String text) {
    try {
        byte[] data = text.getBytes("gbk");
        outputStream.write(data, 0, data.length);
        outputStream.flush();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

打印二维码:

scss 复制代码
/**
 * 打印二維碼
 *
 * @param data
 */
public static void printCode(OutputStream outputStream,String data) {
    try {
        OutputStreamWriter writer = new OutputStreamWriter(outputStream, "gbk");
        int moduleSize = 8;
        int length = data.getBytes("gbk").length;

        //打印二维码矩阵
        writer.write(0x1D);// init
        writer.write("(k");// adjust height of barcode
        writer.write(length + 3); // pl
        writer.write(0); // ph
        writer.write(49); // cn
        writer.write(80); // fn
        writer.write(48); //
        writer.write(data);

        writer.write(0x1D);
        writer.write("(k");
        writer.write(3);
        writer.write(0);
        writer.write(49);
        writer.write(69);
        writer.write(48);

        writer.write(0x1D);
        writer.write("(k");
        writer.write(3);
        writer.write(0);
        writer.write(49);
        writer.write(67);
        writer.write(moduleSize);

        writer.write(0x1D);
        writer.write("(k");
        writer.write(3); // pl
        writer.write(0); // ph
        writer.write(49); // cn
        writer.write(81); // fn
        writer.write(48); // m

        writer.flush();
    } catch (Exception e) {
        //Toast.makeText(this.context, "发送失败!", Toast.LENGTH_SHORT).show();
        e.printStackTrace();
    }
}

二、双屏异显

双屏及多屏在车载,收银机上面,收银秤上面,及相关智能多屏设备中应用得相当广泛。 Android中使用多屏开发用到的是Presentation类型的Diglog,它本质上是个Dialog。

  1. 使用前需要申请窗口权限:
ini 复制代码
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />

申请窗口显示在其他应用上面权限

less 复制代码
if (Settings.canDrawOverlays(this@MainActivity)) {
    start(savedInstanceState)
    lifecycleScope.launch {
        dataStoreUtilsL.get().putData(AppConfig.IS_INIT_APP_KEY, true)
    }
} else {
    val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
    intent.data = Uri.parse("package:$packageName")
    overLayPermission.launch(intent)
}
  1. 查看Presentation源码可以看到,显示该副屏时需要该屏的 Display

3. 使用前先准备好Display

kotlin 复制代码
fun getPresentationDisplays(context: Context): Display {
    var displays: Array<Display>? = null//屏幕数组
    if (displays == null) {
        val mDisplayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
        mDisplayManager?.displays?.takeIf {
            it.size > 1
        }?.let {
            displays = it
        }
    }
    displays?.forEach { it ->
        it.takeIf {
            (it.flags and Display.FLAG_SECURE != 0 && it.flags and Display.FLAG_SUPPORTS_PROTECTED_BUFFERS != 0 && it.flags and Display.FLAG_PRESENTATION != 0)
        }?.let {
            return it
        }
    }
    return displays?.get(0)!!
}
  1. 如果想让屏幕长期存在可以让该Presentation放在android中的Service里面,并且在show方法之前设置window的type,也可以在构造方法中将该Type传进去
scss 复制代码
override fun show() {
    window?.apply {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
            setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY)
        else
            setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT)
        super.show()
    }
    mLifecycleRegistry?.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
}

从构造方法传window的Type 如下:

  1. 其他使用和普通Dialog一样

三、双屏之副屏分辨率调成和主屏一样

在双相同尺寸大屏收银机,收银秤设备中往往副屏显示出来的分辨率和主屏显示出来的分辨率不一样, 怎么解决?怎么让其显示成和主屏看到的效果一样。

应用层解决方案:

让其副屏的布局用主屏异步加载,使用主屏的Context.LayoutInflater,布局加载完成后存好布局,副屏初始化完成之后,直接使用该布局显示。

四、总结:

本文重点介绍了

Android中普通蓝牙打印机怎么打印的

Android中多屏异显怎么实现的 Android中怎么解决副屏的分辨率和主屏一样。

感谢阅读:

欢迎 关注,点赞、收藏

这里你会学到不一样的东西

相关推荐
一个儒雅随和的男子1 小时前
微服务详细教程之nacos和sentinel实战
微服务·架构·sentinel
TroubleMaker1 小时前
OkHttp源码学习之retryOnConnectionFailure属性
android·java·okhttp
叶羽西3 小时前
Android Studio IDE环境配置
android·ide·android studio
发飙的蜗牛'3 小时前
23种设计模式
android·java·设计模式
Hello Dam4 小时前
面向微服务的Spring Cloud Gateway的集成解决方案:用户登录认证与访问控制
spring cloud·微服务·云原生·架构·gateway·登录验证·单点登录
AI人H哥会Java11 小时前
【Spring】Spring的模块架构与生态圈—Spring MVC与Spring WebFlux
java·开发语言·后端·spring·架构
小屁不止是运维11 小时前
麒麟操作系统服务架构保姆级教程(二)ssh远程连接
linux·运维·服务器·学习·架构·ssh
不会写代码的女程序猿11 小时前
关于ETL的两种架构(ETL架构和ELT架构)
数据仓库·架构·etl
花追雨13 小时前
Android -- 双屏异显之方法一
android·双屏异显
小趴菜822713 小时前
安卓 自定义矢量图片控件 - 支持属性修改矢量图路径颜色
android