前Android时代,是应用层面大量C端App百花齐放时代 ,现已经被夸端React Native,Flutter,UniApp 等逐渐蚕食
后Android时代,一定是智能硬件、智能设备时代,大数据可视化时代
前言
在前面文章已经介绍了Android串口,USB,打印机,扫码枪,支付盒子,键盘,鼠标,U盘等开发使用一网打尽的用法,今天我们来简单介绍普通蓝牙打印机,和Android 系统上双屏异显的使用。 蓝牙及蓝牙音乐,蓝牙实时传输音视频将在后面文章中进行介绍
一、普通蓝牙打印机
- 蓝牙打印机使用步骤:权限-->搜索-->连接-->发送打印指令
- 需要权限:
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;
- 打开蓝牙搜索前需要注册蓝牙监听广播:
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);//去开始搜索蓝牙
}
- 蓝牙广播监听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();
}
}
}
}
- 当已经匹配到某蓝牙设备时候:
需要判断找出该蓝牙设备是否是蓝牙打印机:
多个蓝牙打印机可以通过设备里面的: 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);
}
}
}
- 蓝牙设备封装的实体类:包含:蓝牙名称,蓝牙地址,蓝牙设备类型,蓝牙是否匹配
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();
}
}
- 现在蓝牙打印设备已经拿到了,执行蓝牙打印需要拿到
蓝牙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) {
//异常处理
}
}
}
}
- 普通蓝牙打印指令:都是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。
- 使用前需要申请窗口权限:
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)
}
- 查看
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)!!
}
- 如果想让屏幕长期存在可以让该
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 如下:
- 其他使用和普通Dialog一样
三、双屏之副屏分辨率调成和主屏一样
在双相同尺寸大屏收银机,收银秤设备中往往副屏显示出来的分辨率和主屏显示出来的分辨率不一样, 怎么解决?怎么让其显示成和主屏看到的效果一样。
应用层解决方案:
让其副屏的布局用主屏异步加载,使用主屏的Context.LayoutInflater,布局加载完成后存好布局,副屏初始化完成之后,直接使用该布局显示。
四、总结:
本文重点介绍了
Android中普通蓝牙打印机怎么打印的
Android中多屏异显怎么实现的 Android中怎么解决副屏的分辨率和主屏一样。