1. Выбор способа обмена. Описание API.

2. Реализация API на стороне 1С.

3. BroadcastReceiver. Получаем данные ШК на примере АТОЛ Smart.Lite


4. OnKeyUp. Получаем штрихкод со сканера с эмуляцией клавиатуры

В этой части хотелось бы заострить внимание на деталях. Во всех самоучителях, которые мне попадались, просто описаны функции и приблизительно куда это добавлять.


Давайте напишем же наше приложение, которое прослушивает широковещательные сообщения и показывает их во сплывающем окне. Создаем проект с Empty Activity.

Package name указываем "com.domain.barcodeTest" Учебников куча. Теперь в проекте создадим пакет(package). Для себя я его обозвал utils, т.к. не знаю куда же его еще отнести. С сетью он не работает, с БД тоже. На модель не похож.
Поэтому вот так.

image

На изображении выделено, где создать пакет. Внутри utils создаем класс(Kotlin File/Class). Выбираем что это класс, и обзываем его CustomBroadcastReceiver. Полный код файла:

CustomBroadcastReceiver.kt
package com.domain.barcodeTest.utils

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.widget.Toast


class CustomBroadcastReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {

        // Получаем тип и сам штрих код помещаем в переменные
        val type = intent.getStringExtra("EXTRA_BARCODE_DECODING_SYMBOLE")
        val barcode = intent.getStringExtra("EXTRA_BARCODE_DECODING_DATA")

        //Создаем конструктор строки. И помещаем в строку полученные данные.
        val sb = StringBuilder()
        sb.append("Type: $type, Barcode:$barcode\n")
        
        //Показываем что у нас получилось. В коротком всплывающем сообщении.
        Toast.makeText(context, sb.toString(), Toast.LENGTH_SHORT).show()
        
        //Переменная "data class", ее мы еще не описали по этому подсвечивает красным.
        // сейчас мы ее использовать не будем, она нам понадобится позже.
        val bc = Barcode(type, barcode)

    }
}
}


Мы создали свой класс получения широковещательных сообщений унаследовав все от BroadcastReceiver. В нем мы переопределили поведение функции получения сообщения (onReceive). На верхнем уровне "com.domain.barcodeTest" создадим еще один пакет models, и в нем создадим класс "Barcode". Полный код файла:

barcode.kt
package org.innova_it.mws.models

data class Barcode(
    val type: String?,
    val value: String?
)


Тем самым мы создали класс Barcode и создали для него конструктор. Все таки Kotlin хорош. Теперь мы его можем свободно использовать в классе CustomBroadcastReceiver, ошибка должна пропасть. В итоге у нас получилась следующая структура.

image

В пакетах мы храним похожие объекты. Если переводить на язык 1С, то в пакете models мы храним описание данных 1С. Справочники, документы с реквизитами и подчиненными объектами. Там же храним описание таблиц БД, и формат получения данных по сети. Забегая вперед. Вот типичный пример модели из предыдущей части для Номенклатуры. К нашему текущему проекту он не относится. Но для понимания проще с примером.

models/wares.kt

data class PayLoadWares(
    val quantity: Int,
    val wares: List<Ware>
)
//Описание таблицы где хранятся товары в sqlLite(Room)
//@SerializedName - так Retrofit понимает куда помещать данные из json

@Entity(tableName = "wares_table")
data class Ware(

    @PrimaryKey
    @NonNull
    @SerializedName("code")
    @Expose
    val code: String,

    @SerializedName("article")
    @Expose
    val article: String,

    @NonNull
    @SerializedName("name")
    @Expose
    val name: String,

    @SerializedName("fullName")
    @Expose
    val fullName: String,

    @NonNull
    @SerializedName("unit")
    @Expose
    val unit: String
)


data class WareResponse(
    val result : Result,
    val payload: PayLoadWares
)


Если представить проект как структуру 1С, то у нас каждый пакет содержал бы в себе классы: WaresModel(Модель справочника товаров, WaresManager(Менеджер справочника товаров), WaresObject(Объект справочника), WaresUI(Модуль формы), WaresActivity(Форма). Но в отличие от 1С, мы можем в модели описать общие свойства, методы для всех справочников, а потом от них наследоваться. В 1С это делает сама платформа, и нам об этом думать не приходиться.
Как описать интерфейсы на языке 1С, я даже не знаю. Это надо учить до полного просвещения. Дальше без этого будет невозможно.

Вернемся к нашему приложению. Слудующее что нам надо сделать, это подписать приложение на необходимые нам события. Подписывать мы его будем динамически, без использования AndroidManifest.xml.

Для этого нам понадобится добавить переменную и переопределить две процедуры в MainActivity.

Добавим переменную

class MainActivity : AppCompatActivity() {
// в классе MainActivity добавляем слудющий код. Тем самым мы вызвали конструктор "CustomBroadcastReceiver", который унаследован от "BroadcastReceiver"
    private val customBroadcastReceiver = CustomBroadcastReceiver()
...

Подпишемся на события. Переопределим поведение двух методов в MainActivity

override fun onResume() {

        super.onResume()
            registerReceiver(
                customBroadcastReceiver,
                IntentFilter ("com.xcheng.scanner.action.BARCODE_DECODING_BROADCAST")
            )
    }

    override fun onStop() {
        super.onStop()
        unregisterReceiver(customBroadcastReceiver)
    }

Думаю тут все достаточно прозрачно. Мы подписываем приложение на получение собщений с фильтром.На языке 1С При получении сообщения вызывается ВнешнееСобытие(onReceive), где Источник это "com.xcheng.scanner.action.BARCODE_DECODING_BROADCAST". Так себя обзывает внутренняя утилита для работы со сканером на АТОЛ Smart.Lite. А данные у нас это

  • "EXTRA_BARCODE_DECODING_SYMBOLE" — Тип ШК
  • "EXTRA_BARCODE_DECODING_DATA" — Сами данные

И собственно происходит обработка сообщения. Компилируем запускаем. Проверяем. Появились вопросы? Задаем в комментариях. Все. Теперь мы разработчики под Android. :)

Эта часть подойдет для получения желаемого результата. Но после нее, надо идти и изучать основы java. И только потом основы kotlin.

P.S. Тут я хотел бы обратиться к разработчикам Android. У меня сложилась странная ситуация. Раньше я подписывал приложение в OnCreate(), и отписывал в onStop(). Но после того как приложение уходило в onPause(), и при возобновление onResume() приложение при получении сообщения падало с ошибкой. Чем может быть вызвано такое поведение?

Комментарии (15)


  1. Neikist
    29.10.2019 14:23

    надо идти и изучать основы java. И только потом основы kotlin.

    Очень не согласен. Вполне можно и с котлина начинать. Другое дело что уметь читать java нужно, да.
    Зря в дата классе модельки поля nullable сделали, все равно ведь null у вас смысла не имеет для нее.
    Раньше я подписывал приложение в OnCreate(), и отписывал в onStop(). Но после того как приложение уходило в onPause(), и при возобновление onResume() приложение при получении сообщения падало с ошибкой. Чем может быть вызвано такое поведение?

    Ну так onCreate после разворачивания активити не вызывается. onCreate скорее зеркально onDestroy. А после onStop при сворачивании вызываются onRestart и onStart при разворачивании.
    Плюс советуют часто вызов super делать в конце методов onPause, onStop, onDestroy.

    Ну и кстати, лучше вам наверно onPause и onResume использовать для этих целей, если нужно чтобы бродкаст ресивер слушал только когда активити активно.


    1. loki82 Автор
      29.10.2019 14:28

      Зря в дата классе модельки поля nullable сделали

      Это про barcode? Я не знаю, что мне прилетит от китайских разработчиков. Поэтому на всякий случай.
      Ну так onCreate после разворачивания активити не вызывается

      Так в первом примере у меня было в onCreate без отписки. И при сворачивании приложения. Все равно сообщение прилетало. Но вот при сворачивании, разворачивании. Почему то падало, BroadcastReceivеr превращался в null.


      1. Neikist
        29.10.2019 14:30

        Да. Я бы все же даже в случае если прилетает null клал бы "". Либо вовсе не создавал Barcode. От бизнес логики зависит. Но гонять null это во первых не kotlin way, во вторых валидация у вас будет по месту использования, хотя обычно ее по месту получения данных выносят (хотя бывают случаи когда по месту использования, но думаю это не тот вариант).


        1. loki82 Автор
          29.10.2019 14:32

          Пока это был набросок. Понять как оно работает. В Kotlin я две недели. Замечания учту.


  1. AlexMaa
    29.10.2019 17:13

    Ксати «com.xcheng.scanner.action.BARCODE_DECODING_BROADCAST» в настройках утилиты это событие можно переназвать!
    насчет там что то было типа BroadcastReceiver.SetPriority
    Я создал сервис который слушает эти сообщения и отдает текущей активити


  1. loki82 Автор
    29.10.2019 17:14

    насчет там что то было типа BroadcastReceiver.SetPriority
    Я создал сервис который слушает эти сообщения и отдает текущей активити

    Не совсем понятно, а зачем это нужно? Или у вас сервис отдает в 1С: Мобильную платформу?


    1. AlexMaa
      29.10.2019 17:28

      Просто у меня на формах (андроид) много текстовых полей — и ТСД почему-то в режиме сообщений (не эмуляция клавиатура) в эти поля почему все равно добавляет — все что сканирует. Поэтому я решил так — если в «поле ввода данных» прилетает — то что прилетало уже сервис — то из поля ввода это удаляется.
      Возможно я не прав или ТСД как то странно работает. Но пока иду по этому пути.
      У меня похожий проект приемка товаров на ТСД (выгружай через http get данные из 1С Штрих-М и через post отправляю обратно и уже в 1С разбираю) мне нужно основное реализовать в краткие сроки — потом буду разбираться где косяки.
      Кстати ссылка на SDK
      fs.atol.ru/_layouts/15/atol.templates/Handlers/FileHandler.ashx?guid=67a78c6b-1933-4d1d-b5eb-3e1c16fae071&webUrl=


      1. loki82 Автор
        29.10.2019 17:33

        А накой этот SDK нужен? Я его посмотрел. Это исходники утилиты установленной на ТСД. При этом, там уже скомпилированные jar лежат для сканера. Попробуйте вариант из этой статьи. А в настройках переключите на BROADCAST_EVENT. Я подозреваю у вас установлен он в KEYBOARD/BROADCAST


        1. AlexMaa
          29.10.2019 17:49

          KEYBOARD/BROADCAST я знаю у меня именно стоит BROADCAST
          1) либо я «криворукий-рукожоп»
          2) либо у моего ТСД какой-то затык
          3) либо abortBroadcast() не проходит
          4) брал другой такой же ТСД, правда у него другая версия прошивки и сканер не Zebra 2D — там работает нормально!
          по этому пока не знаю на каком варианте остановиться — первый вариант — мне кажется более правильным :)

          Так времени на всесторонне тестирование сейчас нет (time dead line), жду когда фирма закажет еще устройств для тестирования :)


          1. loki82 Автор
            29.10.2019 18:09

            У меня как раз Сканера Zebra 2D. Утилита версии 1.1.1.

            либо abortBroadcast() не проходит

            Зачем это? ШК помещаете в LiveData. Подписываете на эти изменения. Дальше Room, выполняем некие действия. Там тоже помещаем в LiveData подписываемся на изменения. Вообще статью на эту тему я предполагаю написать ориентировочно через неделю.


            1. AlexMaa
              29.10.2019 21:27

              1) Применительно к abortBroadcast — если кто-то еще подписан на это событие хотелось бы оказаться в SetPriority и не передавать дальше.
              2) У меня версия 1.0.9 на другом ТСД была вообще другая нумерация.
              3) на моей версии при включенном только BROADCAST, если например открыть браузер и встать в строку ввода данных (url) — нажать сканировать — в строчку добавляется отсканированный код и все. Но если в утилите выставить событие KEYBOARD, то такое же действие, когда фокус находиться в любом поле ввода данных — прилетает «от сканированные данные» и за тем еще KEYCODE_ENTER — то есть типа происходить «эмуляция ввода информации»
              Интересно на вашей версии?


              1. loki82 Автор
                30.10.2019 00:25

                Все в режиме KEYBOARD_EVENT на моей версии. Есть события от сканера. Как их отловить не понимаю.
                D/SE4710Util: BarCodeReader: symbology = 1, length = -3
                D/SE4710Util: BarCodeReader: symbology = 11, length = 13
                D/SE4710Util: byte2String: data.length = 45
                D/SE4710Util: mDecodeResult = 8888888888888

                И приходят события в
                override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
                Toast.makeText(this, "$keyCode", Toast.LENGTH_SHORT).show()
                return super.onKeyDown(keyCode, event)
                }


      1. loki82 Автор
        29.10.2019 18:19

        Вообще вот. Очень стоит почитать. Как раз описывает архитектуру приложения.
        habr.com/ru/post/456256


  1. gromiloff
    01.11.2019 12:33

    Привет, могу ошибаться но событие BARCODE_DECODING_BROADCAST приходит при скане из оригинального приложения скана. если мне надо в своем приложении организовать работу, то ловятся события нажатия / отпускания на кнопку скана и потом через нативныую либу читается некоторая переменная памяти (по крайне мере именно так и работает сама приложенька в прошивке). Как при этом заставить работать свое приложение только на получение просканированного кода крайне не очевидно.


  1. loki82 Автор
    01.11.2019 12:37

    BARCODE_DECODING_BROADCAST — приходит при нажатии и отпускании кнопки. И пытаться получить ШК между KeyDown и KeyUp. Это крайне не благодарная затея. В таком случае лучше использовать префикс и суффикс. В четвертой части я как раз это и рассмотрел. По мотивам комментариев.