解决android连接IP打印机时,长时间不操作,Socket断开问题

最近参与公司的餐厅接单项目,遇到了一个坑


android手机连接IP打印机,一段时间没有接到打印任务,Socket会被android系统给断开掉
网上有说使用PowerManager 设置wakeLock

  try {
            if (wl!=null)
                PowerManager pm = (PowerManager) context.getApplicationContext().getSystemService(Context.POWER_SERVICE);
            if (pm!=null) {
                wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "app:ScreenOff");
                wl.acquire( /*100 minutes*/);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

然而对于我的项目并不起作用,大概5分钟以上不打印,Socket依然会被断开


既然无法保证Socket一定不会断开,就只能每次在打印之前,获取到socket的连接状态,如果已经断开,重新连接,保证连接成功后,再次进行打印

但是socket类的方法isClosed()、isConnected()、isInputStreamShutdown()、isOutputStreamShutdown()等,这些方法都是本地端的状态,无法判断远端是否已经断开连接。

后来看到这篇文章
https://coral0212.iteye.com/blog/1810905
如果想要知道Socket的实时是否已断开, socket.sendUrgentData(0xFF);采用此方法,发心跳包,如果已断开,则会抛出IOException,否则就表示已连接!

 public void sendDataImmediately(final Vector<Byte> data) {
        if (this.mPort == null) {
            return;
        }

        if (mPort instanceof EthernetPort) {//如果是网络连接
            try {
                EthernetPort  port = (EthernetPort) mPort;
                Class clz = port.getClass();

                Field mSocket = clz.getDeclaredField("mSocket");
                mSocket.setAccessible(true);
                Socket socket = (Socket) mSocket.get(port);
                socket.sendUrgentData(0xFF);//心跳包
                Log.e(TAG, "sendDataImmediately: socket is connected");
                SharedPreferencesUtil.putString(BaseApplication.getInstance(), "connected", "time is :" + DateUtil.getFormatString(Calendar.getInstance(), "yyyy-MM-dd hh:mm:ss:SSS"));
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
                Log.e(TAG, "sendDataImmediately:", e);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
                Log.e(TAG, "sendDataImmediately:", e);
            } catch (IOException e) {
                e.printStackTrace();
                Log.e(TAG, "sendDataImmediately: socket is disconnected!", e);
                openWifiPort();
                SharedPreferencesUtil.putString(BaseApplication.getInstance(), "disconnected", "time is :" + DateUtil.getFormatString(Calendar.getInstance(), "yyyy-MM-dd hh:mm:ss:SSS"));
            } catch (NullPointerException e) {
                e.printStackTrace();
                Log.e(TAG, "sendDataImmediately: socket is disconnected!", e);
                openWifiPort();
                SharedPreferencesUtil.putString(BaseApplication.getInstance(), "disconnected", "time is :" + DateUtil.getFormatString(Calendar.getInstance(), "yyyy-MM-dd hh:mm:ss:SSS"));
            }
            writeDataByWifi(mPort, data);
        } else {
            try {
                Log.e(TAG, "data -> " + new String(com.gprinter.utils.Utils.convertVectorByteTobytes(data), "gb2312"));
                this.mPort.writeDataImmediately(data, 0, data.size());
            } catch (IOException e) {
                e.printStackTrace();
                Log.e(TAG, "sendDataImmediately: writeDataImmediately error", e);
            }
        }

    }

来来来接着写,测试反馈还是不行,在socket被断开后,即使使用 socket.sendUrgentData(0xFF);发送新心跳包方式查询到已断开,但是尝试重新连接会经常出现连接失败,必须得把打印机开关关闭后再打开后,才能重新连接上...莫名其妙的一个问题!

既然通过查询连接状态然后重连走不通了,只能再想其他方式了

于是想到可不可以开启个线程隔几秒给打印机发送查询指令就相当于给打印机发送心跳包了,这样长连接就不会被断开了吧

于是我在socket打开端口成功之后,开启线程,定时给打印机发送查询指令,从而充当心跳的作用

打开端口方法

 /**
     * 打开端口
     *
     * @return
     */
    public void openPort() {
        isOpenPort = false;
        sendStateBroadcast(CONN_STATE_CONNECTING);
        switch (connMethod) {
            case BLUETOOTH:
                System.out.println("id -> " + id);
                mPort = new BluetoothPort(macAddress);
                isOpenPort = mPort.openPort();

                break;
            case USB:
                mPort = new UsbPort(mContext, mUsbDevice);
                isOpenPort = mPort.openPort();
                if (isOpenPort) {
                    IntentFilter filter = new IntentFilter(ACTION_USB_DEVICE_DETACHED);
                    mContext.registerReceiver(usbStateReceiver, filter);
                }
                break;
            case WIFI:
                mPort = new EthernetPort(ip, port);
                isOpenPort = mPort.openPort();
                break;
            case SERIAL_PORT:
                mPort = new SerialPort(serialPortPath, baudrate, 0);
                isOpenPort = mPort.openPort();
                break;
            default:
                break;
        }
        //端口打开成功后,检查连接打印机所使用的打印机指令ESC、TSC
        if (isOpenPort) {
            queryCommand();
        } else {
            sendStateBroadcast(CONN_STATE_FAILED);
        }
    }

开启线程发送查询指令,充当心跳

 /**
     * 查询当前连接打印机所使用打印机指令
     */
    private void queryCommand() {
      /**
     * 查询当前连接打印机所使用打印机指令(ESC(EscCommand.java)、TSC(LabelCommand.java))
     */
    private void queryCommand() {
        //开启读取打印机返回数据线程
        readPrinterState();
        //查询打印机实时状态
        queryPrinterState();
    }
    }
    /**
     * ESC查询打印机实时状态指令
     */
    private static byte[] esc = {0x10, 0x04, 0x02};

    /**
     * 查询打印机实时状态
     */
    private void queryPrinterState() {
        Timer timer = new Timer(true);
        //每5秒查询一次打印机的实时状态
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                //发送ESC查询打印机状态指令
                sendCommand = esc;
                Vector<Byte> data = new Vector<>(esc.length);
                for (byte anEsc : esc) {
                    data.add(anEsc);
                }
                sendDataImmediately(data);
            }
        }, 0, 5000);
    }

读取打印机的实时状态


    private void readPrinterState() {
        new Thread(new PrinterReader())
                .start();
    }

    class PrinterReader implements Runnable {
        private boolean isRun = false;

        private byte[] buffer = new byte[100];

        public PrinterReader() {
            isRun = true;
        }

        @Override
        public void run() {
            try {
                while (isRun) {
                    //读取打印机返回信息
                    int len = readDataImmediately(buffer);
                    if (len > 0) {
                        Message message = Message.obtain();
                        message.what = READ_DATA;
                        Bundle bundle = new Bundle();
                        bundle.putInt(READ_DATA_CNT, len);
                        bundle.putByteArray(READ_BUFFER_ARRAY, buffer);
                        message.setData(bundle);
                        mHandler.sendMessage(message);
                    }
                }
            } catch (Exception e) {
                if (deviceConnFactoryManagers.get(id) != null) {
                    closePort();
                }
            }
        }

        public void cancel() {
            isRun = false;
        }
    }

     static class MyHandler extends Handler {
        WeakReference<DeviceConnFactoryManager> reference;

        MyHandler(DeviceConnFactoryManager manager, Looper looper) {
            super(looper);
            reference = new WeakReference<>(manager);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (!ObjectUtils.checkNonNull(reference)) {
                return;
            }
            switch (msg.what) {
                case READ_DATA:
                    int cnt = msg.getData().getInt(READ_DATA_CNT);
                    byte[] buffer = msg.getData().getByteArray(READ_BUFFER_ARRAY);
                    //这里只对查询状态返回值做处理,其它返回值可参考编程手册来解析
                    if (buffer == null) {
                        return;
                    }
                    int result = reference.get().judgeResponseType(buffer[0]);
                    String status = BaseApplication.getInstance().getString(R.string.str_printer_conn_normal);
                    if (sendCommand == esc) {
                        //设置当前打印机模式为ESC模式
                        if (reference.get().currentPrinterCommand == null) {
                            reference.get().currentPrinterCommand = PrinterCommand.ESC;
                            reference.get().sendStateBroadcast(CONN_STATE_CONNECTED);
                        } else {//查询打印机状态
                            if (result == 0) {//打印机状态查询
                                Intent intent = new Intent(ACTION_QUERY_PRINTER_STATE);
                                intent.putExtra(DEVICE_ID, reference.get().id);
                                BaseApplication.getInstance().sendBroadcast(intent);
                            } else if (result == 1) {//查询打印机实时状态
                                if ((buffer[0] & ESC_STATE_PAPER_ERR) > 0) {
                                    status += " " + BaseApplication.getInstance().getString(R.string.str_printer_out_of_paper);
                                }
                                if ((buffer[0] & ESC_STATE_COVER_OPEN) > 0) {
                                    status += " " + BaseApplication.getInstance().getString(R.string.str_printer_open_cover);
                                }
                                if ((buffer[0] & ESC_STATE_ERR_OCCURS) > 0) {
                                    status += " " + BaseApplication.getInstance().getString(R.string.str_printer_error);
                                }
                                System.out.println(BaseApplication.getInstance().getString(R.string.str_state) + status);
                                ToastUtils.showShortToast(BaseApplication.getInstance(), status);
                            }
                        }
                    } else if (sendCommand == tsc) {
                        //设置当前打印机模式为TSC模式
                        if (reference.get().currentPrinterCommand == null) {
                            reference.get().currentPrinterCommand = PrinterCommand.TSC;
                            reference.get().sendStateBroadcast(CONN_STATE_CONNECTED);
                        } else {
                            if (cnt == 1) {//查询打印机实时状态
                                if ((buffer[0] & TSC_STATE_PAPER_ERR) > 0) {//缺纸
                                    status += " " + BaseApplication.getInstance().getString(R.string.str_printer_out_of_paper);
                                }
                                if ((buffer[0] & TSC_STATE_COVER_OPEN) > 0) {//开盖
                                    status += " " + BaseApplication.getInstance().getString(R.string.str_printer_open_cover);
                                }
                                if ((buffer[0] & TSC_STATE_ERR_OCCURS) > 0) {//打印机报错
                                    status += " " + BaseApplication.getInstance().getString(R.string.str_printer_error);
                                }
                                System.out.println(BaseApplication.getInstance().getString(R.string.str_state) + status);
                                ToastUtils.showShortToast(BaseApplication.getInstance(), status);
                            } else {//打印机状态查询
                                Intent intent = new Intent(ACTION_QUERY_PRINTER_STATE);
                                intent.putExtra(DEVICE_ID, reference.get().id);
                                BaseApplication.getInstance().sendBroadcast(intent);
                            }
                        }
                    }
                    break;
                default:
                    break;
            }
        }

嗯,采用这种方式开始自测是没问题,but,后来又发现问题了,app在前台的情况下,socket确实是一直没有断开,但是当点击home键返回桌面之后,一段时间之后,PrinterReader会抛异常,线程会终止。项目紧急,也懒得追根溯源。总之长连接在android 6.0之后是越来越难以确保不断开了!

private void readPrinterState() {
     new Thread(new PrinterReader())
             .start();
 }

阿西吧,唉呀妈呀脑瓜疼...继续思考...

叮~ 有了,既然长连接如此不可控,采用短连接如何,于是乎,代码改为了打印完了直接closePort,竟然就这么ok了,之后打印再没遇到问题~~~原来这么简单...

总结:长连接改为短连接,网络打印机功能恢复正常!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容